diff --git a/common/messaging/src/main/java/messaging/events/OrderCompletedEvent.java b/common/messaging/src/main/java/messaging/events/OrderCompletedEvent.java deleted file mode 100644 index 0522dcba..00000000 --- a/common/messaging/src/main/java/messaging/events/OrderCompletedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package messaging.events; - -import java.util.UUID; - -public record OrderCompletedEvent( - String userId, - String performanceId -) { - public static OrderCompletedEvent create(UUID userId, UUID performanceId) { - return new OrderCompletedEvent( - userId.toString(), - performanceId.toString() - ); - } -} diff --git a/common/messaging/src/main/java/messaging/events/OrderCompletedForQueueTokenRemovalEvent.java b/common/messaging/src/main/java/messaging/events/OrderCompletedForQueueTokenRemovalEvent.java new file mode 100644 index 00000000..bdca67ee --- /dev/null +++ b/common/messaging/src/main/java/messaging/events/OrderCompletedForQueueTokenRemovalEvent.java @@ -0,0 +1,15 @@ +package messaging.events; + +import java.util.UUID; + +public record OrderCompletedForQueueTokenRemovalEvent( + String userId, + String performanceId +) { + public static OrderCompletedForQueueTokenRemovalEvent create(UUID userId, UUID performanceId) { + return new OrderCompletedForQueueTokenRemovalEvent( + userId.toString(), + performanceId.toString() + ); + } +} diff --git a/common/messaging/src/main/java/messaging/events/OrderCompletedForSeatReservationEvent.java b/common/messaging/src/main/java/messaging/events/OrderCompletedForSeatReservationEvent.java new file mode 100644 index 00000000..bf5a5c14 --- /dev/null +++ b/common/messaging/src/main/java/messaging/events/OrderCompletedForSeatReservationEvent.java @@ -0,0 +1,15 @@ +package messaging.events; + +import java.util.UUID; + +public record OrderCompletedForSeatReservationEvent( + String scheduleId, + String seatId +) { + public static OrderCompletedForSeatReservationEvent create(UUID scheduleId, UUID seatId) { + return new OrderCompletedForSeatReservationEvent( + scheduleId.toString(), + seatId.toString() + ); + } +} diff --git a/common/messaging/src/main/java/messaging/topics/OrderTopic.java b/common/messaging/src/main/java/messaging/topics/OrderTopic.java index df2c9106..95bf6522 100644 --- a/common/messaging/src/main/java/messaging/topics/OrderTopic.java +++ b/common/messaging/src/main/java/messaging/topics/OrderTopic.java @@ -7,7 +7,8 @@ @RequiredArgsConstructor public enum OrderTopic { - COMPLETED("order-completed"); + COMPLETED_FOR_SEAT_RESERVATION("order-completed-for-seat-reservation"), + COMPLETED_FOR_QUEUE_TOKEN_REMOVAL("order-completed-for-queue-token-removal"); private final String topic; diff --git a/services/order/src/main/java/com/ticketPing/order/application/client/PerformanceClient.java b/services/order/src/main/java/com/ticketPing/order/application/client/PerformanceClient.java index 8a0c1888..9a2d9f24 100644 --- a/services/order/src/main/java/com/ticketPing/order/application/client/PerformanceClient.java +++ b/services/order/src/main/java/com/ticketPing/order/application/client/PerformanceClient.java @@ -1,10 +1,7 @@ package com.ticketPing.order.application.client; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; import performance.OrderSeatResponse; -import performance.SeatResponse; import response.CommonResponse; import java.util.UUID; @@ -13,6 +10,4 @@ public interface PerformanceClient { ResponseEntity> getOrderInfo(UUID userId, UUID scheduleId, UUID seatId); ResponseEntity> extendPreReserveTTL(UUID scheduleId, UUID seatId); - - ResponseEntity> updateSeatState(UUID seatId, Boolean seatState); } diff --git a/services/order/src/main/java/com/ticketPing/order/application/service/EventApplicationService.java b/services/order/src/main/java/com/ticketPing/order/application/service/EventApplicationService.java index 988debce..3b883b4d 100644 --- a/services/order/src/main/java/com/ticketPing/order/application/service/EventApplicationService.java +++ b/services/order/src/main/java/com/ticketPing/order/application/service/EventApplicationService.java @@ -1,7 +1,8 @@ package com.ticketPing.order.application.service; +import messaging.events.OrderCompletedForQueueTokenRemovalEvent; +import messaging.events.OrderCompletedForSeatReservationEvent; import messaging.utils.EventSerializer; -import messaging.events.OrderCompletedEvent; import lombok.RequiredArgsConstructor; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; @@ -13,9 +14,14 @@ public class EventApplicationService { private final KafkaTemplate kafkaTemplate; - public void publishOrderCompletedEvent(OrderCompletedEvent event) { + public void publishForSeatReservation(OrderCompletedForSeatReservationEvent event) { String message = EventSerializer.serialize(event); - kafkaTemplate.send(OrderTopic.COMPLETED.getTopic(), message); + kafkaTemplate.send(OrderTopic.COMPLETED_FOR_SEAT_RESERVATION.getTopic(), message); + } + + public void publishForQueueTokenRemoval(OrderCompletedForQueueTokenRemovalEvent event) { + String message = EventSerializer.serialize(event); + kafkaTemplate.send(OrderTopic.COMPLETED_FOR_QUEUE_TOKEN_REMOVAL.getTopic(), message); } } diff --git a/services/order/src/main/java/com/ticketPing/order/application/service/OrderService.java b/services/order/src/main/java/com/ticketPing/order/application/service/OrderService.java index 33bf3cb4..df6e9492 100644 --- a/services/order/src/main/java/com/ticketPing/order/application/service/OrderService.java +++ b/services/order/src/main/java/com/ticketPing/order/application/service/OrderService.java @@ -6,22 +6,22 @@ import com.ticketPing.order.domain.model.entity.Order; import com.ticketPing.order.domain.model.entity.OrderSeat; import com.ticketPing.order.domain.model.enums.OrderStatus; -import com.ticketPing.order.infrastructure.repository.OrderRepository; -import messaging.events.OrderCompletedEvent; +import com.ticketPing.order.domain.repository.OrderRepository; import exception.ApplicationException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import lombok.val; +import messaging.events.OrderCompletedForQueueTokenRemovalEvent; +import messaging.events.OrderCompletedForSeatReservationEvent; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import performance.OrderSeatResponse; import java.util.List; import java.util.UUID; -import caching.repository.RedisRepository; -import performance.OrderSeatResponse; -import static caching.enums.RedisKeyPrefix.AVAILABLE_SEATS; -import static com.ticketPing.order.common.exception.OrderExceptionCase.*; +import static com.ticketPing.order.common.exception.OrderExceptionCase.DUPLICATED_ORDER; +import static com.ticketPing.order.common.exception.OrderExceptionCase.ORDER_NOT_FOUND; @Service @RequiredArgsConstructor @@ -30,11 +30,8 @@ public class OrderService { private final OrderRepository orderRepository; private final EventApplicationService eventApplicationService; - private final RedisRepository redisRepository; private final PerformanceClient performanceClient; - private final static String TTL_PREFIX = "{Seat}:seat_ttl:"; - @Transactional public OrderResponse createOrder(UUID scheduleId, UUID seatId, UUID userId) { validateDuplicateOrder(seatId); @@ -48,30 +45,18 @@ public List getUserOrders(UUID userId) { return orders.stream().map(OrderResponse::from).toList(); } - public OrderResponse validateOrderAndExtendTTL(UUID orderId, UUID userId) { - Order order = validateOrder(orderId, userId); + public void validateOrderAndExtendTTL(UUID orderId, UUID userId) { + Order order = validateAndGetOrder(orderId, userId); performanceClient.extendPreReserveTTL(order.getScheduleId(), order.getOrderSeat().getSeatId()); - return OrderResponse.from(order); } @Transactional - public void updateOrderStatus(UUID orderId, UUID paymentId) { + public void completeOrder(UUID orderId, UUID paymentId) { Order order = findOrderById(orderId); - order.complete(); - - UUID performanceId = order.getPerformanceId(); - UUID scheduleId = order.getScheduleId(); - UUID seatId = order.getOrderSeat().getSeatId(); - - performanceClient.updateSeatState(order.getOrderSeat().getSeatId(), true); + order.complete(paymentId); - String ttlKey = TTL_PREFIX + scheduleId + ":" + seatId + ":" + orderId; - redisRepository.deleteKey(ttlKey); - - String counterKey = AVAILABLE_SEATS.getValue() + performanceId; - redisRepository.decrement(counterKey); - - publishOrderCompletedEvent(order.getUserId(), performanceId); + publishForSeatReservation(order.getScheduleId(), order.getOrderSeat().getSeatId()); + publishForQueueTokenRemoval(order.getUserId(), order.getPerformanceId()); } @Transactional @@ -85,31 +70,39 @@ private Order saveOrderWithOrderSeat(UUID userId, OrderSeatResponse orderData) { return savedOrder; } - private Order findOrderById(UUID orderId){ - return orderRepository.findById(orderId) - .orElseThrow(() -> new ApplicationException(ORDER_NOT_FOUND)); - } - private void validateDuplicateOrder(UUID seatId) { - List duplicateOrders = orderRepository.findByOrderSeatSeatId(seatId) - .stream() - .filter(o -> o.getOrderStatus().equals(OrderStatus.PENDING) || o.getOrderStatus().equals(OrderStatus.COMPLETED)) - .toList(); + boolean hasDuplicate = orderRepository.existsByOrderSeatSeatIdAndOrderStatusIn( + seatId, List.of(OrderStatus.PENDING, OrderStatus.COMPLETED)); - if(!duplicateOrders.isEmpty()) - throw new ApplicationException(SEAT_ALREADY_TAKEN); + if (hasDuplicate) { + throw new ApplicationException(DUPLICATED_ORDER); + } } - private Order validateOrder(UUID orderId, UUID userId) { - Order order = findOrderById(orderId); - if(!order.getOrderStatus().equals(OrderStatus.PENDING) || !order.getUserId().equals(userId)) + private Order validateAndGetOrder(UUID orderId, UUID userId) { + Order order = orderRepository.findByIdAndOrderStatus(orderId, OrderStatus.PENDING) + .orElseThrow(() -> new ApplicationException(OrderExceptionCase.INVALID_ORDER)); + + if (!order.getUserId().equals(userId)) { throw new ApplicationException(OrderExceptionCase.INVALID_ORDER); + } + return order; } - private void publishOrderCompletedEvent(UUID userId, UUID performanceId) { - val event = OrderCompletedEvent.create(userId, performanceId); - eventApplicationService.publishOrderCompletedEvent(event); + private Order findOrderById(UUID orderId){ + return orderRepository.findById(orderId) + .orElseThrow(() -> new ApplicationException(ORDER_NOT_FOUND)); + } + + private void publishForSeatReservation(UUID scheduleId, UUID seatId) { + val event = OrderCompletedForSeatReservationEvent.create(scheduleId, seatId); + eventApplicationService.publishForSeatReservation(event); + } + + private void publishForQueueTokenRemoval(UUID userId, UUID performanceId) { + val event = OrderCompletedForQueueTokenRemovalEvent.create(userId, performanceId); + eventApplicationService.publishForQueueTokenRemoval(event); } } diff --git a/services/order/src/main/java/com/ticketPing/order/common/exception/OrderExceptionCase.java b/services/order/src/main/java/com/ticketPing/order/common/exception/OrderExceptionCase.java index f2b4c138..741541e2 100644 --- a/services/order/src/main/java/com/ticketPing/order/common/exception/OrderExceptionCase.java +++ b/services/order/src/main/java/com/ticketPing/order/common/exception/OrderExceptionCase.java @@ -12,7 +12,7 @@ public enum OrderExceptionCase implements ErrorCase { ORDER_NOT_FOUND(HttpStatus.NOT_FOUND, "예매정보를 찾을 수 없습니다."), INVALID_TTL_NAME(HttpStatus.BAD_REQUEST, "유효하지 않은 TTL 명입니다."), NOT_FOUND_ORDER_ID_IN_TTL(HttpStatus.NOT_FOUND,"TTL 정보에서 order_id를 찾을 수 없습니다."), - SEAT_ALREADY_TAKEN(HttpStatus.BAD_REQUEST, "중복된 주문입니다."), + DUPLICATED_ORDER(HttpStatus.BAD_REQUEST, "중복된 주문입니다."), INVALID_ORDER(HttpStatus.BAD_REQUEST, "유효하지 않은 주문입니다."), SEAT_CACHE_NOT_FOUND(HttpStatus.NOT_FOUND, "레디스에 공연관련 정보가 캐싱되어 있지 않습니다."), ORDER_STATUS_UNKNOWN(HttpStatus.CONFLICT,"저장된 enum 상태값을 사용해야 합니다."), diff --git a/services/order/src/main/java/com/ticketPing/order/domain/model/entity/Order.java b/services/order/src/main/java/com/ticketPing/order/domain/model/entity/Order.java index 4b8800fe..cb3292ca 100644 --- a/services/order/src/main/java/com/ticketPing/order/domain/model/entity/Order.java +++ b/services/order/src/main/java/com/ticketPing/order/domain/model/entity/Order.java @@ -58,8 +58,9 @@ public void updateOrderSeat(OrderSeat orderSeat) { this.orderSeat = orderSeat; } - public void complete(){ + public void complete(UUID paymentId){ this.orderStatus = OrderStatus.COMPLETED; + this.paymentId = paymentId; } } diff --git a/services/order/src/main/java/com/ticketPing/order/domain/repository/OrderRepository.java b/services/order/src/main/java/com/ticketPing/order/domain/repository/OrderRepository.java new file mode 100644 index 00000000..6b8c39c4 --- /dev/null +++ b/services/order/src/main/java/com/ticketPing/order/domain/repository/OrderRepository.java @@ -0,0 +1,20 @@ +package com.ticketPing.order.domain.repository; + +import com.ticketPing.order.domain.model.entity.Order; +import com.ticketPing.order.domain.model.enums.OrderStatus; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface OrderRepository { + Order save(Order order); + + Optional findById(UUID orderId); + + Optional findByIdAndOrderStatus(UUID orderId, OrderStatus orderStatus); + + List findByUserId(UUID userId); + + boolean existsByOrderSeatSeatIdAndOrderStatusIn(UUID seatId, List pending); +} diff --git a/services/order/src/main/java/com/ticketPing/order/infrastructure/client/PerformanceFeignClient.java b/services/order/src/main/java/com/ticketPing/order/infrastructure/client/PerformanceFeignClient.java index 1cf34662..2223cd54 100644 --- a/services/order/src/main/java/com/ticketPing/order/infrastructure/client/PerformanceFeignClient.java +++ b/services/order/src/main/java/com/ticketPing/order/infrastructure/client/PerformanceFeignClient.java @@ -3,12 +3,12 @@ import com.ticketPing.order.application.client.PerformanceClient; import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import performance.OrderSeatResponse; -import performance.SeatResponse; import response.CommonResponse; + import java.util.UUID; -import org.springframework.http.ResponseEntity; @FeignClient(name = "performance") public interface PerformanceFeignClient extends PerformanceClient { @@ -22,8 +22,5 @@ ResponseEntity> getOrderInfo(@RequestHeader("X ResponseEntity> extendPreReserveTTL(@RequestParam("scheduleId") UUID scheduleId, @PathVariable("seatId") UUID seatId); - @PutMapping("/api/v1/seats/{seatId}") - ResponseEntity> updateSeatState(@PathVariable("seatId") UUID seatId, - @RequestParam("seatState") Boolean seatState); } diff --git a/services/order/src/main/java/com/ticketPing/order/infrastructure/config/KafkaTopicConfig.java b/services/order/src/main/java/com/ticketPing/order/infrastructure/config/KafkaTopicConfig.java index 2eaf7717..8e6e297d 100644 --- a/services/order/src/main/java/com/ticketPing/order/infrastructure/config/KafkaTopicConfig.java +++ b/services/order/src/main/java/com/ticketPing/order/infrastructure/config/KafkaTopicConfig.java @@ -10,8 +10,16 @@ public class KafkaTopicConfig { @Bean - public NewTopic orderCompletedTopic() { - return TopicBuilder.name(OrderTopic.COMPLETED.getTopic()) + public NewTopic seatReservationTopic() { + return TopicBuilder.name(OrderTopic.COMPLETED_FOR_SEAT_RESERVATION.getTopic()) + .partitions(3) + .replicas(3) + .build(); + } + + @Bean + public NewTopic queueTokenRemovalTopic() { + return TopicBuilder.name(OrderTopic.COMPLETED_FOR_QUEUE_TOKEN_REMOVAL.getTopic()) .partitions(3) .replicas(3) .build(); diff --git a/services/order/src/main/java/com/ticketPing/order/infrastructure/listener/EventConsumer.java b/services/order/src/main/java/com/ticketPing/order/infrastructure/listener/EventConsumer.java index 1e89c2fa..02a7b190 100644 --- a/services/order/src/main/java/com/ticketPing/order/infrastructure/listener/EventConsumer.java +++ b/services/order/src/main/java/com/ticketPing/order/infrastructure/listener/EventConsumer.java @@ -20,7 +20,7 @@ public class EventConsumer { public void handlePaymentCompletedEvent(ConsumerRecord record, Acknowledgment acknowledgment) { EventLogger.logReceivedMessage(record); PaymentCompletedEvent event = EventSerializer.deserialize(record.value(), PaymentCompletedEvent.class); - orderService.updateOrderStatus(event.orderId(), event.paymentId()); + orderService.completeOrder(event.orderId(), event.paymentId()); acknowledgment.acknowledge(); } diff --git a/services/order/src/main/java/com/ticketPing/order/infrastructure/repository/OrderRepository.java b/services/order/src/main/java/com/ticketPing/order/infrastructure/repository/OrderJpaRepository.java similarity index 51% rename from services/order/src/main/java/com/ticketPing/order/infrastructure/repository/OrderRepository.java rename to services/order/src/main/java/com/ticketPing/order/infrastructure/repository/OrderJpaRepository.java index e6475a87..9f4e8976 100644 --- a/services/order/src/main/java/com/ticketPing/order/infrastructure/repository/OrderRepository.java +++ b/services/order/src/main/java/com/ticketPing/order/infrastructure/repository/OrderJpaRepository.java @@ -1,11 +1,11 @@ package com.ticketPing.order.infrastructure.repository; import com.ticketPing.order.domain.model.entity.Order; -import java.util.List; -import java.util.UUID; +import com.ticketPing.order.domain.repository.OrderRepository; import org.springframework.data.jpa.repository.JpaRepository; -public interface OrderRepository extends JpaRepository { - List findByUserId(UUID userId); - List findByOrderSeatSeatId(UUID seatId); +import java.util.UUID; + +public interface OrderJpaRepository extends OrderRepository, JpaRepository { + } diff --git a/services/order/src/main/java/com/ticketPing/order/presentation/controller/OrderController.java b/services/order/src/main/java/com/ticketPing/order/presentation/controller/OrderController.java index e7b4b304..8ffb08bb 100644 --- a/services/order/src/main/java/com/ticketPing/order/presentation/controller/OrderController.java +++ b/services/order/src/main/java/com/ticketPing/order/presentation/controller/OrderController.java @@ -2,6 +2,8 @@ import com.ticketPing.order.application.dtos.OrderResponse; import com.ticketPing.order.application.service.OrderService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import response.CommonResponse; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; @@ -10,6 +12,8 @@ import java.util.List; import java.util.UUID; +import static response.CommonResponse.success; + @RestController @RequiredArgsConstructor @@ -20,27 +24,34 @@ public class OrderController { @Operation(summary = "예매 좌석 생성") @PostMapping - public CommonResponse createOrder(@RequestHeader("X_USER_ID") UUID userId, - @RequestParam("performanceId") UUID performanceId, - @RequestParam("scheduleId") UUID scheduleId, - @RequestParam("seatId") UUID seatId) { + public ResponseEntity> createOrder(@RequestHeader("X_USER_ID") UUID userId, + @RequestParam("performanceId") UUID performanceId, + @RequestParam("scheduleId") UUID scheduleId, + @RequestParam("seatId") UUID seatId) { OrderResponse orderResponse = orderService.createOrder(scheduleId, seatId, userId); - return CommonResponse.success(orderResponse); + return ResponseEntity + .status(201) + .body(success(orderResponse)); } @Operation(summary = "사용자 예매 목록 전체 조회") @GetMapping("/user/reservations") - public CommonResponse> getUserReservation(@RequestHeader("X_USER_ID") UUID userId) { + public ResponseEntity>> getUserReservation(@RequestHeader("X_USER_ID") UUID userId) { List userReservationDto = orderService.getUserOrders(userId); - return CommonResponse.success(userReservationDto); + return ResponseEntity + .status(200) + .body(success(userReservationDto)); } @Operation(summary = "주문 정보 검증") @PostMapping("/{orderId}/validate") - public CommonResponse validateOrder(@RequestHeader("X_USER_ID") UUID userId, + public ResponseEntity> validateOrder(@RequestHeader("X_USER_ID") UUID userId, + @RequestParam("performanceId") UUID performanceId, @PathVariable("orderId") UUID orderId) { - OrderResponse orderResponse = orderService.validateOrderAndExtendTTL(orderId, userId); - return CommonResponse.success(orderResponse); + orderService.validateOrderAndExtendTTL(orderId, userId); + return ResponseEntity + .status(200) + .body(success()); } } diff --git a/services/performance/build.gradle b/services/performance/build.gradle index de9c8e3d..5b02df92 100644 --- a/services/performance/build.gradle +++ b/services/performance/build.gradle @@ -41,6 +41,7 @@ dependencies { implementation(project(':common:caching')) { exclude(group: 'org.springframework.boot', module: 'spring-boot-starter-data-redis') } + implementation project(':common:messaging') implementation project(':common:monitoring') // MVC diff --git a/services/performance/src/main/java/com/ticketPing/performance/PerformanceApplication.java b/services/performance/src/main/java/com/ticketPing/performance/PerformanceApplication.java index dbd2da86..49c75a08 100644 --- a/services/performance/src/main/java/com/ticketPing/performance/PerformanceApplication.java +++ b/services/performance/src/main/java/com/ticketPing/performance/PerformanceApplication.java @@ -7,7 +7,7 @@ @EnableScheduling @SpringBootApplication -@ComponentScan(basePackages = {"com.ticketPing.performance", "aop", "exception", "audit"}) +@ComponentScan(basePackages = {"com.ticketPing.performance", "aop", "exception", "audit", "messaging"}) public class PerformanceApplication { public static void main(String[] args) { SpringApplication.run(PerformanceApplication.class, args); diff --git a/services/performance/src/main/java/com/ticketPing/performance/application/service/SeatService.java b/services/performance/src/main/java/com/ticketPing/performance/application/service/SeatService.java index 96164e8c..f24f0113 100644 --- a/services/performance/src/main/java/com/ticketPing/performance/application/service/SeatService.java +++ b/services/performance/src/main/java/com/ticketPing/performance/application/service/SeatService.java @@ -19,6 +19,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -61,6 +62,12 @@ public void extendPreReserveTTL(UUID scheduleId, UUID seatId) { cacheService.extendPreReserveTTL(scheduleId, seatId, Duration.ofSeconds(PRE_RESERVE_TTL)); } + public void reserveSeat(String scheduleId, String seatId) { + Seat seat = seatRepository.findById(UUID.fromString(seatId)) + .orElseThrow(() -> new ApplicationException(SeatExceptionCase.SEAT_NOT_FOUND)); + cacheService.reserveSeat(scheduleId, seatId); + } + public long cacheSeatsForSchedule(Schedule schedule) { List seats = seatRepository.findByScheduleWithSeatCost(schedule); diff --git a/services/performance/src/main/java/com/ticketPing/performance/domain/model/entity/SeatCache.java b/services/performance/src/main/java/com/ticketPing/performance/domain/model/entity/SeatCache.java index 41ba11e9..0c969612 100644 --- a/services/performance/src/main/java/com/ticketPing/performance/domain/model/entity/SeatCache.java +++ b/services/performance/src/main/java/com/ticketPing/performance/domain/model/entity/SeatCache.java @@ -32,7 +32,11 @@ public static SeatCache from(Seat seat) { } public void cancelPreReserveSeat() { - seatStatus = SeatStatus.AVAILABLE.getValue();; + seatStatus = SeatStatus.AVAILABLE.getValue(); userId = null; } + + public void reserveSeat() { + seatStatus = SeatStatus.RESERVED.getValue(); + } } diff --git a/services/performance/src/main/java/com/ticketPing/performance/domain/repository/SeatRepository.java b/services/performance/src/main/java/com/ticketPing/performance/domain/repository/SeatRepository.java index 89aa6218..87f8e0b0 100644 --- a/services/performance/src/main/java/com/ticketPing/performance/domain/repository/SeatRepository.java +++ b/services/performance/src/main/java/com/ticketPing/performance/domain/repository/SeatRepository.java @@ -10,9 +10,11 @@ public interface SeatRepository { Seat save(Seat seat); + Optional findById(UUID uuid); + + Optional findByIdWithAll(UUID seatId); + Optional findByIdWithSeatCost(UUID id); List findByScheduleWithSeatCost(Schedule schedule); - - Optional findByIdWithAll(UUID seatId); } diff --git a/services/performance/src/main/java/com/ticketPing/performance/infrastructure/listener/EventConsumer.java b/services/performance/src/main/java/com/ticketPing/performance/infrastructure/listener/EventConsumer.java new file mode 100644 index 00000000..0c1abcad --- /dev/null +++ b/services/performance/src/main/java/com/ticketPing/performance/infrastructure/listener/EventConsumer.java @@ -0,0 +1,27 @@ +package com.ticketPing.performance.infrastructure.listener; + +import com.ticketPing.performance.application.service.SeatService; +import lombok.RequiredArgsConstructor; +import messaging.events.OrderCompletedForSeatReservationEvent; +import messaging.utils.EventLogger; +import messaging.utils.EventSerializer; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.Acknowledgment; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class EventConsumer { + + private final SeatService seatService; + + @KafkaListener(topics = "order-completed-for-seat-reservation", groupId = "performance-group") + public void handleOrderCompletedForSeatReservationEvent(ConsumerRecord record, Acknowledgment acknowledgment) { + EventLogger.logReceivedMessage(record); + OrderCompletedForSeatReservationEvent event = EventSerializer.deserialize(record.value(), OrderCompletedForSeatReservationEvent.class); + seatService.reserveSeat(event.scheduleId(), event.seatId()); + acknowledgment.acknowledge(); + } + +} diff --git a/services/performance/src/main/java/com/ticketPing/performance/infrastructure/service/CacheService.java b/services/performance/src/main/java/com/ticketPing/performance/infrastructure/service/CacheService.java index a5a7889a..b0ea0f2d 100644 --- a/services/performance/src/main/java/com/ticketPing/performance/infrastructure/service/CacheService.java +++ b/services/performance/src/main/java/com/ticketPing/performance/infrastructure/service/CacheService.java @@ -55,6 +55,9 @@ public void extendPreReserveTTL(UUID scheduleId, UUID seatId, Duration ttl) { } public void canclePreReserveSeat(UUID scheduleId, UUID seatId) { + String ttlKey = "ttl:{" + scheduleId + "}:" + seatId; + redissonClient.getBucket(ttlKey).delete(); + String key = "seat:{" + scheduleId + "}"; RMap seatCacheMap = redissonClient.getMap(key, JsonJacksonCodec.INSTANCE); @@ -63,9 +66,20 @@ public void canclePreReserveSeat(UUID scheduleId, UUID seatId) { seatCache.cancelPreReserveSeat(); seatCacheMap.put(seatId.toString(), seatCache); + } + public void reserveSeat(String scheduleId, String seatId) { String ttlKey = "ttl:{" + scheduleId + "}:" + seatId; redissonClient.getBucket(ttlKey).delete(); + + String key = "seat:{" + scheduleId + "}"; + RMap seatCacheMap = redissonClient.getMap(key, JsonJacksonCodec.INSTANCE); + + SeatCache seatCache = Optional.ofNullable(seatCacheMap.get(seatId.toString())) + .orElseThrow(() -> new ApplicationException(SeatExceptionCase.SEAT_CACHE_NOT_FOUND)); + seatCache.reserveSeat(); + + seatCacheMap.put(seatId.toString(), seatCache); } public void cacheAvailableSeats(UUID performanceId, long availableSeats) { diff --git a/services/performance/src/main/resources/application.yml b/services/performance/src/main/resources/application.yml index 0781f357..7b29e521 100644 --- a/services/performance/src/main/resources/application.yml +++ b/services/performance/src/main/resources/application.yml @@ -13,6 +13,7 @@ spring: - "classpath:application-eureka.yml" - "classpath:application-jpa.yml" - "classpath:application-redis.yml" + - "classpath:application-kafka.yml" - "classpath:application-monitoring.yml" server: diff --git a/services/queue-manage/src/main/java/com/ticketPing/queue_manage/infrastructure/config/ReactiveKafkaConfig.java b/services/queue-manage/src/main/java/com/ticketPing/queue_manage/infrastructure/config/ReactiveKafkaConfig.java index 168f622a..f871a9b2 100644 --- a/services/queue-manage/src/main/java/com/ticketPing/queue_manage/infrastructure/config/ReactiveKafkaConfig.java +++ b/services/queue-manage/src/main/java/com/ticketPing/queue_manage/infrastructure/config/ReactiveKafkaConfig.java @@ -39,7 +39,7 @@ public ReceiverOptions receiverOptions() { props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); // 토픽 할당 - List topics = Arrays.asList(OrderTopic.COMPLETED.getTopic()); + List topics = Arrays.asList(OrderTopic.COMPLETED_FOR_QUEUE_TOKEN_REMOVAL.getTopic()); return ReceiverOptions.create(props) .subscription(topics) diff --git a/services/queue-manage/src/main/java/com/ticketPing/queue_manage/infrastructure/listener/EventConsumer.java b/services/queue-manage/src/main/java/com/ticketPing/queue_manage/infrastructure/listener/EventConsumer.java index 55080750..401085e5 100644 --- a/services/queue-manage/src/main/java/com/ticketPing/queue_manage/infrastructure/listener/EventConsumer.java +++ b/services/queue-manage/src/main/java/com/ticketPing/queue_manage/infrastructure/listener/EventConsumer.java @@ -7,6 +7,7 @@ import java.time.Duration; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import messaging.events.OrderCompletedForQueueTokenRemovalEvent; import messaging.utils.EventLogger; import messaging.utils.EventSerializer; import org.springframework.boot.context.event.ApplicationReadyEvent; @@ -16,7 +17,6 @@ import reactor.core.publisher.Mono; import reactor.kafka.receiver.ReceiverRecord; import reactor.util.retry.Retry; -import messaging.events.OrderCompletedEvent; import messaging.topics.OrderTopic; @Slf4j @@ -38,7 +38,7 @@ public void consumeMessage() { } private Mono handleMessage(ReceiverRecord record) { - if (record.topic().equals(OrderTopic.COMPLETED.getTopic())) { + if (record.topic().equals(OrderTopic.COMPLETED_FOR_QUEUE_TOKEN_REMOVAL.getTopic())) { return handleOrderCompletedEvent(record); } return Mono.empty(); @@ -46,7 +46,7 @@ private Mono handleMessage(ReceiverRecord record) { private Mono handleOrderCompletedEvent(ReceiverRecord record) { EventLogger.logReceivedMessage(record); - OrderCompletedEvent event = EventSerializer.deserialize(record.value(), OrderCompletedEvent.class); + OrderCompletedForQueueTokenRemovalEvent event = EventSerializer.deserialize(record.value(), OrderCompletedForQueueTokenRemovalEvent.class); String tokenValue = generateTokenValue(event.userId(), event.performanceId()); return Mono.fromRunnable(() -> workingQueueService.transferToken(ORDER_COMPLETED, tokenValue))