Challenge Java Spring Boot
Application de Gestion Touristique
TourismApp
Preparation aux Entretiens Techniques
Dr. BADR EL KHALYLY
Decembre 2025
Table des matières
1 Introduction et Contexte 3
1.1 Presentation du Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.1 Architecture Globale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Objectifs Pedagogiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.1 Modele Fonctionnel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Java Core - Fondamentaux OOP 5
2.1 Challenge 1.1 : Modelisation des Entites . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.1 Diagramme UML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3 Java Streams et Optional 8
3.1 Challenge 2.1 : Manipulation avec Streams . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.2 Challenge 2.2 : Maitrise de Optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
4 Gestion des Exceptions 11
4.1 Challenge 3.1 : Hierarchie d’Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4.1.1 Schema de la hierarchie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5 Spring Boot - Architecture 13
5.1 Challenge 4.1 : Structure du Projet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
5.2 Challenge 4.2 : Entite JPA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
5.3 Challenge 4.3 : DTOs et Validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
5.4 Challenge 4.4 : Repository et Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
6 API REST et Gestion Erreurs 17
6.1 Challenge 5.1 : Controller REST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
6.2 Challenge 5.2 : GlobalExceptionHandler . . . . . . . . . . . . . . . . . . . . . . . . . . 18
7 Tests 20
7.1 Challenge 6.1 : Tests Unitaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
7.2 Challenge 6.2 : Tests Integration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
8 DevOps - Docker et CI/CD 23
8.1 Challenge 7.1 : Dockerisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
8.2 Challenge 7.2 : Pipeline GitLab CI/CD . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
8.2.1 Schema Pipeline CI/CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
9 Questions d’Entretien 26
9.1 Java Core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
9.2 Spring Boot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
9.3 DevOps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
1
TABLE DES MATIÈRES TourismApp
10 Configuration Complete 27
2
Chapitre 1
Introduction et Contexte
1.1 Presentation du Challenge
Bienvenue dans ce challenge complet de developpement Java/Spring Boot ! Vous allez construire
TourismApp, une application de gestion touristique.
1.1.1 Architecture Globale
Client Web/Mobile
API REST
Service Layer
Repository JPA
PostgreSQL
Figure 1.1 – Architecture en couches
1.2 Objectifs Pedagogiques
Ce challenge couvre 100% des competences de la matrice junior :
— Java Core : OOP, Streams, Optional, Exceptions, Design Patterns
— Spring Boot : IoC/DI, REST API, Validation, i18n
— Persistance : JPA, Spring Data, Transactions
— Tests : JUnit 5, Mockito, Tests d’integration
— DevOps : Git, Docker, CI/CD GitLab
3
CHAPITRE 1. INTRODUCTION ET CONTEXTE TourismApp
1.2.1 Modele Fonctionnel
Destinations Hotels Excursions
Clients Reservations Avis
Figure 1.2 – Entites metier
4
Chapitre 2
Java Core - Fondamentaux OOP
2.1 Challenge 1.1 : Modelisation des Entites
Challenge
Creez la hierarchie de classes pour le domaine touristique :
1. Classe abstraite AbstractEntity avec id, createdAt, updatedAt
2. Interface Bookable pour les elements reservables
3. Classes : Destination, Hotel, Excursion
4. Implementez equals(), hashCode(), toString()
2.1.1 Diagramme UML
«abstract»
AbstractEntity «interface»
Bookable
- id: Long
- createdAt: LocalDateTime + getPricePerDay(): BigDecimal
- updatedAt: LocalDateTime + isAvailable(): boolean
+ equals(): boolean + getBookingDescription(): String
+ hashCode(): int
Hotel Destination Excursion
- name: String - name: String - title: String
- stars: int * - country: String - duration: int
- pricePerNight: BigDecimal
destination - description: String - price: BigDecimal
+ getPricePerDay() + getHotels(): List<Hotel> + getPricePerDay()
+ isAvailable() + isAvailable()
Figure 2.1 – Diagramme de classes UML – Heritage et Implementation
Solution
[Link]
1 public abstract class AbstractEntity {
2 protected Long id;
3 protected LocalDateTime createdAt;
4 protected LocalDateTime updatedAt;
5
6 protected AbstractEntity() {
7 [Link] = [Link]();
8 [Link] = [Link]();
5
CHAPITRE 2. JAVA CORE - FONDAMENTAUX OOP TourismApp
9 }
10
11 @Override
12 public boolean equals(Object o) {
13 if (this == o) return true;
14 if (o == null || getClass() != [Link]()) return false;
15 AbstractEntity that = (AbstractEntity) o;
16 return id != null && [Link](id, [Link]);
17 }
18
19 @Override
20 public int hashCode() {
21 return getClass().hashCode();
22 }
23 }
[Link]
1 public interface Bookable {
2 BigDecimal getPricePerDay();
3 boolean isAvailable(LocalDate start, LocalDate end);
4 String getBookingDescription();
5 }
[Link] (avec Builder Pattern)
1 public class Hotel extends AbstractEntity implements Bookable {
2 private String name;
3 private int stars;
4 private BigDecimal pricePerNight;
5 private Destination destination;
6
7 private Hotel(Builder builder) {
8 [Link] = [Link];
9 [Link] = [Link];
10 [Link] = [Link];
11 [Link] = [Link];
12 }
13
14 @Override
15 public BigDecimal getPricePerDay() { return pricePerNight; }
16
17 @Override
18 public boolean isAvailable(LocalDate start, LocalDate end) {
19 return true; // Logique simplifiee
20 }
21
22 @Override
23 public String getBookingDescription() {
24 return [Link]("Hotel %s (%d*)", name, stars);
25 }
26
27 // Builder Pattern
28 public static class Builder {
29 private String name;
30 private int stars;
31 private BigDecimal pricePerNight;
32 private Destination destination;
33
34 public Builder name(String name) {
35 [Link] = name; return this;
36 }
37 public Builder stars(int stars) {
6
CHAPITRE 2. JAVA CORE - FONDAMENTAUX OOP TourismApp
38 if (stars < 1 || stars > 5)
39 throw new IllegalArgumentException("Stars: 1-5");
40 [Link] = stars; return this;
41 }
42 public Builder price(BigDecimal price) {
43 [Link] = price; return this;
44 }
45 public Builder destination(Destination d) {
46 [Link] = d; return this;
47 }
48 public Hotel build() {
49 [Link](name);
50 return new Hotel(this);
51 }
52 }
53 public static Builder builder() { return new Builder(); }
54 }
Questions d’entretien
Q1 : Pourquoi hashCode() retourne getClass().hashCode() ?
R : Pour les entites JPA, l’id peut etre null avant persistance. Cette approche garantit le contrat
equals/hashCode.
Q2 : Interface vs classe abstraite - quand utiliser chacune ?
R : Interface : contrat, heritage multiple. Abstraite : partage de code/etat, heritage simple.
Q3 : Avantages du Builder Pattern ?
R : Code lisible, validation a la construction, objets immutables, parametres optionnels sans sur-
charge.
7
Chapitre 3
Java Streams et Optional
3.1 Challenge 2.1 : Manipulation avec Streams
Challenge
Implementez dans TourismAnalytics :
1. Hotels 4+ etoiles d’une destination
2. Prix moyen par pays (groupingBy)
3. Top 5 destinations reservees
4. Chiffre d’affaires par client
Solution
1 public class TourismAnalytics {
2 private final List<Hotel> hotels;
3 private final List<Reservation> reservations;
4
5 // 1. Hotels premium d’une destination
6 public List<Hotel> findPremiumHotels(Destination dest) {
7 return [Link]()
8 .filter(h -> [Link]().equals(dest))
9 .filter(h -> [Link]() >= 4)
10 .sorted([Link](Ho[Link]
11 .collect([Link]());
12 }
13
14 // 2. Prix moyen par pays
15 public Map<String, Double> avgPriceByCountry() {
16 return [Link]()
17 .collect([Link](
18 h -> [Link]().getCountry(),
19 [Link](
20 h -> [Link]().doubleValue())
21 ));
22 }
23
24 // 3. Top N destinations
25 public List<Destination> topDestinations(int limit) {
26 return [Link]()
27 .map(r -> [Link]().getDestination())
28 .collect([Link](
29 [Link](), [Link]()))
30 .entrySet().stream()
31 .sorted([Link].<Destination,Long>comparingByValue()
32 .reversed())
8
CHAPITRE 3. JAVA STREAMS ET OPTIONAL TourismApp
33 .limit(limit)
34 .map([Link]::getKey)
35 .collect([Link]());
36 }
37
38 // 4. CA par client
39 public Map<Client, BigDecimal> revenueByClient() {
40 return [Link]()
41 .collect([Link](
42 Reservation::getClient,
43 [Link]([Link],
44 Reservation::getTotalPrice, BigDecimal::add)
45 ));
46 }
47 }
3.2 Challenge 2.2 : Maitrise de Optional
Challenge
Refactorisez ce code avec Optional :
1 public String getCountry(Reservation r) {
2 if (r != null) {
3 Hotel h = [Link]();
4 if (h != null) {
5 Destination d = [Link]();
6 if (d != null) return [Link]();
7 }
8 }
9 return "Unknown";
10 }
Solution
1 // Version avec Optional
2 public String getCountry(Reservation r) {
3 return [Link](r)
4 .map(Reservation::getHotel)
5 .map(Ho[Link]
6 .map(Destination::getCountry)
7 .orElse("Unknown");
8 }
9
10 // Autre exemple
11 public void processReservation(Long id) {
12 [Link](id)
13 .filter(Reservation::isConfirmed)
14 .ifPresentOrElse(
15 this::sendConfirmation,
16 () -> [Link]("Not found: {}", id));
17 }
Attention
Anti-patterns Optional :
— Ne jamais utiliser comme champ de classe
— Ne jamais utiliser en parametre de methode
9
CHAPITRE 3. JAVA STREAMS ET OPTIONAL TourismApp
— Preferer orElseGet() a orElse() pour calculs couteux
Questions d’entretien
Q : Difference entre map() et flatMap() sur Optional ?
R : map() emballe dans Optional. flatMap() pour fonctions retournant deja Optional (evite
Optional<Optional<T»).
10
Chapitre 4
Gestion des Exceptions
4.1 Challenge 3.1 : Hierarchie d’Exceptions
Challenge
Creez une hierarchie d’exceptions metier avec codes d’erreur et support i18n.
4.1.1 Schema de la hierarchie
RuntimeException
TourismException
ResourceNotFoundBookingExceptionValidationException
Figure 4.1 – Hierarchie des exceptions
Solution
[Link]
1 public enum ErrorCode {
2 HOTEL_NOT_FOUND("ERR_001", "[Link]"),
3 DESTINATION_NOT_FOUND("ERR_002", "[Link]"),
4 HOTEL_NOT_AVAILABLE("ERR_100", "[Link]"),
5 INVALID_DATE_RANGE("ERR_101", "[Link]");
6
7 private final String code;
8 private final String messageKey;
9
10 ErrorCode(String code, String key) {
11 [Link] = code; [Link] = key;
12 }
13 public String getCode() { return code; }
14 public String getMessageKey() { return messageKey; }
15 }
[Link]
1 public class TourismException extends RuntimeException {
2 private final ErrorCode errorCode;
3 private final Map<String, Object> params = new HashMap<>();
4
5 public TourismException(ErrorCode errorCode) {
11
CHAPITRE 4. GESTION DES EXCEPTIONS TourismApp
6 super([Link]());
7 [Link] = errorCode;
8 }
9
10 public TourismException addParam(String key, Object val) {
11 [Link](key, val);
12 return this;
13 }
14
15 public ErrorCode getErrorCode() { return errorCode; }
16 public Map<String, Object> getParams() { return params; }
17 }
[Link]
1 public class ResourceNotFoundException extends TourismException {
2 public ResourceNotFoundException(ErrorCode code,
3 String type, Object id) {
4 super(code);
5 addParam("resourceType", type);
6 addParam("resourceId", id);
7 }
8
9 public static ResourceNotFoundException hotel(Long id) {
10 return new ResourceNotFoundException(
11 ErrorCode.HOTEL_NOT_FOUND, "Hotel", id);
12 }
13 }
Questions d’entretien
Q : Checked vs Unchecked exceptions ?
R : Checked (Exception) : doit etre declaree, erreurs recuperables. Unchecked (RuntimeExcep-
tion) : erreurs de programmation, gestion centralisee.
12
Chapitre 5
Spring Boot - Architecture
5.1 Challenge 4.1 : Structure du Projet
Controller - @RestController : HotelController, ReservationController
DTO
Service - @Service : HotelService, BookingService
Entity
Repository - @Repository : HotelRepository, DestinationRepository
Domain - Entities : Hotel, Destination, Reservation
Figure 5.1 – Architecture en couches Spring Boot
1 src/main/java/com/tourism/
2 +-- controller/
3 | +-- [Link]
4 | +-- advice/[Link]
5 +-- service/
6 | +-- [Link]
7 | +-- impl/[Link]
8 +-- repository/
9 | +-- [Link]
10 +-- domain/
11 | +-- entity/[Link]
12 | +-- dto/[Link], [Link]
13 +-- exception/
14 +-- [Link], [Link]
5.2 Challenge 4.2 : Entite JPA
Solution
1 @Entity
2 @Table(name = "hotels")
3 public class Hotel extends AbstractEntity {
4
5 @Column(nullable = false)
6 private String name;
7
8 @Column(nullable = false)
9 private int stars;
10
11 @Column(name = "price_per_night", precision = 10, scale = 2)
13
CHAPITRE 5. SPRING BOOT - ARCHITECTURE TourismApp
12 private BigDecimal pricePerNight;
13
14 @ManyToOne(fetch = [Link])
15 @JoinColumn(name = "destination_id", nullable = false)
16 private Destination destination;
17
18 @OneToMany(mappedBy = "hotel", cascade = [Link])
19 private List<Reservation> reservations = new ArrayList<>();
20
21 private boolean active = true;
22
23 protected Hotel() {} // JPA
24
25 public Hotel(String name, int stars, BigDecimal price,
26 Destination dest) {
27 [Link] = name;
28 [Link] = stars;
29 [Link] = price;
30 [Link] = dest;
31 }
32 // Getters/Setters...
33 }
5.3 Challenge 4.3 : DTOs et Validation
Solution
[Link] (Record)
1 public record HotelDTO(
2 Long id,
3 String name,
4 int stars,
5 BigDecimal pricePerNight,
6 String destinationName,
7 String country
8 ) {
9 public static HotelDTO from(Hotel h) {
10 return new HotelDTO([Link](), [Link](), [Link](),
11 [Link](),
12 [Link]().getName(),
13 [Link]().getCountry());
14 }
15 }
[Link]
1 public class CreateHotelRequest {
2 @NotBlank(message = "{[Link]}")
3 @Size(min = 2, max = 100)
4 private String name;
5
6 @NotNull @Min(1) @Max(5)
7 private Integer stars;
8
9 @NotNull
10 @DecimalMin(value = "0.01")
11 @Digits(integer = 8, fraction = 2)
12 private BigDecimal pricePerNight;
13
14 @NotNull
14
CHAPITRE 5. SPRING BOOT - ARCHITECTURE TourismApp
15 private Long destinationId;
16
17 // Getters/Setters...
18 }
messages_fr.properties
1 [Link]=Le nom est obligatoire
2 [Link]=Hotel {resourceId} non trouve
3 [Link]=Hotel indisponible du {startDate} au {endDate}
messages_en.properties
1 [Link]=Hotel name is required
2 [Link]=Hotel {resourceId} not found
3 [Link]=Hotel unavailable from {startDate} to {endDate}
5.4 Challenge 4.4 : Repository et Service
Solution
[Link]
1 @Repository
2 public interface HotelRepository extends JpaRepository<Hotel, Long> {
3 // Requete derivee
4 List<Hotel> findByDestinationIdAndActiveTrue(Long destId);
5
6 // JPQL
7 @Query("SELECT h FROM Hotel h WHERE [Link] = :country " +
8 "AND [Link] <= :maxPrice AND [Link] = true")
9 List<Hotel> findByCountryAndMaxPrice(
10 @Param("country") String country,
11 @Param("maxPrice") BigDecimal maxPrice);
12 }
[Link]
1 @Slf4j
2 @Service
3 @RequiredArgsConstructor
4 @Transactional(readOnly = true)
5 public class HotelServiceImpl implements HotelService {
6 private final HotelRepository hotelRepo;
7 private final DestinationRepository destRepo;
8 private final HotelMapper mapper;
9
10 @Override
11 @Transactional
12 public HotelDTO createHotel(CreateHotelRequest req) {
13 [Link]("Creating hotel: {}", [Link]());
14
15 Destination dest = [Link]([Link]())
16 .orElseThrow(() -> ResourceNotFoundException
17 .destination([Link]()));
18
19 Hotel hotel = [Link](req);
20 [Link](dest);
21 return [Link]([Link](hotel));
22 }
23
24 @Override
25 public HotelDTO getById(Long id) {
15
CHAPITRE 5. SPRING BOOT - ARCHITECTURE TourismApp
26 return [Link](id)
27 .map(mapper::toDTO)
28 .orElseThrow(() -> [Link](id));
29 }
30
31 @Override
32 public Page<HotelDTO> getAll(Pageable pageable) {
33 return [Link](pageable).map(mapper::toDTO);
34 }
35 }
16
Chapitre 6
API REST et Gestion Erreurs
6.1 Challenge 5.1 : Controller REST
Solution
1 @RestController
2 @RequestMapping("/api/v1/hotels")
3 @RequiredArgsConstructor
4 public class HotelController {
5 private final HotelService hotelService;
6
7 @PostMapping
8 @ResponseStatus([Link])
9 public ResponseEntity<HotelDTO> create(
10 @Valid @RequestBody CreateHotelRequest request) {
11 return [Link]([Link])
12 .body([Link](request));
13 }
14
15 @GetMapping("/{id}")
16 public ResponseEntity<HotelDTO> getById(@PathVariable Long id) {
17 return [Link]([Link](id));
18 }
19
20 @GetMapping
21 public ResponseEntity<Page<HotelDTO>> getAll(Pageable pageable) {
22 return [Link]([Link](pageable));
23 }
24
25 @PutMapping("/{id}")
26 public ResponseEntity<HotelDTO> update(@PathVariable Long id,
27 @Valid @RequestBody UpdateHotelRequest request) {
28 return [Link]([Link](id, request));
29 }
30
31 @DeleteMapping("/{id}")
32 @ResponseStatus(HttpStatus.NO_CONTENT)
33 public ResponseEntity<Void> delete(@PathVariable Long id) {
34 [Link](id);
35 return [Link]().build();
36 }
37 }
17
CHAPITRE 6. API REST ET GESTION ERREURS TourismApp
6.2 Challenge 5.2 : GlobalExceptionHandler
Solution
1 @Slf4j
2 @RestControllerAdvice
3 @RequiredArgsConstructor
4 public class GlobalExceptionHandler {
5 private final MessageSource messageSource;
6
7 @ExceptionHandler([Link])
8 public ResponseEntity<ApiError> handleNotFound(
9 ResourceNotFoundException ex) {
10 [Link]("Resource not found: {}", [Link]());
11
12 String msg = resolveMessage([Link]().getMessageKey(),
13 [Link]());
14 ApiError error = [Link]()
15 .timestamp([Link]())
16 .status(HttpStatus.NOT_FOUND.value())
17 .code([Link]().getCode())
18 .message(msg).build();
19
20 return [Link](HttpStatus.NOT_FOUND).body(error);
21 }
22
23 @ExceptionHandler([Link])
24 public ResponseEntity<ApiError> handleValidation(
25 MethodArgumentNotValidException ex) {
26 Map<String, String> errors = new HashMap<>();
27 [Link]().getFieldErrors().forEach(e ->
28 [Link]([Link](), [Link]()));
29
30 ApiError error = [Link]()
31 .timestamp([Link]())
32 .status(HttpStatus.BAD_REQUEST.value())
33 .code("ERR_VALIDATION")
34 .message("Validation failed")
35 .fieldErrors(errors).build();
36
37 return [Link]().body(error);
38 }
39
40 private String resolveMessage(String key, Map<String, Object> params) {
41 Locale locale = [Link]();
42 String msg = [Link](key, null, key, locale);
43 for (var e : [Link]())
44 msg = [Link]("{" + [Link]() + "}",
45 [Link]([Link]()));
46 return msg;
47 }
48 }
[Link]
1 @Data @Builder
2 @JsonInclude([Link].NON_NULL)
3 public class ApiError {
4 private LocalDateTime timestamp;
5 private int status;
6 private String code;
7 private String message;
8 private Map<String, String> fieldErrors;
18
CHAPITRE 6. API REST ET GESTION ERREURS TourismApp
9 }
19
Chapitre 7
Tests
7.1 Challenge 6.1 : Tests Unitaires
Solution
1 @ExtendWith([Link])
2 @DisplayName("HotelService Tests")
3 class HotelServiceTest {
4 @Mock private HotelRepository hotelRepo;
5 @Mock private DestinationRepository destRepo;
6 @Mock private HotelMapper mapper;
7 @InjectMocks private HotelServiceImpl service;
8
9 private Destination testDest;
10 private Hotel testHotel;
11
12 @BeforeEach
13 void setUp() {
14 testDest = new Destination();
15 [Link](1L);
16 [Link]("Paris");
17
18 testHotel = new Hotel("Test", 4,
19 new BigDecimal("150"), testDest);
20 [Link](1L);
21 }
22
23 @Test
24 @DisplayName("Should create hotel successfully")
25 void shouldCreateHotel() {
26 CreateHotelRequest req = new CreateHotelRequest();
27 [Link]("New Hotel");
28 [Link](1L);
29
30 when([Link](1L)).thenReturn([Link](testDest));
31 when([Link](req)).thenReturn(testHotel);
32 when([Link](any())).thenReturn(testHotel);
33 when([Link](testHotel)).thenReturn(
34 new HotelDTO(1L, "Test", 4, null, "Paris", "France"));
35
36 HotelDTO result = [Link](req);
37
38 assertThat(result).isNotNull();
39 assertThat([Link]()).isEqualTo("Test");
40 verify(hotelRepo).save(any([Link]));
41 }
42
20
CHAPITRE 7. TESTS TourismApp
43 @Test
44 @DisplayName("Should throw when destination not found")
45 void shouldThrowWhenDestNotFound() {
46 CreateHotelRequest req = new CreateHotelRequest();
47 [Link](999L);
48
49 when([Link](999L)).thenReturn([Link]());
50
51 assertThatThrownBy(() -> [Link](req))
52 .isInstanceOf([Link]);
53 verify(hotelRepo, never()).save(any());
54 }
55 }
7.2 Challenge 6.2 : Tests Integration
Solution
1 @SpringBootTest
2 @AutoConfigureMockMvc
3 @ActiveProfiles("test")
4 @Transactional
5 class HotelControllerIT {
6 @Autowired private MockMvc mockMvc;
7 @Autowired private ObjectMapper objectMapper;
8 @Autowired private DestinationRepository destRepo;
9
10 private Destination testDest;
11
12 @BeforeEach
13 void setUp() {
14 testDest = new Destination();
15 [Link]("Marrakech");
16 [Link]("Maroc");
17 testDest = [Link](testDest);
18 }
19
20 @Test
21 void shouldCreateHotel() throws Exception {
22 CreateHotelRequest req = new CreateHotelRequest();
23 [Link]("Riad Luxe");
24 [Link](5);
25 [Link](new BigDecimal("300"));
26 [Link]([Link]());
27
28 [Link](post("/api/v1/hotels")
29 .contentType(MediaType.APPLICATION_JSON)
30 .content([Link](req)))
31 .andExpect(status().isCreated())
32 .andExpect(jsonPath("$.name").value("Riad Luxe"))
33 .andExpect(jsonPath("$.stars").value(5));
34 }
35
36 @Test
37 void shouldReturnValidationErrors() throws Exception {
38 CreateHotelRequest req = new CreateHotelRequest();
39 [Link](""); // Invalid
40 [Link](10); // Invalid
41
42 [Link](post("/api/v1/hotels")
21
CHAPITRE 7. TESTS TourismApp
43 .contentType(MediaType.APPLICATION_JSON)
44 .content([Link](req)))
45 .andExpect(status().isBadRequest())
46 .andExpect(jsonPath("$.[Link]").exists());
47 }
48 }
[Link]
1 spring:
2 datasource:
3 url: jdbc:h2:mem:testdb
4 driver-class-name: [Link]
5 jpa:
6 [Link]-auto: create-drop
22
Chapitre 8
DevOps - Docker et CI/CD
8.1 Challenge 7.1 : Dockerisation
Solution
Dockerfile
1 # Build stage
2 FROM maven:3.9-eclipse-temurin-21 AS builder
3 WORKDIR /app
4 COPY [Link] .
5 RUN mvn dependency:go-offline
6 COPY src ./src
7 RUN mvn clean package -DskipTests
8
9 # Runtime stage
10 FROM eclipse-temurin:21-jre-alpine
11 WORKDIR /app
12 RUN addgroup -S app && adduser -S app -G app
13 USER app
14 COPY --from=builder /app/target/*.jar [Link]
15 EXPOSE 8080
16 HEALTHCHECK --interval=30s --timeout=3s \
17 CMD wget -q --spider [Link] || exit 1
18 ENTRYPOINT ["java", "-jar", "[Link]"]
[Link]
1 version: ’3.8’
2 services:
3 app:
4 build: .
5 ports:
6 - "8080:8080"
7 environment:
8 - SPRING_PROFILES_ACTIVE=docker
9 - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/tourismdb
10 - SPRING_DATASOURCE_USERNAME=tourism
11 - SPRING_DATASOURCE_PASSWORD=tourism123
12 depends_on:
13 db:
14 condition: service_healthy
15
16 db:
17 image: postgres:15-alpine
18 ports:
19 - "5432:5432"
20 environment:
21 - POSTGRES_DB=tourismdb
22 - POSTGRES_USER=tourism
23 - POSTGRES_PASSWORD=tourism123
24 volumes:
25 - postgres_data:/var/lib/postgresql/data
26 healthcheck:
23
CHAPITRE 8. DEVOPS - DOCKER ET CI/CD TourismApp
27 test: ["CMD-SHELL", "pg_isready -U tourism"]
28 interval: 10s
29 timeout: 5s
30 retries: 5
31
32 volumes:
33 postgres_data:
8.2 Challenge 7.2 : Pipeline GitLab CI/CD
Solution
1 stages:
2 - build
3 - test
4 - package
5 - deploy
6
7 variables:
8 MAVEN_OPTS: "-[Link]=.m2/repository"
9
10 cache:
11 paths:
12 - .m2/repository/
13
14 build:
15 stage: build
16 image: maven:3.9-eclipse-temurin-21
17 script:
18 - mvn clean compile -DskipTests
19 artifacts:
20 paths: [target/]
21
22 test:
23 stage: test
24 image: maven:3.9-eclipse-temurin-21
25 script:
26 - mvn test
27 artifacts:
28 reports:
29 junit: target/surefire-reports/*.xml
30
31 docker-build:
32 stage: package
33 image: docker:24
34 services:
35 - docker:24-dind
36 script:
37 - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
38 - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
39 - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
40 only:
41 - main
42
43 deploy-staging:
44 stage: deploy
45 script:
46 - echo "Deploy to staging..."
47 environment:
48 name: staging
49 only:
50 - develop
51
52 deploy-prod:
53 stage: deploy
54 script:
55 - echo "Deploy to production..."
56 environment:
57 name: production
24
CHAPITRE 8. DEVOPS - DOCKER ET CI/CD TourismApp
58 when: manual
59 only:
60 - main
8.2.1 Schema Pipeline CI/CD
Build Test Quality Package Deploy
Figure 8.1 – Pipeline CI/CD
25
Chapitre 9
Questions d’Entretien
9.1 Java Core
Questions d’entretien
Q1 : Difference entre == et equals() ?
R : == compare les references, equals() le contenu logique.
Q2 : Contrat equals/hashCode ?
R : Si [Link](b) alors [Link]() == [Link]().
Q3 : Streams paralleles - risques ?
R : Overhead, operations non thread-safe, ordre non garanti.
Q4 : Qu’est-ce qu’une Functional Interface ?
R : Interface avec une seule methode abstraite (Predicate, Function, Consumer).
9.2 Spring Boot
Questions d’entretien
Q1 : IoC et Dependency Injection ?
R : IoC : le framework controle le cycle de vie. DI : injection des dependances par le framework.
Q2 : @Component vs @Service vs @Repository ?
R : Stereotypes Spring. @Service : logique metier. @Repository : acces donnees + traduction ex-
ceptions.
Q3 : @Transactional ?
R : Gestion transactions via AOP. Rollback sur RuntimeException par defaut.
Q4 : Validation Spring Boot ?
R : Annotations JSR-380 + @Valid. Erreurs via @ExceptionHandler.
9.3 DevOps
Questions d’entretien
Q1 : Image Docker vs Container ?
R : Image : template read-only. Container : instance executable.
Q2 : Multi-stage build ?
R : Plusieurs FROM pour separer build/runtime. Image finale legere.
Q3 : Etapes pipeline CI/CD ?
R : Build → Test → Quality → Package → Deploy.
26
Chapitre 10
Configuration Complete
1 spring:
2 application:
3 name: tourism-app
4 messages:
5 basename: messages
6 encoding: UTF-8
7 jpa:
8 open-in-view: false
9 hibernate:
10 ddl-auto: validate
11 jackson:
12 serialization:
13 write-dates-as-timestamps: false
14
15 server:
16 port: 8080
17
18 management:
19 endpoints:
20 web:
21 exposure:
22 include: health,info,metrics
23
24 logging:
25 level:
26 [Link]: DEBUG
Bonne chance !
Dr. BADR EL KHALYLY
Decembre 2025
27