0% ont trouvé ce document utile (0 vote)
5 vues28 pages

Challenge Tourisme Java

Le document présente un challenge de développement d'une application de gestion touristique, TourismApp, utilisant Java et Spring Boot. Il couvre divers aspects techniques, y compris la modélisation des entités, la manipulation de données avec des streams, la gestion des exceptions, et l'architecture de l'application. Des objectifs pédagogiques sont définis pour guider l'apprentissage des compétences nécessaires en développement Java et DevOps.

Transféré par

Galand Noah
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
5 vues28 pages

Challenge Tourisme Java

Le document présente un challenge de développement d'une application de gestion touristique, TourismApp, utilisant Java et Spring Boot. Il couvre divers aspects techniques, y compris la modélisation des entités, la manipulation de données avec des streams, la gestion des exceptions, et l'architecture de l'application. Des objectifs pédagogiques sont définis pour guider l'apprentissage des compétences nécessaires en développement Java et DevOps.

Transféré par

Galand Noah
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

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

Vous aimerez peut-être aussi