Skip to content
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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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()
);
}
}
Original file line number Diff line number Diff line change
@@ -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()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,6 +10,4 @@ public interface PerformanceClient {
ResponseEntity<CommonResponse<OrderSeatResponse>> getOrderInfo(UUID userId, UUID scheduleId, UUID seatId);

ResponseEntity<CommonResponse<Object>> extendPreReserveTTL(UUID scheduleId, UUID seatId);

ResponseEntity<CommonResponse<SeatResponse>> updateSeatState(UUID seatId, Boolean seatState);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,9 +14,14 @@ public class EventApplicationService {

private final KafkaTemplate<String, String> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -48,30 +45,18 @@ public List<OrderResponse> 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
Expand All @@ -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<Order> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 상태값을 사용해야 합니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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<Order> findById(UUID orderId);

Optional<Order> findByIdAndOrderStatus(UUID orderId, OrderStatus orderStatus);

List<Order> findByUserId(UUID userId);

boolean existsByOrderSeatSeatIdAndOrderStatusIn(UUID seatId, List<OrderStatus> pending);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -22,8 +22,5 @@ ResponseEntity<CommonResponse<OrderSeatResponse>> getOrderInfo(@RequestHeader("X
ResponseEntity<CommonResponse<Object>> extendPreReserveTTL(@RequestParam("scheduleId") UUID scheduleId,
@PathVariable("seatId") UUID seatId);

@PutMapping("/api/v1/seats/{seatId}")
ResponseEntity<CommonResponse<SeatResponse>> updateSeatState(@PathVariable("seatId") UUID seatId,
@RequestParam("seatState") Boolean seatState);
}

Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class EventConsumer {
public void handlePaymentCompletedEvent(ConsumerRecord<String, String> 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();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<Order, UUID> {
List<Order> findByUserId(UUID userId);
List<Order> findByOrderSeatSeatId(UUID seatId);
import java.util.UUID;

public interface OrderJpaRepository extends OrderRepository, JpaRepository<Order, UUID> {

}
Loading
Loading