Evaluation Article: A quick and practical example of Hexagonal Architecture in Java
This commit is contained in:
parent
b532ae2a0c
commit
ac30817a23
@ -0,0 +1,20 @@
|
||||
package com.baeldung.hexagonal;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
|
||||
|
||||
|
||||
@SpringBootApplication(exclude={
|
||||
CassandraAutoConfiguration.class,
|
||||
MongoDataAutoConfiguration.class,
|
||||
MongoAutoConfiguration.class
|
||||
})
|
||||
public class HexagonalSpringApplication {
|
||||
|
||||
public static void main(final String[] args) {
|
||||
SpringApplication.run(HexagonalSpringApplication.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.baeldung.hexagonal.adapter;
|
||||
|
||||
import com.baeldung.hexagonal.domain.Booking;
|
||||
import com.baeldung.hexagonal.port.BookingPersistencePort;
|
||||
import com.baeldung.hexagonal.repository.BookingRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class BookingPersistenceAdapter implements BookingPersistencePort {
|
||||
|
||||
@Autowired
|
||||
private BookingRepository bookingRepository;
|
||||
|
||||
public boolean persist(Booking booking) {
|
||||
return bookingRepository.save(booking);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.baeldung.hexagonal.adapter;
|
||||
|
||||
import com.baeldung.hexagonal.port.BookingServicePort;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static com.baeldung.hexagonal.port.BookingServicePort.BookingRequest;
|
||||
import static com.baeldung.hexagonal.port.BookingServicePort.BookingResponse;
|
||||
import static com.baeldung.hexagonal.port.BookingServicePort.BookingResponse.*;
|
||||
|
||||
@RestController
|
||||
public class RestAPIEndpointAdapter {
|
||||
|
||||
private BookingServicePort bookingServicePort;
|
||||
|
||||
@Autowired
|
||||
public RestAPIEndpointAdapter(BookingServicePort bookingServicePort) {
|
||||
this.bookingServicePort = bookingServicePort;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/booking")
|
||||
public ResponseEntity<BookingResponse> createBooking(@RequestBody BookingRequest request) {
|
||||
BookingResponse response = bookingServicePort.book(request);
|
||||
|
||||
if (response.getStatusCode() == SEAT_NOT_AVAILABLE
|
||||
|| response.getStatusCode() == PAYMENT_FAILED){
|
||||
return new ResponseEntity<BookingResponse>(response, HttpStatus.PRECONDITION_FAILED);
|
||||
} else if (response.getStatusCode() == UNKNOWN_ERROR) {
|
||||
return new ResponseEntity<BookingResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
return new ResponseEntity<BookingResponse>(response, HttpStatus.CREATED);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.baeldung.hexagonal.adapter;
|
||||
|
||||
import com.baeldung.hexagonal.external.service.TheatreService;
|
||||
import com.baeldung.hexagonal.port.TheatreServicePort;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
public class TheatreServiceAdapter implements TheatreServicePort {
|
||||
|
||||
@Autowired
|
||||
private TheatreService theatreService;
|
||||
|
||||
public Optional<String> reserveSeats(String theatreId, String movieShowId, Set<String> seats) {
|
||||
ResponseEntity<TheatreService.Reservation> response = theatreService.postReservation(theatreId, movieShowId, seats);
|
||||
if (response.getStatusCode() == HttpStatus.CREATED) {
|
||||
return Optional.of(response.getBody().getId());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public boolean releaseSeats(String resrevationId) {
|
||||
ResponseEntity<?> response = theatreService.deleteReservation(resrevationId);
|
||||
return response.getStatusCode() == HttpStatus.NO_CONTENT;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.baeldung.hexagonal.adapter;
|
||||
|
||||
import com.baeldung.hexagonal.external.service.CustomerWalletService;
|
||||
import com.baeldung.hexagonal.port.WalletServicePort;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class WalletServiceAdapter implements WalletServicePort {
|
||||
|
||||
private final CustomerWalletService customerWalletService;
|
||||
|
||||
@Autowired
|
||||
public WalletServiceAdapter(CustomerWalletService customerWalletService) {
|
||||
this.customerWalletService = customerWalletService;
|
||||
}
|
||||
|
||||
public boolean debit(String customerId, Double amount) {
|
||||
HttpStatus response = customerWalletService.postDebit(customerId, amount);
|
||||
return response == HttpStatus.CREATED;
|
||||
}
|
||||
}
|
84
ddd/src/main/java/com/baeldung/hexagonal/domain/Booking.java
Normal file
84
ddd/src/main/java/com/baeldung/hexagonal/domain/Booking.java
Normal file
@ -0,0 +1,84 @@
|
||||
package com.baeldung.hexagonal.domain;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class Booking {
|
||||
private String bookingId;
|
||||
private String movieShowId;
|
||||
private String theatreId;
|
||||
private String customerId;
|
||||
private Set<String> seats;
|
||||
private Double amount;
|
||||
private Status status;
|
||||
|
||||
public enum Status {
|
||||
INITIAL, SUCCESS, FAILURE
|
||||
}
|
||||
|
||||
public Booking(
|
||||
String bookingId, String movieShowId, String theatreId, String customerId, Set<String> seats, Double amount, Status status) {
|
||||
this.bookingId = bookingId;
|
||||
this.movieShowId = movieShowId;
|
||||
this.theatreId = theatreId;
|
||||
this.customerId = customerId;
|
||||
this.seats = seats;
|
||||
this.amount = amount;
|
||||
this.status = Status.INITIAL;
|
||||
}
|
||||
|
||||
public String getMovieShowId() {
|
||||
return movieShowId;
|
||||
}
|
||||
|
||||
public String getTheatreId() {
|
||||
return theatreId;
|
||||
}
|
||||
|
||||
public Set<String> getSeats() {
|
||||
return seats;
|
||||
}
|
||||
|
||||
public String getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public String getBookingId() {
|
||||
return bookingId;
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setBookingId(String bookingId) {
|
||||
this.bookingId = bookingId;
|
||||
}
|
||||
|
||||
public void setMovieShowId(String movieShowId) {
|
||||
this.movieShowId = movieShowId;
|
||||
}
|
||||
|
||||
public void setTheatreId(String theatreId) {
|
||||
this.theatreId = theatreId;
|
||||
}
|
||||
|
||||
public void setCustomerId(String customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public void setSeats(Set<String> seats) {
|
||||
this.seats = seats;
|
||||
}
|
||||
|
||||
public void setAmount(Double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public void setStatus(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
8
ddd/src/main/java/com/baeldung/hexagonal/external/service/CustomerWalletService.java
vendored
Normal file
8
ddd/src/main/java/com/baeldung/hexagonal/external/service/CustomerWalletService.java
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
package com.baeldung.hexagonal.external.service;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public interface CustomerWalletService {
|
||||
|
||||
HttpStatus postDebit(String customerId, Double amount);
|
||||
}
|
11
ddd/src/main/java/com/baeldung/hexagonal/external/service/MockCustomerWalletService.java
vendored
Normal file
11
ddd/src/main/java/com/baeldung/hexagonal/external/service/MockCustomerWalletService.java
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package com.baeldung.hexagonal.external.service;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class MockCustomerWalletService implements CustomerWalletService {
|
||||
public HttpStatus postDebit(String customerId, Double amount) {
|
||||
return HttpStatus.CREATED;
|
||||
}
|
||||
}
|
21
ddd/src/main/java/com/baeldung/hexagonal/external/service/MockTheatreService.java
vendored
Normal file
21
ddd/src/main/java/com/baeldung/hexagonal/external/service/MockTheatreService.java
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package com.baeldung.hexagonal.external.service;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class MockTheatreService implements TheatreService {
|
||||
|
||||
public ResponseEntity<Reservation> postReservation(String theatreId, String movieShowId, Set<String> seats) {
|
||||
return new ResponseEntity<Reservation>(
|
||||
new Reservation(UUID.randomUUID().toString()), HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
public ResponseEntity<?> deleteReservation(String reservationId) {
|
||||
return new ResponseEntity(HttpStatus.NO_CONTENT);
|
||||
}
|
||||
}
|
24
ddd/src/main/java/com/baeldung/hexagonal/external/service/TheatreService.java
vendored
Normal file
24
ddd/src/main/java/com/baeldung/hexagonal/external/service/TheatreService.java
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package com.baeldung.hexagonal.external.service;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface TheatreService {
|
||||
|
||||
ResponseEntity<Reservation> postReservation(String theatreId, String movieShowId, Set<String> seats);
|
||||
|
||||
ResponseEntity<?> deleteReservation(String reservationId);
|
||||
|
||||
class Reservation {
|
||||
private final String id;
|
||||
|
||||
public Reservation(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.baeldung.hexagonal.port;
|
||||
|
||||
import com.baeldung.hexagonal.domain.Booking;
|
||||
|
||||
public interface BookingPersistencePort {
|
||||
boolean persist(Booking booking);
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package com.baeldung.hexagonal.port;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface BookingServicePort {
|
||||
|
||||
BookingResponse book(BookingRequest request);
|
||||
|
||||
class BookingRequest {
|
||||
private String movieShowId;
|
||||
private String customerId;
|
||||
private String theatreId;
|
||||
private Set<String> seats;
|
||||
private Double amount;
|
||||
|
||||
public String getMovieShowId() {
|
||||
return movieShowId;
|
||||
}
|
||||
|
||||
public void setMovieShowId(String movieShowId) {
|
||||
this.movieShowId = movieShowId;
|
||||
}
|
||||
|
||||
public String getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(String customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public String getTheatreId() {
|
||||
return theatreId;
|
||||
}
|
||||
|
||||
public void setTheatreId(String theatreId) {
|
||||
this.theatreId = theatreId;
|
||||
}
|
||||
|
||||
public Set<String> getSeats() {
|
||||
return seats;
|
||||
}
|
||||
|
||||
public void setSeats(Set<String> seats) {
|
||||
this.seats = seats;
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(Double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
class BookingResponse {
|
||||
public static final int SUCCESS = 0;
|
||||
public static final int SEAT_NOT_AVAILABLE = 1;
|
||||
public static final int PAYMENT_FAILED = 2;
|
||||
public static final int UNKNOWN_ERROR = 3;
|
||||
|
||||
private final int statusCode;
|
||||
|
||||
public BookingResponse(int statusCode) {
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.baeldung.hexagonal.port;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public interface TheatreServicePort {
|
||||
Optional<String> reserveSeats(String theatreId, String movieShowId, Set<String> seats);
|
||||
boolean releaseSeats(String resrevationId);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.baeldung.hexagonal.port;
|
||||
|
||||
public interface WalletServicePort {
|
||||
boolean debit(String customerId, Double amount);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.baeldung.hexagonal.repository;
|
||||
|
||||
import com.baeldung.hexagonal.domain.Booking;
|
||||
|
||||
public interface BookingRepository {
|
||||
|
||||
boolean save(Booking booking);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.baeldung.hexagonal.repository;
|
||||
|
||||
import com.baeldung.hexagonal.domain.Booking;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class MockBookingRepository implements BookingRepository {
|
||||
public boolean save(Booking booking) {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package com.baeldung.hexagonal.usecase;
|
||||
|
||||
import com.baeldung.hexagonal.domain.Booking;
|
||||
import com.baeldung.hexagonal.port.BookingPersistencePort;
|
||||
import com.baeldung.hexagonal.port.BookingServicePort;
|
||||
import com.baeldung.hexagonal.port.TheatreServicePort;
|
||||
import com.baeldung.hexagonal.port.WalletServicePort;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.baeldung.hexagonal.port.BookingServicePort.BookingResponse.*;
|
||||
|
||||
@Component
|
||||
public class BookTicketUseCase implements BookingServicePort {
|
||||
|
||||
private BookingPersistencePort bookingPersistencePort;
|
||||
private TheatreServicePort theatreServicePort;
|
||||
private WalletServicePort walletServicePort;
|
||||
|
||||
@Autowired
|
||||
public BookTicketUseCase(BookingPersistencePort bookingPersistencePort, TheatreServicePort theatreServicePort, WalletServicePort walletServicePort) {
|
||||
this.bookingPersistencePort = bookingPersistencePort;
|
||||
this.theatreServicePort = theatreServicePort;
|
||||
this.walletServicePort = walletServicePort;
|
||||
}
|
||||
|
||||
public BookingResponse book(BookingRequest request) {
|
||||
|
||||
Booking booking = new Booking(
|
||||
UUID.randomUUID().toString(),
|
||||
request.getMovieShowId(),
|
||||
request.getTheatreId(),
|
||||
request.getCustomerId(),
|
||||
request.getSeats(),
|
||||
request.getAmount(),
|
||||
Booking.Status.INITIAL);
|
||||
|
||||
if (!bookingPersistencePort.persist(booking)) {
|
||||
return new BookingResponse(UNKNOWN_ERROR);
|
||||
}
|
||||
|
||||
Optional<String> reservationIdOptional = theatreServicePort.reserveSeats(
|
||||
booking.getTheatreId(),
|
||||
booking.getMovieShowId(),
|
||||
booking.getSeats());
|
||||
if (!reservationIdOptional.isPresent()) {
|
||||
booking.setStatus(Booking.Status.FAILURE);
|
||||
bookingPersistencePort.persist(booking);
|
||||
return new BookingResponse(SEAT_NOT_AVAILABLE);
|
||||
}
|
||||
|
||||
if (!walletServicePort.debit(booking.getCustomerId(), booking.getAmount())) {
|
||||
reservationIdOptional.ifPresent(reservationId -> theatreServicePort.releaseSeats(reservationId));
|
||||
booking.setStatus(Booking.Status.FAILURE);
|
||||
bookingPersistencePort.persist(booking);
|
||||
return new BookingResponse(PAYMENT_FAILED);
|
||||
}
|
||||
|
||||
booking.setStatus(Booking.Status.SUCCESS);
|
||||
bookingPersistencePort.persist(booking);
|
||||
|
||||
return new BookingResponse(SUCCESS);
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package com.baeldung.hexagonal.adapter;
|
||||
|
||||
import com.baeldung.hexagonal.port.BookingServicePort;
|
||||
import com.baeldung.hexagonal.port.BookingServicePort.BookingRequest;
|
||||
import com.baeldung.hexagonal.port.BookingServicePort.BookingResponse;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import static com.baeldung.hexagonal.port.BookingServicePort.BookingResponse.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class RestAPIEndpointAdapterUnitTest {
|
||||
|
||||
private BookingServicePort bookingServicePort;
|
||||
private RestAPIEndpointAdapter restAPIEndpointAdapter;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
bookingServicePort = mock(BookingServicePort.class);
|
||||
restAPIEndpointAdapter = new RestAPIEndpointAdapter(bookingServicePort);
|
||||
}
|
||||
|
||||
private BookingServicePort.BookingRequest getBookingRequest() {
|
||||
BookingServicePort.BookingRequest request = new BookingServicePort.BookingRequest();
|
||||
request.setTheatreId("theatre-id");
|
||||
request.setMovieShowId("movie-show-id");
|
||||
request.setCustomerId("customer-id");
|
||||
request.setSeats(new HashSet<>(Arrays.asList("A1", "A2")));
|
||||
request.setAmount(100.00);
|
||||
return request;
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBookingServicePortReturnsUnknownError_thenReturnInternalServerError() {
|
||||
BookingServicePort.BookingRequest request = getBookingRequest();
|
||||
when(bookingServicePort.book(any(BookingRequest.class))).thenReturn(new BookingResponse(UNKNOWN_ERROR));
|
||||
|
||||
ResponseEntity<BookingResponse> response = restAPIEndpointAdapter.createBooking(request);
|
||||
|
||||
verify(bookingServicePort).book(any(BookingRequest.class));
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBookingServicePortReturnsSeatUnavailable_thenReturnPreconditionFailed() {
|
||||
BookingServicePort.BookingRequest request = getBookingRequest();
|
||||
when(bookingServicePort.book(any(BookingRequest.class))).thenReturn(new BookingResponse(SEAT_NOT_AVAILABLE));
|
||||
|
||||
ResponseEntity<BookingResponse> response = restAPIEndpointAdapter.createBooking(request);
|
||||
|
||||
verify(bookingServicePort).book(any(BookingRequest.class));
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.PRECONDITION_FAILED, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBookingServicePortReturnsPaymentFailed_thenReturnPreconditionFailed() {
|
||||
BookingServicePort.BookingRequest request = getBookingRequest();
|
||||
when(bookingServicePort.book(any(BookingRequest.class))).thenReturn(new BookingResponse(PAYMENT_FAILED));
|
||||
|
||||
ResponseEntity<BookingResponse> response = restAPIEndpointAdapter.createBooking(request);
|
||||
|
||||
verify(bookingServicePort).book(any(BookingRequest.class));
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.PRECONDITION_FAILED, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBookingServicePortReturnsSuccess_thenReturnCreated() {
|
||||
BookingServicePort.BookingRequest request = getBookingRequest();
|
||||
when(bookingServicePort.book(any(BookingRequest.class))).thenReturn(new BookingResponse(SUCCESS));
|
||||
|
||||
ResponseEntity<BookingResponse> response = restAPIEndpointAdapter.createBooking(request);
|
||||
|
||||
verify(bookingServicePort).book(any(BookingRequest.class));
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.CREATED, response.getStatusCode());
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package com.baeldung.hexagonal.usecase;
|
||||
|
||||
import com.baeldung.hexagonal.domain.Booking;
|
||||
import com.baeldung.hexagonal.port.BookingPersistencePort;
|
||||
import com.baeldung.hexagonal.port.BookingServicePort;
|
||||
import com.baeldung.hexagonal.port.TheatreServicePort;
|
||||
import com.baeldung.hexagonal.port.WalletServicePort;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class BookTicketUseCaseUnitTest {
|
||||
|
||||
private BookingPersistencePort bookingPersistencePort;
|
||||
private TheatreServicePort theatreServicePort;
|
||||
private WalletServicePort walletServicePort;
|
||||
private BookTicketUseCase bookTicketUseCase;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
bookingPersistencePort = mock(BookingPersistencePort.class);
|
||||
theatreServicePort = mock(TheatreServicePort.class);
|
||||
walletServicePort = mock(WalletServicePort.class);
|
||||
bookTicketUseCase = new BookTicketUseCase(bookingPersistencePort, theatreServicePort, walletServicePort);
|
||||
}
|
||||
|
||||
private BookingServicePort.BookingRequest getBookingRequest() {
|
||||
BookingServicePort.BookingRequest request = new BookingServicePort.BookingRequest();
|
||||
request.setTheatreId("theatre-id");
|
||||
request.setMovieShowId("movie-show-id");
|
||||
request.setCustomerId("customer-id");
|
||||
request.setSeats(new HashSet<>(Arrays.asList("A1", "A2")));
|
||||
request.setAmount(100.00);
|
||||
return request;
|
||||
}
|
||||
|
||||
private Booking getBooking(BookingServicePort.BookingRequest request) {
|
||||
return new Booking(
|
||||
"booking-id",
|
||||
request.getMovieShowId(),
|
||||
request.getTheatreId(),
|
||||
request.getCustomerId(),
|
||||
request.getSeats(),
|
||||
request.getAmount(),
|
||||
Booking.Status.INITIAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenErrorInInitialPersistence_thenReturnUnknownError() {
|
||||
BookingServicePort.BookingRequest request = getBookingRequest();
|
||||
Booking booking = getBooking(request);
|
||||
when(bookingPersistencePort.persist(any(Booking.class))).thenReturn(false);
|
||||
BookingServicePort.BookingResponse response = bookTicketUseCase.book(request);
|
||||
|
||||
verify(bookingPersistencePort, times(1)).persist(any(Booking.class));
|
||||
assertNotNull(response);
|
||||
assertEquals(BookingServicePort.BookingResponse.UNKNOWN_ERROR, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenErrorInReserveSeats_thenReturnSeatNotAvailable() {
|
||||
BookingServicePort.BookingRequest request = getBookingRequest();
|
||||
Booking booking = getBooking(request);
|
||||
when(bookingPersistencePort.persist(any(Booking.class))).thenReturn(true);
|
||||
when(theatreServicePort.reserveSeats(booking.getTheatreId(), booking.getMovieShowId(), booking.getSeats()))
|
||||
.thenReturn(Optional.empty());
|
||||
BookingServicePort.BookingResponse response = bookTicketUseCase.book(request);
|
||||
|
||||
verify(bookingPersistencePort, times(2)).persist(any(Booking.class));
|
||||
verify(theatreServicePort).reserveSeats(any(String.class), any(String.class), any(HashSet.class));
|
||||
assertNotNull(response);
|
||||
assertEquals(BookingServicePort.BookingResponse.SEAT_NOT_AVAILABLE, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenErrorInWalletDebit_thenReturnPaymentFailed() {
|
||||
BookingServicePort.BookingRequest request = getBookingRequest();
|
||||
Booking booking = getBooking(request);
|
||||
when(bookingPersistencePort.persist(any(Booking.class))).thenReturn(true);
|
||||
when(theatreServicePort.reserveSeats(booking.getTheatreId(), booking.getMovieShowId(), booking.getSeats()))
|
||||
.thenReturn(Optional.of("reservation-id"));
|
||||
when(walletServicePort.debit(booking.getCustomerId(), booking.getAmount()))
|
||||
.thenReturn(false);
|
||||
BookingServicePort.BookingResponse response = bookTicketUseCase.book(request);
|
||||
|
||||
verify(bookingPersistencePort, times(2)).persist(any(Booking.class));
|
||||
verify(theatreServicePort).reserveSeats(any(String.class), any(String.class), any(HashSet.class));
|
||||
verify(walletServicePort).debit(any(String.class), any(Double.class));
|
||||
verify(theatreServicePort).releaseSeats(any(String.class));
|
||||
assertNotNull(response);
|
||||
assertEquals(BookingServicePort.BookingResponse.PAYMENT_FAILED, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoErrorInAnyPorts_thenReturnSuccess() {
|
||||
BookingServicePort.BookingRequest request = getBookingRequest();
|
||||
Booking booking = getBooking(request);
|
||||
when(bookingPersistencePort.persist(any(Booking.class))).thenReturn(true);
|
||||
when(theatreServicePort.reserveSeats(booking.getTheatreId(), booking.getMovieShowId(), booking.getSeats()))
|
||||
.thenReturn(Optional.of("reservation-id"));
|
||||
when(walletServicePort.debit(booking.getCustomerId(), booking.getAmount()))
|
||||
.thenReturn(true);
|
||||
BookingServicePort.BookingResponse response = bookTicketUseCase.book(request);
|
||||
|
||||
verify(bookingPersistencePort, times(2)).persist(any(Booking.class));
|
||||
verify(theatreServicePort).reserveSeats(any(String.class), any(String.class), any(HashSet.class));
|
||||
verify(walletServicePort).debit(any(String.class), any(Double.class));
|
||||
assertNotNull(response);
|
||||
assertEquals(BookingServicePort.BookingResponse.SUCCESS, response.getStatusCode());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user