본문 바로가기

개발 프로젝트/2024 제로베이스 프로젝트

제로베이스 연습프로젝트 : 매장 테이블 예약 서비스4 - MQ, Websocket 기능확장

도입 필요성 : 비즈니스 시나리오

  매장주인이 예약을 거절할 경우에 손님은 즉시, 이 내용에 대해서 알림을 받을 필요성이 있다. 이를 위해서 웹소켓 방식을 도입하여 서버에서 request가 없는 상황에서도 알림을 발송하게끔 하고자 한다.

또한, 매장 주인이 예약을 거절할 경우에 DB에 status 변경작업도 일어나야 하기 때문에, 웹소켓 발송 서비스와 DB 저장 서비스를 비동기적으로 실행시켜서 결합도를 느슨하게 하고자한다.

 

설계검토 : 동기화할 task와 비동기 task 식별

  • 동기화할 작업 : DB저장과 MQ Task Produce는 동기화 되어야된다. 왜냐하면, 예약상태 변경과 DB저장은 핵심적인 비즈니스 로직이기 때문에 이 작업이 미완료 되었는데 MQ task Produce는 생성되면 거짓 알람이 되는 것이기에 
  • 비동기화 할 작업 : notification로그 db저장, firebase알람, 웹소켓 알람
    • 반면 notification 로그 db저장은 핵심 로직이 아니기때문에 일시적으로 상태가 비동기적이어도 무관함.

 

설계초안

 

  • notificationProducer을 생성하여 해당 클래스의 .sendReservationRejectedMessage(reservationEntity); 호출
@Service
@RequiredArgsConstructor
public class ReservationService {
    private final ReservationRepository reservationRepository;
    private final NotificationProducer notificationProducer;

    @Transactional
    public ReservationDto rejectReservation(String partnerId, String reservationId) {
        log.info("Rejecting reservation {}", reservationId);

        ReservationEntity reservationEntity = reservationRepository.findByReservationId(reservationId)
            .orElseThrow(() -> new CustomException(ErrorCode.RESERVATION_ID_NONEXISTENT));

        if (!reservationEntity.getStoreEntity().getPartnerEntity().getPartnerId().equals(partnerId)) {
            throw new CustomException(ErrorCode.MEMBERID_STOREOWNER_UNMATCHED);
        }

        if (!reservationEntity.getReservationStatus().equals(ReservationStatus.REQUESTED)) {
            throw new CustomException(ErrorCode.RESERVATION_STATUS_ERROR);
        }

        log.info("Updating reservation status to REJECTED for reservation {}", reservationId);
        reservationEntity.setReservationStatus(ReservationStatus.REJECTED);
        reservationRepository.save(reservationEntity); // ✅ 동기 처리 (트랜잭션 보장)

        // ✅ 비동기 처리 (RabbitMQ로 이벤트 발행)
        notificationProducer.sendReservationRejectedMessage(reservationEntity);

        return ReservationDto.fromEntity(reservationEntity);
    }
}