Compare commits
6 Commits
cancel-res
...
borrow
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5e4767a9c | ||
|
|
8dc64f2c79 | ||
|
|
ba246cf0c9 | ||
|
|
a557dea9a9 | ||
|
|
b2f9c7f56f | ||
|
|
6b0aeb4141 |
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.2.6.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>io.wkrzywiec.hexagonal</groupId>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.wkrzywiec.hexagonal.library.BookTestData;
|
||||
import io.wkrzywiec.hexagonal.library.UserTestData;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BookReservationCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowBookCommand;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.AFTER_TEST_METHOD;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class BorrowBookComponentTest {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
private String baseURL;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
this.baseURL = "http://localhost:" + port;
|
||||
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
@DisplayName("Borrow reserved book")
|
||||
@Sql({"/book-and-user.sql", "/available-book.sql"})
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
public void givenBookIsReserved_thenBorrowIt_thenBookIsBorrowed() {
|
||||
//given
|
||||
Long homoDeusBookId = jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM book WHERE title = ?",
|
||||
Long.class,
|
||||
BookTestData.homoDeusBookTitle());
|
||||
|
||||
Long activeUserId = jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM user WHERE email = ?",
|
||||
Long.class,
|
||||
UserTestData.johnDoeEmail());
|
||||
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO public.reserved (book_id, user_id) VALUES (?, ?)",
|
||||
homoDeusBookId,
|
||||
activeUserId);
|
||||
|
||||
BorrowBookCommand borrowBookCommand =
|
||||
BorrowBookCommand.builder()
|
||||
.bookId(homoDeusBookId )
|
||||
.userId(activeUserId)
|
||||
.build();
|
||||
|
||||
//when
|
||||
given()
|
||||
.contentType("application/json")
|
||||
.body(borrowBookCommand)
|
||||
.when()
|
||||
.post( baseURL + "/borrow")
|
||||
.prettyPeek()
|
||||
.then();
|
||||
|
||||
Long reservationId = jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM borrowed WHERE book_id = ?",
|
||||
Long.class,
|
||||
homoDeusBookId);
|
||||
|
||||
assertTrue(reservationId > 0);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package io.wkrzywiec.hexagonal.library.domain.borrowing;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.wkrzywiec.hexagonal.library.BookTestData;
|
||||
import io.wkrzywiec.hexagonal.library.UserTestData;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BookReservationCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.inventory.infrastructure.BookRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -51,7 +52,7 @@ public class MakeReservationComponentTest {
|
||||
Long activeUserId = jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM user WHERE email = ?",
|
||||
Long.class,
|
||||
"john.doe@test.com");
|
||||
UserTestData.johnDoeEmail());
|
||||
|
||||
BookReservationCommand reservationCommand =
|
||||
BookReservationCommand.builder()
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.application;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowBookCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.BorrowBook;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/borrow")
|
||||
@RequiredArgsConstructor
|
||||
public class BorrowBookController {
|
||||
|
||||
@Qualifier("BorrowBook")
|
||||
private final BorrowBook borrowBook;
|
||||
|
||||
@PostMapping("")
|
||||
public ResponseEntity<String> borrowBook(@RequestBody BorrowBookCommand borrowBookCommand){
|
||||
borrowBook.handle(borrowBookCommand);
|
||||
return new ResponseEntity<>("Book with an id " + borrowBookCommand.getBookId() + " was borrowed", HttpStatus.CREATED);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.MakeBookAvaila
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.MakeBookAvailable;
|
||||
import io.wkrzywiec.hexagonal.library.domain.inventory.core.model.NewBookWasAddedEvent;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -11,6 +12,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class NewBookWasAddedEventHandler {
|
||||
|
||||
@Qualifier("MakeBookAvailable")
|
||||
private final MakeBookAvailable makeBookAvailable;
|
||||
|
||||
@EventListener
|
||||
|
||||
@@ -2,11 +2,13 @@ package io.wkrzywiec.hexagonal.library.domain.borrowing.application;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.CancelOverdueReservations;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class OverdueReservationScheduler {
|
||||
|
||||
@Qualifier("CancelOverdueReservations")
|
||||
private final CancelOverdueReservations overdueReservations;
|
||||
|
||||
@Scheduled(fixedRate = 10 * 1000)
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.wkrzywiec.hexagonal.library.domain.borrowing.application;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BookReservationCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.ReserveBook;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequiredArgsConstructor
|
||||
public class ReservationController {
|
||||
|
||||
@Qualifier("ReserveBook")
|
||||
private final ReserveBook reserveBook;
|
||||
|
||||
@PostMapping("")
|
||||
|
||||
@@ -4,6 +4,8 @@ import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ActiveUser;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.AvailableBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BookReservationCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BookReservedEvent;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowBookCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.DueDate;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.MakeBookAvailableCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.OverdueReservation;
|
||||
@@ -11,6 +13,8 @@ import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservationDet
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.exception.ActiveUserNotFoundException;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.exception.AvailableBookNotFoundExeption;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.exception.ReservedBookNotFoundException;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.BorrowBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.CancelOverdueReservations;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.MakeBookAvailable;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.ReserveBook;
|
||||
@@ -20,8 +24,9 @@ import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.outgoing.Borro
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BorrowingFacade implements MakeBookAvailable, ReserveBook, CancelOverdueReservations {
|
||||
public class BorrowingFacade implements MakeBookAvailable, ReserveBook, CancelOverdueReservations, BorrowBook {
|
||||
|
||||
private final BorrowingDatabase database;
|
||||
private final BorrowingEventPublisher eventPublisher;
|
||||
@@ -59,4 +64,17 @@ public class BorrowingFacade implements MakeBookAvailable, ReserveBook, CancelOv
|
||||
overdueReservationList.forEach(
|
||||
overdue -> database.setBookAvailable(overdue.getBookIdentificationAsLong()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(BorrowBookCommand borrowBookCommand) {
|
||||
ActiveUser activeUser =
|
||||
database.getActiveUser(borrowBookCommand.getUserId())
|
||||
.orElseThrow(() -> new ActiveUserNotFoundException(borrowBookCommand.getUserId()));
|
||||
ReservedBook reservedBook =
|
||||
database.getReservedBook(borrowBookCommand.getBookId())
|
||||
.orElseThrow(() -> new ReservedBookNotFoundException(borrowBookCommand.getBookId()));
|
||||
|
||||
BorrowedBook borrowedBook = activeUser.borrow(reservedBook);
|
||||
database.save(borrowedBook);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,16 @@ public class ActiveUser {
|
||||
|
||||
private final Long id;
|
||||
private final List<ReservedBook> reservedBooks;
|
||||
private final List<BorrowedBook> borrowedBooks;
|
||||
|
||||
public ActiveUser(Long id, List<ReservedBook> reservedBooks) {
|
||||
public ActiveUser(Long id, List<ReservedBook> reservedBooks, List<BorrowedBook> borrowedBooks) {
|
||||
this.id = id;
|
||||
this.reservedBooks = reservedBooks;
|
||||
this.borrowedBooks = borrowedBooks;
|
||||
}
|
||||
|
||||
public ReservedBook reserve(AvailableBook availableBook){
|
||||
if (reservedBooks.size() < 3){
|
||||
if (hasUserNotReachedLimitOfBooks()){
|
||||
ReservedBook reservedBook = new ReservedBook(availableBook.getIdAsLong(), id);
|
||||
reservedBooks.add(reservedBook);
|
||||
return reservedBook;
|
||||
@@ -26,6 +28,16 @@ public class ActiveUser {
|
||||
}
|
||||
}
|
||||
|
||||
public BorrowedBook borrow(ReservedBook reservedBook) {
|
||||
if (hasUserNotReachedLimitOfBooks()){
|
||||
BorrowedBook borrowedBook = new BorrowedBook(reservedBook.getIdAsLong(), id);
|
||||
borrowedBooks.add(borrowedBook);
|
||||
return borrowedBook;
|
||||
} else {
|
||||
throw new TooManyBooksAssignedToUserException(id);
|
||||
}
|
||||
}
|
||||
|
||||
public Long getIdAsLong(){
|
||||
return id;
|
||||
}
|
||||
@@ -33,4 +45,12 @@ public class ActiveUser {
|
||||
public List<ReservedBook> getReservedBookList(){
|
||||
return reservedBooks;
|
||||
}
|
||||
|
||||
public List<BorrowedBook> getBorrowedBookList() {
|
||||
return borrowedBooks;
|
||||
}
|
||||
|
||||
private boolean hasUserNotReachedLimitOfBooks(){
|
||||
return reservedBooks.size() + borrowedBooks.size() < 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.core.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class BookIdentification {
|
||||
private final Long value;
|
||||
|
||||
public Long getValueAsLong(){
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.core.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Builder
|
||||
public class BorrowBookCommand {
|
||||
private Long bookId;
|
||||
private Long userId;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.core.model;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class BorrowedBook implements Book {
|
||||
|
||||
private final Long bookId;
|
||||
private final Long userId;
|
||||
private final Instant borrowedDate;
|
||||
|
||||
public BorrowedBook(Long bookId, Long userId) {
|
||||
this.bookId = bookId;
|
||||
this.userId = userId;
|
||||
this.borrowedDate = Instant.now();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getIdAsLong() {
|
||||
return bookId;
|
||||
}
|
||||
|
||||
public Long getAssignedUserIdAsLong(){
|
||||
return userId;
|
||||
}
|
||||
|
||||
public Instant getBorrowedDateAsInstant(){
|
||||
return borrowedDate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.exception;
|
||||
|
||||
public class ReservedBookNotFoundException extends RuntimeException {
|
||||
public ReservedBookNotFoundException(Long bookId) {
|
||||
super("There is no reserved book with an ID: " + bookId,
|
||||
null,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowBookCommand;
|
||||
|
||||
public interface BorrowBook {
|
||||
void handle(BorrowBookCommand borrowBookCommand);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.outgoing;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ActiveUser;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.AvailableBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.DueDate;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.OverdueReservation;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservationDetails;
|
||||
@@ -15,5 +16,7 @@ public interface BorrowingDatabase {
|
||||
Optional<AvailableBook> getAvailableBook(Long bookId);
|
||||
Optional<ActiveUser> getActiveUser(Long userId);
|
||||
ReservationDetails save(ReservedBook reservedBook);
|
||||
void save(BorrowedBook borrowedBook);
|
||||
List<OverdueReservation> findReservationsAfter(DueDate dueDate);
|
||||
Optional<ReservedBook> getReservedBook(Long bookId);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing.infrastructure;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.infrastructure.entity.OverdueReservationEntity;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ActiveUser;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.AvailableBook;
|
||||
@@ -65,19 +66,8 @@ public class BorrowingDatabaseAdapter implements BorrowingDatabase {
|
||||
}
|
||||
|
||||
List<ReservedBook> reservedBooksByUser = getReservedBooksByUser(userId);
|
||||
return Optional.of(new ActiveUser(userId, reservedBooksByUser));
|
||||
}
|
||||
|
||||
private List<ReservedBook> getReservedBooksByUser(Long userId) {
|
||||
try {
|
||||
return jdbcTemplate.queryForList(
|
||||
"SELECT book_id FROM reserved WHERE reserved.user_id = ?",
|
||||
ReservedBook.class,
|
||||
userId
|
||||
);
|
||||
} catch (DataAccessException exception){
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<BorrowedBook> borrowedBooksByUser = getBorrowedBooksByUser(userId);
|
||||
return Optional.of(new ActiveUser(userId, reservedBooksByUser, borrowedBooksByUser));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,6 +89,24 @@ public class BorrowingDatabaseAdapter implements BorrowingDatabase {
|
||||
return new ReservationDetails(reservationId, reservedBook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(BorrowedBook borrowedBook) {
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO borrowed (book_id, user_id, borrowed_date) VALUES (?, ?, ?)",
|
||||
borrowedBook.getIdAsLong(),
|
||||
borrowedBook.getAssignedUserIdAsLong(),
|
||||
borrowedBook.getBorrowedDateAsInstant());
|
||||
|
||||
jdbcTemplate.update(
|
||||
"DELETE FROM reserved WHERE book_id = ?",
|
||||
borrowedBook.getIdAsLong());
|
||||
|
||||
jdbcTemplate.update(
|
||||
"DELETE FROM available WHERE book_id = ?",
|
||||
borrowedBook.getIdAsLong());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OverdueReservation> findReservationsAfter(DueDate dueDate) {
|
||||
List<OverdueReservationEntity> entities = jdbcTemplate.query(
|
||||
@@ -109,4 +117,33 @@ public class BorrowingDatabaseAdapter implements BorrowingDatabase {
|
||||
.map(entity -> new OverdueReservation(entity.getReservationId(), entity.getBookIdentification()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ReservedBook> getReservedBook(Long bookId) {
|
||||
try {
|
||||
return Optional.ofNullable(
|
||||
jdbcTemplate.queryForObject(
|
||||
"SELECT book_id AS bookId, user_id AS userId, reserved_date AS reservedDate FROM reserved WHERE reserved.book_id = ?",
|
||||
ReservedBook.class,
|
||||
bookId));
|
||||
} catch (DataAccessException exception) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private List<ReservedBook> getReservedBooksByUser(Long userId) {
|
||||
try {
|
||||
return jdbcTemplate.queryForList(
|
||||
"SELECT book_id FROM reserved WHERE reserved.user_id = ?",
|
||||
ReservedBook.class,
|
||||
userId
|
||||
);
|
||||
} catch (DataAccessException exception){
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
private List<BorrowedBook> getBorrowedBooksByUser(Long userId) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package io.wkrzywiec.hexagonal.library.infrastructure;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.BorrowingFacade;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.infrastructure.BorrowingDatabaseAdapter;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.infrastructure.SpringBorrowingEventPublisherAdapter;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.BorrowBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.CancelOverdueReservations;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.MakeBookAvailable;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.incoming.ReserveBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.outgoing.BorrowingDatabase;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.ports.outgoing.BorrowingEventPublisher;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.infrastructure.BorrowingDatabaseAdapter;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.infrastructure.SpringBorrowingEventPublisherAdapter;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
public class BorrowingDomainConfig {
|
||||
@@ -23,7 +28,26 @@ public class BorrowingDomainConfig {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("MakeBookAvailable")
|
||||
public MakeBookAvailable makeBookAvailable(BorrowingDatabase database, BorrowingEventPublisher borrowingEventPublisher) {
|
||||
return new BorrowingFacade(database, borrowingEventPublisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("ReserveBook")
|
||||
public ReserveBook reserveBook(BorrowingDatabase database, BorrowingEventPublisher borrowingEventPublisher){
|
||||
return new BorrowingFacade(database, borrowingEventPublisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("BorrowBook")
|
||||
public BorrowBook borrowBook(BorrowingDatabase database, BorrowingEventPublisher borrowingEventPublisher){
|
||||
return new BorrowingFacade(database, borrowingEventPublisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("CancelOverdueReservations")
|
||||
public CancelOverdueReservations cancelOverdueReservations(BorrowingDatabase database, BorrowingEventPublisher borrowingEventPublisher){
|
||||
return new BorrowingFacade(database, borrowingEventPublisher);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.wkrzywiec.hexagonal.library.domain.borrowing;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ActiveUser;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowBookCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservedBook;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class BorrowTestData {
|
||||
|
||||
public static BorrowBookCommand anyBorrowBookCommand(Long bookId, Long userId){
|
||||
return BorrowBookCommand.builder()
|
||||
.bookId(bookId)
|
||||
.userId(userId)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ReservedBook anyReservedBook(Long bookId, Long userId){
|
||||
return new ReservedBook(bookId, userId);
|
||||
}
|
||||
|
||||
public static ActiveUser anyActiveUser(Long userId){
|
||||
return new ActiveUser(userId, new ArrayList<ReservedBook>(), new ArrayList<BorrowedBook>());
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import io.wkrzywiec.hexagonal.library.domain.borrowing.core.BorrowingFacade;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ActiveUser;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.AvailableBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BookReservationCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowBookCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.MakeBookAvailableCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.exception.ActiveUserNotFoundException;
|
||||
@@ -169,4 +170,22 @@ public class BorrowingFacadeTest {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Successfully borrow a book")
|
||||
public void givenReservedBookAndActiveUser_whenBorrowing_thenBookIsBorrowed(){
|
||||
//given
|
||||
BorrowBookCommand borrowBookCommand = BorrowTestData.anyBorrowBookCommand(100L, 100L);
|
||||
ReservedBook reservedBook = BorrowTestData.anyReservedBook(borrowBookCommand.getBookId(), borrowBookCommand.getUserId());
|
||||
ActiveUser activeUser = BorrowTestData.anyActiveUser(borrowBookCommand.getUserId());
|
||||
|
||||
database.activeUsers.put(activeUser.getIdAsLong(), activeUser);
|
||||
database.reservedBooks.put(reservedBook.getIdAsLong(), reservedBook);
|
||||
|
||||
//when
|
||||
facade.handle(borrowBookCommand);
|
||||
|
||||
//then
|
||||
assertEquals(1, activeUser.getBorrowedBookList().size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.wkrzywiec.hexagonal.library.domain.borrowing;
|
||||
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ActiveUser;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.AvailableBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.DueDate;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.OverdueReservation;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservationDetails;
|
||||
@@ -20,6 +21,7 @@ public class InMemoryBorrowingDatabase implements BorrowingDatabase {
|
||||
ConcurrentHashMap<Long, ActiveUser> activeUsers = new ConcurrentHashMap<>();
|
||||
ConcurrentHashMap<Long, AvailableBook> availableBooks = new ConcurrentHashMap<>();
|
||||
ConcurrentHashMap<Long, ReservedBook> reservedBooks = new ConcurrentHashMap<>();
|
||||
ConcurrentHashMap<Long, BorrowedBook> borrowedBooks = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void setBookAvailable(Long bookId) {
|
||||
@@ -49,10 +51,16 @@ public class InMemoryBorrowingDatabase implements BorrowingDatabase {
|
||||
public ReservationDetails save(ReservedBook reservedBook) {
|
||||
Long reservationId = new Random().nextLong();
|
||||
availableBooks.remove(reservedBook.getIdAsLong());
|
||||
reservedBooks.put(reservationId, reservedBook);
|
||||
reservedBooks.put(reservationId, reservedBook);
|
||||
return new ReservationDetails(new ReservationId(reservationId), reservedBook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(BorrowedBook borrowedBook) {
|
||||
reservedBooks.remove(borrowedBook.getIdAsLong());
|
||||
borrowedBooks.put(borrowedBook.getIdAsLong(), borrowedBook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OverdueReservation> findReservationsAfter(DueDate dueDate) {
|
||||
return reservedBooks.values().stream()
|
||||
@@ -65,4 +73,9 @@ public class InMemoryBorrowingDatabase implements BorrowingDatabase {
|
||||
reservedBook.getIdAsLong()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ReservedBook> getReservedBook(Long bookId) {
|
||||
return Optional.of(reservedBooks.get(bookId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.wkrzywiec.hexagonal.library.domain.borrowing;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ActiveUser;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.AvailableBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BookReservationCommand;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservedBook;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -25,6 +26,6 @@ public class ReservationTestData {
|
||||
}
|
||||
|
||||
public static ActiveUser anyActiveUser(Long userId){
|
||||
return new ActiveUser(userId, new ArrayList<ReservedBook>());
|
||||
return new ActiveUser(userId, new ArrayList<ReservedBook>(), new ArrayList<BorrowedBook>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import io.wkrzywiec.hexagonal.library.BookTestData;
|
||||
import io.wkrzywiec.hexagonal.library.UserTestData;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ActiveUser;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.AvailableBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.BorrowedBook;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.DueDate;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.OverdueReservation;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservationDetails;
|
||||
import io.wkrzywiec.hexagonal.library.domain.borrowing.core.model.ReservedBook;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -109,6 +111,50 @@ public class BorrowingDatabaseAdapterITCase {
|
||||
assertTrue(reservationDetails.getReservationId().getIdAsLong() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
@DisplayName("Get reserved book by its id")
|
||||
@Sql({"/book-and-user.sql"})
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
public void shouldFindReservedBook(){
|
||||
//given
|
||||
Long bookId = getHomoDeusBookId();
|
||||
Long johnDoeUserId = getJohnDoeUserId();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO public.reserved (book_id, user_id) VALUES (?, ?)",
|
||||
bookId,
|
||||
johnDoeUserId);
|
||||
|
||||
//when
|
||||
Optional<ReservedBook> reservedBook = database.getReservedBook(bookId);
|
||||
|
||||
//then
|
||||
assertTrue(reservedBook.isPresent());
|
||||
assertEquals(bookId, reservedBook.get().getIdAsLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Save borrowed book")
|
||||
@Sql({"/book-and-user.sql"})
|
||||
@Sql(scripts = "/clean-database.sql", executionPhase = AFTER_TEST_METHOD)
|
||||
public void shouldSaveBorrowedBook(){
|
||||
//given
|
||||
Long bookId = getHomoDeusBookId();
|
||||
Long activeUserId = getJohnDoeUserId();
|
||||
|
||||
BorrowedBook borrowedBook = new BorrowedBook(bookId, activeUserId);
|
||||
|
||||
//when
|
||||
database.save(borrowedBook);
|
||||
|
||||
//then
|
||||
Long savedBookId = jdbcTemplate.queryForObject(
|
||||
"SELECT book_id FROM borrowed WHERE book_id = ?",
|
||||
Long.class,
|
||||
bookId);
|
||||
assertEquals(bookId, savedBookId);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Find book after 3 days of reservation")
|
||||
@Sql({"/book-and-user.sql"})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
DELETE FROM public.borrowed;
|
||||
DELETE FROM public.reserved;
|
||||
DELETE FROM public.available;
|
||||
DELETE FROM public.book_author;
|
||||
|
||||
Reference in New Issue
Block a user