Skip to content

Commit

Permalink
feat : 예매대기 등록 API application layer 구현 (#32)
Browse files Browse the repository at this point in the history
* feat: ValidationException 정의

* fix: User 엔티티 기본 생성자 public 으로 전환

* feat: WaitingBookingErrorCode 정의

* feat: WaitingBooking 생성로직 구현

* feat: 예매대기 등록 API 요청, 응답 DTO 구현

* feat: 예매대기 엔티티 생성 서비스로직 구현

* feat: 예매대기 등록 퍼사드로직 구현
  • Loading branch information
EunChanNam committed Jan 15, 2024
1 parent ea02a5f commit 20fb2e4
Show file tree
Hide file tree
Showing 13 changed files with 390 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,10 @@ public ErrorResponseTemplate handleMethodArgumentNotValidException(
return new ErrorResponseTemplate(message, ARGUMENT_NOT_VALID_ERROR_CODE);
}

@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(NotFoundException.class)
public ErrorResponseTemplate handleNotFoundException(
NotFoundException e
) {
log.error("NotFoundException : ", e);
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ValidationException.class)
public ErrorResponseTemplate handleValidationException(ValidationException e) {
log.error("ValidationException : ", e);
return new ErrorResponseTemplate(e.getMessage(), e.getCode());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dev.hooon.common.exception;

import lombok.Getter;

@Getter
public class ValidationException extends RuntimeException {

private final String code;

public ValidationException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
}
3 changes: 1 addition & 2 deletions core/src/main/java/dev/hooon/user/domain/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Table(name = "user_table")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
public class User extends TimeBaseEntity {

@Id
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.hooon.waitingbooking.application;

import org.springframework.stereotype.Service;

import dev.hooon.user.domain.entity.User;
import dev.hooon.waitingbooking.domain.entity.WaitingBooking;
import dev.hooon.waitingbooking.domain.repository.WaitingBookingRepository;
import dev.hooon.waitingbooking.dto.request.WaitingRegisterRequest;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class WaitingBookingService {

private final WaitingBookingRepository waitingBookingRepository;

public WaitingBooking createWaitingBooking(User user, WaitingRegisterRequest request) {
WaitingBooking waitingBooking = WaitingBooking.of(user, request.seatCount(), request.seatIds());
waitingBookingRepository.save(waitingBooking);

return waitingBooking;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.hooon.waitingbooking.application.facade;

import org.springframework.stereotype.Component;

import dev.hooon.user.application.UserService;
import dev.hooon.user.domain.entity.User;
import dev.hooon.waitingbooking.application.WaitingBookingService;
import dev.hooon.waitingbooking.domain.entity.WaitingBooking;
import dev.hooon.waitingbooking.dto.request.WaitingRegisterRequest;
import dev.hooon.waitingbooking.dto.response.WaitingRegisterResponse;
import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class WaitingBookingFacade {

private final WaitingBookingService waitingBookingService;
private final UserService userService;

public WaitingRegisterResponse registerWaitingBooking(Long userId, WaitingRegisterRequest request) {
User user = userService.getUserById(userId);
WaitingBooking waitingBooking = waitingBookingService.createWaitingBooking(user, request);

return new WaitingRegisterResponse(waitingBooking.getId());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package dev.hooon.waitingbooking.domain.entity;

import static dev.hooon.common.exception.CommonValidationError.*;
import static dev.hooon.waitingbooking.exception.WaitingBookingErrorCode.*;
import static jakarta.persistence.CascadeType.*;
import static jakarta.persistence.ConstraintMode.*;
import static jakarta.persistence.FetchType.*;
Expand All @@ -8,7 +10,10 @@
import java.util.ArrayList;
import java.util.List;

import org.springframework.util.Assert;

import dev.hooon.common.entity.TimeBaseEntity;
import dev.hooon.common.exception.ValidationException;
import dev.hooon.user.domain.entity.User;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand All @@ -28,6 +33,8 @@
@NoArgsConstructor
public class WaitingBooking extends TimeBaseEntity {

private static final String WAITING_BOOKING = "waitingBooking";

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "waiting_booking_id")
Expand All @@ -43,6 +50,62 @@ public class WaitingBooking extends TimeBaseEntity {
foreignKey = @ForeignKey(value = NO_CONSTRAINT))
private User user;

private int seatCount;

@OneToMany(mappedBy = "waitingBooking", cascade = {REMOVE, PERSIST})
List<WaitingBookingSeat> waitingBookingSeats = new ArrayList<>();

// 생성 메소드
private WaitingBooking(
User user,
int seatCount,
List<Long> seatIds
) {
Assert.notNull(user, getNotNullMessage(WAITING_BOOKING, "user"));
validateSeatCountInRange(seatCount);
validateSelectedSeats(seatCount, seatIds);

this.status = WaitingStatus.WAITING;
this.seatCount = seatCount;
this.user = user;
applyWaitingBookingSeats(seatIds);
}

private void applyWaitingBookingSeats(List<Long> seatIds) {
seatIds.forEach(seatId -> {
WaitingBookingSeat waitingBookingSeat = WaitingBookingSeat.of(seatId, this);
this.waitingBookingSeats.add(waitingBookingSeat);
});
}

// 검증 메소드
private void validateSeatCountInRange(int seatCount) {
// 좌석 개수를 1~3 개 선택하지 않으면 예외
if (seatCount < 1 || seatCount > 3) {
throw new ValidationException(INVALID_SEAT_COUNT);
}
}

private void validateSelectedSeats(int seatCount, List<Long> seatIds) {
// 좌석을 하나도 고르지 않으면 예외
if (seatIds.isEmpty()) {
throw new ValidationException(EMPTY_SELECTED_SEAT);
}

// seatCount 보다 적게 선택하거나 10배수 넘게 좌석을 선택하면 예외
int maxSeatCount = seatCount * 10;
int selectedSeatCount = seatIds.size();
if (selectedSeatCount > maxSeatCount || selectedSeatCount < seatCount) {
throw new ValidationException(INVALID_SELECTED_SEAT_COUNT);
}
}

// 팩토리 메소드
public static WaitingBooking of(
User user,
int seatCount,
List<Long> seatIds
) {
return new WaitingBooking(user, seatCount, seatIds);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package dev.hooon.waitingbooking.domain.entity;

import static dev.hooon.common.exception.CommonValidationError.*;
import static jakarta.persistence.ConstraintMode.*;
import static jakarta.persistence.FetchType.*;
import static jakarta.persistence.GenerationType.*;

import org.springframework.util.Assert;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.ForeignKey;
Expand All @@ -21,6 +24,8 @@
@NoArgsConstructor
public class WaitingBookingSeat {

private static final String WAITING_BOOKING_SEAT = "waitingBookingSeat";

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "waiting_booking_seat_id")
Expand All @@ -35,4 +40,17 @@ public class WaitingBookingSeat {
nullable = false,
foreignKey = @ForeignKey(value = NO_CONSTRAINT))
private WaitingBooking waitingBooking;

// 생성 메소드
private WaitingBookingSeat(Long seatId, WaitingBooking waitingBooking) {
Assert.notNull(seatId, getNotNullMessage(WAITING_BOOKING_SEAT, "seatId"));
Assert.notNull(waitingBooking, getNotNullMessage(WAITING_BOOKING_SEAT, "waitingBooking"));
this.seatId = seatId;
this.waitingBooking = waitingBooking;
}

// 팩토리 메소드
public static WaitingBookingSeat of(Long seatId, WaitingBooking waitingBooking) {
return new WaitingBookingSeat(seatId, waitingBooking);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.hooon.waitingbooking.dto.request;

import java.util.List;

public record WaitingRegisterRequest(
int seatCount,
List<Long> seatIds
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dev.hooon.waitingbooking.dto.response;

public record WaitingRegisterResponse(
Long waitingBookingId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.hooon.waitingbooking.exception;

import dev.hooon.common.exception.ErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum WaitingBookingErrorCode implements ErrorCode {

INVALID_SEAT_COUNT("좌석 개수는 1~3 개 내로 선택해야합니다", "W_001"),
EMPTY_SELECTED_SEAT("좌석은 반드시 1개 이상 선택해야합니다", "W_002"),
INVALID_SELECTED_SEAT_COUNT("좌석은 선택한 좌석 개수에서 10배수 까지 선택 가능합니다", "W_003");

private final String message;
private final String code;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package dev.hooon.waitingbooking.application;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

import java.util.List;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import dev.hooon.user.domain.entity.User;
import dev.hooon.waitingbooking.domain.entity.WaitingBooking;
import dev.hooon.waitingbooking.domain.entity.WaitingBookingSeat;
import dev.hooon.waitingbooking.domain.entity.WaitingStatus;
import dev.hooon.waitingbooking.domain.repository.WaitingBookingRepository;
import dev.hooon.waitingbooking.dto.request.WaitingRegisterRequest;

@DisplayName("[WaitingBookingService 테스트]")
@ExtendWith(MockitoExtension.class)
class WaitingBookingServiceTest {

@InjectMocks
private WaitingBookingService waitingBookingService;
@Mock
private WaitingBookingRepository waitingBookingRepository;

@Test
@DisplayName("[WaitingBooking 을 생성하고 응답한다]")
void createWaitingBookingTest() {
//given
int seatCount = 2;
List<Long> seatIds = List.of(1L, 2L, 3L);
WaitingRegisterRequest request = new WaitingRegisterRequest(seatCount, seatIds);
User user = new User();

//when
WaitingBooking result = waitingBookingService.createWaitingBooking(user, request);

//then
assertAll(
() -> assertThat(result.getSeatCount()).isEqualTo(seatCount),
() -> assertThat(result.getStatus()).isEqualTo(WaitingStatus.WAITING),
() -> assertThat(result.getUser()).isEqualTo(user),
() -> {
List<Long> actualSeatIds = result.getWaitingBookingSeats().stream()
.map(WaitingBookingSeat::getSeatId)
.toList();
assertThat(actualSeatIds)
.hasSameSizeAs(seatIds)
.containsAll(seatIds);
}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package dev.hooon.waitingbooking.application.facade;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;

import java.util.List;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import dev.hooon.user.application.UserService;
import dev.hooon.user.domain.entity.User;
import dev.hooon.waitingbooking.application.WaitingBookingService;
import dev.hooon.waitingbooking.domain.entity.WaitingBooking;
import dev.hooon.waitingbooking.dto.request.WaitingRegisterRequest;
import dev.hooon.waitingbooking.dto.response.WaitingRegisterResponse;

@DisplayName("[WaitingBookingFacade 테스트]")
@ExtendWith(MockitoExtension.class)
class WaitingBookingFacadeTest {

@InjectMocks
private WaitingBookingFacade waitingBookingFacade;
@Mock
private WaitingBookingService waitingBookingService;
@Mock
private UserService userService;

@Test
void registerWaitingBookingTest() {
//given
User user = new User();
WaitingRegisterRequest request = new WaitingRegisterRequest(3, List.of(1L, 2L, 3L));
WaitingBooking waitingBooking = WaitingBooking.of(user, request.seatCount(), request.seatIds());
// 테스트하는 로직에 waitingBooking 의 ID 가 필요하기 때문에 리플렉션으로 주입
ReflectionTestUtils.setField(waitingBooking, "id", 1L);

given(userService.getUserById(1L)).willReturn(user);
given(waitingBookingService.createWaitingBooking(user, request))
.willReturn(waitingBooking);

//when
WaitingRegisterResponse result = waitingBookingFacade.registerWaitingBooking(1L, request);

//then
assertThat(result.waitingBookingId()).isEqualTo(1L);
}
}
Loading

0 comments on commit 20fb2e4

Please sign in to comment.