From ee87754cc8f7eaacb39b755b42bda0159d707096 Mon Sep 17 00:00:00 2001 From: LocKey Date: Fri, 4 Sep 2026 16:32:24 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20Outbox=20=EC=B5=9C=EC=A2=85=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=9D=84=20=EB=8B=A8=EC=9D=BC=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=EC=9C=BC=EB=A1=9C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notification/outbox/OutboxFailureService.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxFailureService.java 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..227ddb4 --- /dev/null +++ b/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxFailureService.java @@ -0,0 +1,4 @@ +package com.backendsystemdesignlab.notification.outbox; + +public class OutboxFailureService { +} From 7152d76de9d50aafa41dfb3970f84a578ed31146 Mon Sep 17 00:00:00 2001 From: LocKey Date: Fri, 4 Sep 2026 16:32:33 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20Outbox=20=EC=B5=9C=EC=A2=85=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=9D=84=20=EB=8B=A8=EC=9D=BC=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=EC=9C=BC=EB=A1=9C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../outbox/OutboxFailureService.java | 69 +++++++++++++++++++ .../notification/outbox/OutboxPublisher.java | 8 +-- .../outbox/OutboxTransactionService.java | 20 ------ 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxFailureService.java b/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxFailureService.java index 227ddb4..6c5fe76 100644 --- a/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxFailureService.java +++ b/src/main/java/com/backendsystemdesignlab/notification/outbox/OutboxFailureService.java @@ -1,4 +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; - } }