Compare commits

..

6 Commits

Author SHA1 Message Date
Wojtek Krzywiec
e5e4767a9c applicationa layer for borrowing books 2020-06-04 08:15:48 +02:00
Wojtek Krzywiec
8dc64f2c79 adpater for saving borrowed book 2020-06-04 06:59:03 +02:00
Wojtek Krzywiec
ba246cf0c9 borrow book facade 2020-06-02 07:52:52 +02:00
Wojtek Krzywiec
a557dea9a9 cleaning 2020-05-31 16:19:56 +02:00
Wojtek Krzywiec
b2f9c7f56f Merge pull request #15 from wkrzywiec/cancel-reservation
cancel reservation scheduler added
2020-05-31 16:03:51 +02:00
Wojtek Krzywiec
6b0aeb4141 Merge pull request #14 from wkrzywiec/cancel-reservation
Cancel reservation
2020-05-30 21:50:09 +02:00
23 changed files with 405 additions and 34 deletions

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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()

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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("")

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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<>();
}
}

View File

@@ -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);
}
}

View File

@@ -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>());
}
}

View File

@@ -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());
}
}

View File

@@ -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));
}
}

View File

@@ -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>());
}
}

View File

@@ -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"})

View File

@@ -1,3 +1,4 @@
DELETE FROM public.borrowed;
DELETE FROM public.reserved;
DELETE FROM public.available;
DELETE FROM public.book_author;