쿠버네티스, eBPF, 그리고 머신러닝 이야기
by Yang Taehwan
@Service
public class PaymentService {
private final PaymentRepository paymentRepository;
private final ApplicationEventPublisher applicationEventPublisher;
// ...
@Transactional
public void approvePayment(Long paymentId) {
Payment payment = paymentRepository.findById(paymentId)
.orElseThrow(() -> new IllegalArgumentException("Payment does not exist. paymentId=" + paymentId));
payment.approve();
PaymentCompletedEvent event = new PaymentCompletedEvent(
UUID.randomUUID().toString(),
payment.getId(),
payment.getOrderId(),
payment.getUserId(),
payment.getAmount()
);
applicationEventPublisher.publishEvent(event);
}
}
@Component
public class OrderPaymentEventHandler {
// ...
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handle(PaymentCompletedEvent event) {
try {
Order order = orderRepository.findById(event.orderId())
.orElseThrow(() -> new IllegalArgumentException(
"Order does not exist. orderId=" + event.orderId()
));
order.markPaid();
OrderPaidEvent orderPaidEvent = new OrderPaidEvent(
UUID.randomUUID().toString(),
order.getId(),
order.getUserId()
);
String payload = objectMapper.writeValueAsString(orderPaidEvent);
// OutboxEvent를 만든 후
OutboxEvent outboxEvent = OutboxEvent.pending(
orderPaidEvent.eventId(),
"ORDER",
String.valueOf(order.getId()),
"OrderPaidEvent",
payload
);
// 데이터베이스에 저장한다.
// 이후 이벤트를 모든 consumer가 소비하면 이벤트의 상태를 변경한다.(PENDING -> SENT)
outboxRepository.save(outboxEvent);
} catch (JsonProcessingException e) {
throw new RuntimeException("OrderPaidEvent serialization failed", e);
} catch (Exception e) {
log.error("Failed to handle PaymentCompletedEvent. paymentId={}, orderId={}",
event.paymentId(), event.orderId(), e);
throw e;
}
}
}
@Component
public class OutboxPublisher {
// ...
// 3000ms 주기
// publish는 @Transactional이 아니다.
// PENDING 상태의 이벤트 목록을 가져와 각 이벤트를 트랜잭션 처리 함수롤 전달한다.
@Scheduled(fixedDelay = 3000)
public void publish() {
List<OutboxEvent> events =
outboxRepository.findTop100ByStatusOrderByIdAsc(OutboxStatus.PENDING);
for (OutboxEvent event : events) {
publishSingleEvent(event.getId());
}
}
// Event를 하나씩 가져와 OutboxEventDispatcher에 이벤트 타입에 따른 처리를 위임한다.
@Transactional
public void publishSingleEvent(Long outboxEventId) {
OutboxEvent event = outboxRepository.findById(outboxEventId)
.orElseThrow(() -> new IllegalArgumentException(
"Outbox event does not exist. outboxEventId=" + outboxEventId
));
if (event.getStatus() != OutboxStatus.PENDING) {
log.info("Skipping outbox event because status is not PENDING. eventId={}, status={}",
event.getEventId(),
event.getStatus()
);
return;
}
try {
log.info("Publishing outbox event. eventId={}, eventType={}, aggregateType={}, aggregateId={}",
event.getEventId(),
event.getEventType(),
event.getAggregateType(),
event.getAggregateId()
);
outboxEventDispatcher.dispatch(event);
event.markSent();
log.info("Outbox event published successfully. eventId={}", event.getEventId());
} catch (Exception e) {
log.error("Failed to publish outbox event. eventId={}", event.getEventId(), e);
event.markFailed();
}
}
}
@Component
public class OutboxEventDispatcher {
// ...
// 이벤트 타입에 따라 이벤트를 처리하는 핸들러로 분기 처리한다.
@Transactional
public void dispatch(OutboxEvent outboxEvent) {
if ("OrderPaidEvent".equals(outboxEvent.getEventType())) {
handleOrderPaidEvent(outboxEvent);
return;
}
throw new IllegalArgumentException("Unsupported event type. eventType=" + outboxEvent.getEventType());
}
// OrderPaidEvent 이벤트 핸들러 함수
private void handleOrderPaidEvent(OutboxEvent outboxEvent) {
try {
OrderPaidEvent event = objectMapper.readValue(
outboxEvent.getPayload(),
OrderPaidEvent.class
);
deliveryService.createDelivery(event);
notificationService.sendOrderPaidNotification(event);
} catch (JsonProcessingException e) {
throw new RuntimeException("OrderPaidEvent deserialization failed", e);
}
}
}