Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat : 예매대기 처리 스케줄러 구현 #43

Merged
merged 14 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions core/src/main/java/dev/hooon/show/application/SeatService.java
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최소 단위 기능으로 메서드로 분리한거 굿굿

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package dev.hooon.show.application;

import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import dev.hooon.show.domain.entity.seat.Seat;
import dev.hooon.show.domain.entity.seat.SeatStatus;
import dev.hooon.show.domain.repository.SeatRepository;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class SeatService {

private final SeatRepository seatRepository;

// 취소 상태인 좌석의 ID 를 모두 조회
public Set<Long> getCanceledSeatIds() {
return seatRepository.findByStatusIsCanceled()
.stream()
.map(Seat::getId)
.collect(Collectors.toSet());
}

// 좌석을 대기중 상태로 변경
@Transactional
public void updateSeatToWaiting(Collection<Long> targetIds) {
seatRepository.updateStatusByIdIn(targetIds, SeatStatus.WAITING);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 뒤에 In 은 무슨의미인가요?!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 절을 통해 가져온다는 의미에요!

}

// 좌석을 대기중 예약가능 상태로 변경
@Transactional
public void updateSeatToAvailable(Collection<Long> targetIds) {
seatRepository.updateStatusByIdIn(targetIds, SeatStatus.AVAILABLE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ public enum SeatStatus {

AVAILABLE("예매 가능"),
BOOKED("예매 완료"),
CANCELED("예매 취소");
CANCELED("예매 취소"),
// 취소된 좌석이 예매대기로 인해 6시간동안 예매대기를 한 사용자에 한해 예약이 가능한 상태
WAITING("예매 대기");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻👍🏻


private final String description;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package dev.hooon.show.domain.repository;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

import dev.hooon.show.domain.entity.seat.Seat;
import dev.hooon.show.domain.entity.seat.SeatStatus;
import dev.hooon.show.dto.query.SeatDateRoundDto;

public interface SeatRepository {

void saveAll(Iterable<Seat> seats);

Optional<Seat> findById(Long id);

List<SeatDateRoundDto> findSeatDateRoundInfoByShowId(Long showId);

List<Seat> findByStatusIsCanceled();

void updateStatusByIdIn(Collection<Long> ids, SeatStatus status);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package dev.hooon.show.infrastructure.adaptor;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

import org.springframework.stereotype.Repository;

import dev.hooon.show.domain.entity.seat.Seat;
import dev.hooon.show.domain.entity.seat.SeatStatus;
import dev.hooon.show.domain.repository.SeatRepository;
import dev.hooon.show.dto.query.SeatDateRoundDto;
import dev.hooon.show.infrastructure.repository.SeatJpaRepository;
Expand All @@ -21,8 +24,23 @@ public void saveAll(Iterable<Seat> seats) {
seatJpaRepository.saveAll(seats);
}

@Override
public Optional<Seat> findById(Long id) {
return seatJpaRepository.findById(id);
}

@Override
public List<SeatDateRoundDto> findSeatDateRoundInfoByShowId(Long showId) {
return seatJpaRepository.findSeatDateRoundInfoByShowId(showId);
}

@Override
public List<Seat> findByStatusIsCanceled() {
return seatJpaRepository.findBySeatStatus(SeatStatus.CANCELED);
}

@Override
public void updateStatusByIdIn(Collection<Long> ids, SeatStatus status) {
seatJpaRepository.updateStatusByIdIn(ids, status);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package dev.hooon.show.infrastructure.repository;

import java.util.Collection;
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import dev.hooon.show.domain.entity.seat.Seat;
import dev.hooon.show.domain.entity.seat.SeatStatus;
import dev.hooon.show.dto.query.SeatDateRoundDto;

public interface SeatJpaRepository extends JpaRepository<Seat, Long> {
Expand All @@ -18,4 +22,10 @@ public interface SeatJpaRepository extends JpaRepository<Seat, Long> {
order by s.showDate, s.showRound.round
""")
List<SeatDateRoundDto> findSeatDateRoundInfoByShowId(Long showId);

List<Seat> findBySeatStatus(SeatStatus status);

@Modifying
@Query("update Seat s SET s.seatStatus = :status where s.id in :ids")
void updateStatusByIdIn(@Param("ids") Collection<Long> ids, @Param("status") SeatStatus status);
}
29 changes: 18 additions & 11 deletions core/src/main/java/dev/hooon/user/domain/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,25 @@
@NoArgsConstructor
public class User extends TimeBaseEntity {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "user_id")
private Long id;
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "user_id")
private Long id;

@Column(name = "user_email", nullable = false, unique = true)
private String email;
@Column(name = "user_email", nullable = false, unique = true)
private String email;

@Column(name = "user_name", nullable = false)
private String name;
@Column(name = "user_name", nullable = false)
private String name;

@Enumerated(STRING)
@Column(name = "user_role", nullable = false)
private UserRole userRole;
@Enumerated(STRING)
@Column(name = "user_role", nullable = false)
private UserRole userRole;

// 테스트용 AllArgsConstructor
public User(String email, String name, UserRole userRole) {
this.email = email;
this.name = name;
this.userRole = userRole;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@

public interface UserRepository {

User save(User user);

Optional<User> findById(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public class UserRepositoryAdaptor implements UserRepository {

private final UserJpaRepository userJpaRepository;

@Override
public User save(User user) {
return userJpaRepository.save(user);
}

@Override
public Optional<User> findById(Long id) {
return userJpaRepository.findById(id);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package dev.hooon.waitingbooking.application;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import dev.hooon.user.domain.entity.User;
import dev.hooon.waitingbooking.domain.entity.WaitingBooking;
Expand All @@ -20,4 +23,15 @@ public WaitingBooking createWaitingBooking(User user, WaitingRegisterRequest req

return waitingBooking;
}

// 대기 상태인 WaitingBooking 조회
public List<WaitingBooking> getWaitingBookingsByStatusIsWaiting() {
return waitingBookingRepository.findByStatusIsWaiting();
}

// ID 에 해당하는 WaitingBooking ACTIVATION 상태로 변경하고 expireAt 6시간뒤로 설정
@Transactional
public void activateWaitingBooking(Long waitingBookingId) {
waitingBookingRepository.updateToActiveById(waitingBookingId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것도 updateStatusToActivateById 는 어떠신가요? ㅎㅎ (이거도 단순히 제안 ㅎㅎㅎ)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의미상으로 "예약대기를 활성화한다" 라는 의미를 담은 서비스로직이라고 생각해서 제 생각에는 나은거같다고 생각이 듭니다! 수진님이 강력히 원하시는건 아닌거같으니...ㅎㅎ 이대로 가볼게요 ㅎㅎ

}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package dev.hooon.waitingbooking.application.facade;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.springframework.stereotype.Component;

import dev.hooon.show.application.SeatService;
import dev.hooon.user.application.UserService;
import dev.hooon.user.domain.entity.User;
import dev.hooon.waitingbooking.application.WaitingBookingService;
Expand All @@ -16,11 +21,63 @@ public class WaitingBookingFacade {

private final WaitingBookingService waitingBookingService;
private final UserService userService;
private final SeatService seatService;

// 선택한 좌석중에서 취소좌석에 포함되는 좌석 ID 를 LIST 로 응답
private List<Long> fetchMatchingSeatIds(
Set<Long> canceledSeatIds,
List<Long> selectedSeatIds,
int seatCount
) {
List<Long> matchSeatIds = new ArrayList<>();
for (Long selectedSeatId : selectedSeatIds) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분 stream으로 처리는 어려운건가용??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그 바로 아랫줄에 사이즈가 seatCount 랑 같아지면 break 치는 부분이 있는데 stream forEach 에선 break 를 지원안해서 이렇게 구현했습니다~!

if (canceledSeatIds.contains(selectedSeatId)) {
matchSeatIds.add(selectedSeatId);
}
if (matchSeatIds.size() == seatCount) {
break;
}
}
return matchSeatIds;
}

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

return new WaitingRegisterResponse(waitingBooking.getId());
}

public void processWaitingBooking() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석으로 설명을 잘 해놓아서 어떤 흐름인지 이해가 잘 되네요 👍🏻👍🏻

// 1. 취소된 좌석을 모두 조회한다 (PK Set 으로)
Set<Long> canceledSeatIds = seatService.getCanceledSeatIds();
// 2. 대기중 상태인 예약대기 목록을 날짜순으로 조회한다
List<WaitingBooking> waitingList = waitingBookingService.getWaitingBookingsByStatusIsWaiting();
// 3. waitingList 반복하면서 아래 작업을 수행
/*
* (1) 대기목록에 포함된 좌석의 PK 가 취소된 좌석 Set 에 존재하는지 확인
* (2) 대기목록의 좌석중에서 취소목록에 포함되는 좌석 아이디 가져옴
* (3) 가져온 좌석 아이디 사이즈가 선택좌석개수와 같으면 취소좌석 SET 에서 해당 PK 지우고 좌석 상태를 취소에서 대기로 바꾸고 예약대기를 활성화
* + 사용자에게 메일 발송 (구현 예정)
*/
waitingList.forEach(waitingBooking -> {
// (1)
List<Long> selectedSeatIds = waitingBooking.getSelectedSeatIds();
// (2)
List<Long> matchSeatIds = fetchMatchingSeatIds(
canceledSeatIds,
selectedSeatIds,
waitingBooking.getSeatCount()
);
// (3)
if (matchSeatIds.size() == waitingBooking.getSeatCount()) {
matchSeatIds.forEach(canceledSeatIds::remove);
seatService.updateSeatToWaiting(matchSeatIds);
waitingBookingService.activateWaitingBooking(waitingBooking.getId());
// 메일 알림 이벤트 발행
}
});
// 4. 반복이 끝났는데 남아있는 취소 좌석들은 예약가능 상태로 변경
seatService.updateSeatToAvailable(canceledSeatIds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static jakarta.persistence.FetchType.*;
import static jakarta.persistence.GenerationType.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -52,6 +53,8 @@ public class WaitingBooking extends TimeBaseEntity {

private int seatCount;

private LocalDateTime expiredAt;

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

Expand Down Expand Up @@ -108,4 +111,10 @@ public static WaitingBooking of(
) {
return new WaitingBooking(user, seatCount, seatIds);
}

public List<Long> getSelectedSeatIds() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예약 대기 좌석에 해당하는 좌석 id값을 list로 반환하는 함수가 맞을까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞습니다~ 👍

return waitingBookingSeats.stream()
.map(WaitingBookingSeat::getSeatId)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package dev.hooon.waitingbooking.domain.repository;

import java.util.List;
import java.util.Optional;

import dev.hooon.waitingbooking.domain.entity.WaitingBooking;

public interface WaitingBookingRepository {

void save(WaitingBooking waitingBooking);

Optional<WaitingBooking> findById(Long id);

List<WaitingBooking> findAll();

// WaitingStatus 가 WAITING 인 데이터를 최신순으로 조회하는 쿼리
List<WaitingBooking> findByStatusIsWaiting();

void updateToActiveById(Long id);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package dev.hooon.waitingbooking.infrastructure.adaptor;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import org.springframework.stereotype.Repository;

import dev.hooon.waitingbooking.domain.entity.WaitingBooking;
import dev.hooon.waitingbooking.domain.entity.WaitingStatus;
import dev.hooon.waitingbooking.domain.repository.WaitingBookingRepository;
import dev.hooon.waitingbooking.infrastructure.repository.WaitingBookingJpaRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -20,9 +23,27 @@ public void save(WaitingBooking waitingBooking) {
waitingBookingJpaRepository.save(waitingBooking);
}

@Override
public Optional<WaitingBooking> findById(Long id) {
return waitingBookingJpaRepository.findById(id);
}

@Override
public List<WaitingBooking> findAll() {
return waitingBookingJpaRepository.findAll();
}

@Override
public List<WaitingBooking> findByStatusIsWaiting() {
return waitingBookingJpaRepository.findByStatusOrderByIdDesc(WaitingStatus.WAITING);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네이밍 굿굿 한번에 바로 파악했네요

}

@Override
public void updateToActiveById(Long id) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateStatusToActiveById 는 어떨까요?! (이것도 사소해~)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 쿼리가 어댑터쪽 코드보면 상태변경도있는데 expireAt 도 6시간 뒤로 설정해주는 쿼리라서 이렇게 네이밍했어요!

waitingBookingJpaRepository.updateStatusAndExpireAt(
id,
WaitingStatus.ACTIVATION,
LocalDateTime.now().plusHours(6)
);
}
}
Loading