diff --git a/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxFailureService.java b/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxFailureService.java new file mode 100644 index 0000000..6c5fe76 --- /dev/null +++ b/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxFailureService.java @@ -0,0 +1,73 @@ +package com.backendsystemdesignlab.notification.outbox; + +import com.backendsystemdesignlab.notification.notification.domain.DeliveryStatus; +import com.backendsystemdesignlab.notification.notification.domain.Notification; +import com.backendsystemdesignlab.notification.notification.domain.NotificationDelivery; +import com.backendsystemdesignlab.notification.notification.repository.NotificationDeliveryRepository; +import com.backendsystemdesignlab.notification.notification.repository.NotificationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class OutboxFailureService { + + private static final int MAX_PUBLISH_ATTEMPTS = 5; + + private final OutboxEventRepository outboxEventRepository; + private final NotificationRepository notificationRepository; + private final NotificationDeliveryRepository deliveryRepository; + + @Transactional + public boolean recordFailure(Long outboxEventId, String error) { + + OutboxEvent event = outboxEventRepository.findById(outboxEventId) + .orElseThrow(() -> new IllegalArgumentException("Outbox event not found: " + outboxEventId)); + + if (event.getStatus() != OutboxStatus.PENDING) { + return false; + } + + event.recordFailure(error); + + if (event.getAttemptCount() < MAX_PUBLISH_ATTEMPTS) { + return false; + } + + event.markFailed(); + + Notification notification = notificationRepository.findByIdForUpdate(event.getNotificationId()) + .orElseThrow(() -> new IllegalArgumentException("알림을 찾을 수 없습니다.")); + + NotificationDelivery delivery = deliveryRepository.findById(event.getDeliveryId()) + .orElseThrow(() -> new IllegalArgumentException("전송 정보를 찾을 수 없습니다.")); + + if (delivery.getStatus() == DeliveryStatus.PENDING) { + delivery.markFailed(); + } + + updateNotificationStatus(notification); + + return true; + } + + private void updateNotificationStatus(Notification notification) { + deliveryRepository.flush(); + + long total = deliveryRepository.countByNotificationId(notification.getId()); + long sent = deliveryRepository.countByNotificationIdAndStatus(notification.getId(), DeliveryStatus.SENT); + long failed = deliveryRepository.countByNotificationIdAndStatus(notification.getId(), DeliveryStatus.FAILED); + + // 아직 처리 중인 Delivery 존재 + if (sent + failed < total) { + return; + } + + if (failed > 0) { + notification.fail(); + } else { + notification.complete(); + } + } +} diff --git a/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxPublisher.java b/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxPublisher.java index 20bbe4f..4466ec1 100644 --- a/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxPublisher.java +++ b/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxPublisher.java @@ -22,6 +22,7 @@ public class OutboxPublisher { private final OutboxTransactionService transactionService; private final RabbitTemplate rabbitTemplate; private final NotificationTransactionService notificationTransactionService; + private final OutboxFailureService failureService; // DB Connection 없음 (RabbitMQ가 DB를 잡고 있지 않게) @Scheduled( @@ -95,14 +96,9 @@ private String routingKey(OutboxEvent event) { private void handleFailure(OutboxEvent event, String reason) { - boolean finalFailure = transactionService.recordPublishFailure(event.getId(), reason); + boolean finalFailure = failureService.recordFailure(event.getId(), reason); if (finalFailure) { - notificationTransactionService.recordPublishFinalFailure( - event.getNotificationId(), - event.getDeliveryId() - ); - log.error("Outbox 마지막 시도 실패. outboxId={}, reason={}", event.getId(), reason); } else { log.warn("Outbox publish 실패. outboxId={}, reason={}", event.getId(), reason); diff --git a/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxTransactionService.java b/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxTransactionService.java index 5ce7d6a..2054ffd 100644 --- a/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxTransactionService.java +++ b/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxTransactionService.java @@ -11,7 +11,6 @@ public class OutboxTransactionService { private final OutboxEventRepository outboxEventRepository; - private static final int MAX_PUBLISH_ATTEMPTS = 5; @Transactional(readOnly = true) public List findPendingEvents() { @@ -28,23 +27,4 @@ public void markPublished(Long outboxEventId) { } event.markPublished(); } - - @Transactional - public boolean recordPublishFailure(Long outboxEventId, String error) { - OutboxEvent event = outboxEventRepository.findById(outboxEventId) - .orElseThrow(() -> new IllegalArgumentException("Outbox event not found: " + outboxEventId)); - - if (event.getStatus() != OutboxStatus.PENDING) { - return false; - } - - event.recordFailure(error); - - if (event.getAttemptCount() >= MAX_PUBLISH_ATTEMPTS) { - event.markFailed(); - return true; - } - - return false; - } }