diff --git a/src/main/java/com/sampoom/backend/api/material/event/dto/MaterialEvent.java b/src/main/java/com/sampoom/backend/api/material/event/dto/MaterialEvent.java index 87db6c6..6da79f6 100644 --- a/src/main/java/com/sampoom/backend/api/material/event/dto/MaterialEvent.java +++ b/src/main/java/com/sampoom/backend/api/material/event/dto/MaterialEvent.java @@ -27,6 +27,7 @@ public static class Payload { private String name; private String materialUnit; private Integer baseQuantity; + private Integer standardQuantity; // 기준 수량 추가 private Integer leadTime; private boolean deleted; private Long materialCategoryId; diff --git a/src/main/java/com/sampoom/backend/api/material/event/service/MaterialEventBatchService.java b/src/main/java/com/sampoom/backend/api/material/event/service/MaterialEventBatchService.java index 1b056a6..2824393 100644 --- a/src/main/java/com/sampoom/backend/api/material/event/service/MaterialEventBatchService.java +++ b/src/main/java/com/sampoom/backend/api/material/event/service/MaterialEventBatchService.java @@ -29,19 +29,24 @@ public void publishAllMaterialEvents() { List materials = materialRepository.findAll(); for (Material material : materials) { try { - // MaterialEvent.Payload 생성 (필요에 따라 필드 매핑) - MaterialEvent.Payload payload = MaterialEvent.Payload.builder() - .materialId(material.getId()) - .materialCode(material.getMaterialCode()) - .name(material.getName()) - .materialUnit(material.getMaterialUnit()) - .baseQuantity(material.getBaseQuantity()) - .leadTime(material.getLeadTime()) - .standardCost(material.getStandardCost()) - .deleted(false) - .materialCategoryId(material.getMaterialCategory().getId()) - - // ... 필요한 필드 추가 ... + // 전체 MaterialEvent 객체 생성 + MaterialEvent materialEvent = MaterialEvent.builder() + .eventId(java.util.UUID.randomUUID().toString()) + .eventType("MaterialCreated") + .version(material.getVersion()) + .occurredAt(java.time.OffsetDateTime.now().toString()) + .payload(MaterialEvent.Payload.builder() + .materialId(material.getId()) + .materialCode(material.getMaterialCode()) + .name(material.getName()) + .materialUnit(material.getMaterialUnit()) + .baseQuantity(material.getBaseQuantity()) + .standardQuantity(material.getStandardQuantity() != null ? material.getStandardQuantity() : 1) + .leadTime(material.getLeadTime()) + .standardCost(material.getStandardCost()) + .deleted(false) + .materialCategoryId(material.getMaterialCategory().getId()) + .build()) .build(); // Outbox 엔티티 생성 @@ -49,10 +54,10 @@ public void publishAllMaterialEvents() { .aggregateType("MATERIAL") .aggregateId(material.getId()) .eventType("MaterialCreated") - .payload(objectMapper.writeValueAsString(payload)) + .payload(objectMapper.writeValueAsString(materialEvent)) .version(material.getVersion()) .occurredAt(OffsetDateTime.now()) - .status(OutboxStatus.READY) // 상태 필드명에 맞게 수정 + .status(OutboxStatus.READY) .build(); outboxRepository.save(outbox); diff --git a/src/main/java/com/sampoom/backend/api/material/service/MaterialService.java b/src/main/java/com/sampoom/backend/api/material/service/MaterialService.java index e84509f..4da7594 100644 --- a/src/main/java/com/sampoom/backend/api/material/service/MaterialService.java +++ b/src/main/java/com/sampoom/backend/api/material/service/MaterialService.java @@ -109,24 +109,24 @@ public MaterialResponseDTO createMaterial(MaterialRequestDTO requestDTO) { materialRepository.save(material); - // 이벤트 발행 - 부품 이벤트와 같은 방식으로 수정 - MaterialEvent.Payload payload = MaterialEvent.Payload.builder() - .materialId(material.getId()) - .materialCode(material.getMaterialCode()) - .name(material.getName()) - .materialUnit(material.getMaterialUnit()) - .baseQuantity(material.getBaseQuantity()) - .leadTime(material.getLeadTime()) - .deleted(false) - .materialCategoryId(category.getId()) - .build(); - - MaterialEvent event = MaterialEvent.builder() + // 이벤트 발행 - 전체 MaterialEvent 객체 저장 + MaterialEvent materialEvent = MaterialEvent.builder() .eventId(UUID.randomUUID().toString()) .eventType("MaterialCreated") .version(material.getVersion()) .occurredAt(OffsetDateTime.now().toString()) - .payload(payload) + .payload(MaterialEvent.Payload.builder() + .materialId(material.getId()) + .materialCode(material.getMaterialCode()) + .name(material.getName()) + .materialUnit(material.getMaterialUnit()) + .baseQuantity(material.getBaseQuantity()) + .standardQuantity(material.getStandardQuantity() != null ? material.getStandardQuantity() : 1) + .leadTime(material.getLeadTime()) + .deleted(false) + .materialCategoryId(category.getId()) + .standardCost(material.getStandardCost()) + .build()) .build(); outboxService.saveEvent( @@ -134,7 +134,7 @@ public MaterialResponseDTO createMaterial(MaterialRequestDTO requestDTO) { material.getId(), "MaterialCreated", material.getVersion(), - event.getPayload() // event 전체가 아닌 payload만 전달 + materialEvent ); return new MaterialResponseDTO(material); @@ -171,24 +171,24 @@ public MaterialResponseDTO updateMaterial(Long id, MaterialRequestDTO requestDTO materialRepository.flush(); - // 이벤트 발행 - 부품 이벤트와 같은 방식으로 수정 - MaterialEvent.Payload payload = MaterialEvent.Payload.builder() - .materialId(material.getId()) - .materialCode(material.getMaterialCode()) - .name(material.getName()) - .materialUnit(material.getMaterialUnit()) - .baseQuantity(material.getBaseQuantity()) - .leadTime(material.getLeadTime()) - .deleted(false) - .materialCategoryId(material.getMaterialCategory().getId()) - .build(); - - MaterialEvent event = MaterialEvent.builder() + // 이벤트 발행 - 전체 MaterialEvent 객체 저장 + MaterialEvent materialEvent = MaterialEvent.builder() .eventId(UUID.randomUUID().toString()) .eventType("MaterialUpdated") .version(material.getVersion()) .occurredAt(OffsetDateTime.now().toString()) - .payload(payload) + .payload(MaterialEvent.Payload.builder() + .materialId(material.getId()) + .materialCode(material.getMaterialCode()) + .name(material.getName()) + .materialUnit(material.getMaterialUnit()) + .baseQuantity(material.getBaseQuantity()) + .standardQuantity(material.getStandardQuantity() != null ? material.getStandardQuantity() : 1) + .leadTime(material.getLeadTime()) + .deleted(false) + .materialCategoryId(material.getMaterialCategory().getId()) + .standardCost(material.getStandardCost()) + .build()) .build(); outboxService.saveEvent( @@ -196,7 +196,7 @@ public MaterialResponseDTO updateMaterial(Long id, MaterialRequestDTO requestDTO material.getId(), "MaterialUpdated", material.getVersion(), - event.getPayload() // event 전체가 아닌 payload만 전달 + materialEvent ); return new MaterialResponseDTO(material); @@ -208,24 +208,24 @@ public void deleteMaterial(Long id) { Material material = materialRepository.findById(id) .orElseThrow(() -> new NotFoundException(ErrorStatus.MATERIAL_NOT_FOUND)); - // 이벤트 발행 (삭제 전에) - 부품 이벤트와 같은 방식으로 수정 - MaterialEvent.Payload payload = MaterialEvent.Payload.builder() - .materialId(material.getId()) - .materialCode(material.getMaterialCode()) - .name(material.getName()) - .materialUnit(material.getMaterialUnit()) - .baseQuantity(material.getBaseQuantity()) - .leadTime(material.getLeadTime()) - .deleted(true) - .materialCategoryId(material.getMaterialCategory().getId()) - .build(); - - MaterialEvent event = MaterialEvent.builder() + // 이벤트 발행 (삭제 전에) - 전체 MaterialEvent 객체 저장 + MaterialEvent materialEvent = MaterialEvent.builder() .eventId(UUID.randomUUID().toString()) .eventType("MaterialDeleted") .version(material.getVersion()) .occurredAt(OffsetDateTime.now().toString()) - .payload(payload) + .payload(MaterialEvent.Payload.builder() + .materialId(material.getId()) + .materialCode(material.getMaterialCode()) + .name(material.getName()) + .materialUnit(material.getMaterialUnit()) + .baseQuantity(material.getBaseQuantity()) + .standardQuantity(material.getStandardQuantity() != null ? material.getStandardQuantity() : 1) + .leadTime(material.getLeadTime()) + .deleted(true) + .materialCategoryId(material.getMaterialCategory().getId()) + .standardCost(material.getStandardCost()) + .build()) .build(); outboxService.saveEvent( @@ -233,7 +233,7 @@ public void deleteMaterial(Long id) { material.getId(), "MaterialDeleted", material.getVersion(), - event.getPayload() // event 전체가 아닌 payload만 전달 + materialEvent ); materialRepository.delete(material); diff --git a/src/main/java/com/sampoom/backend/api/part/event/dto/PartEvent.java b/src/main/java/com/sampoom/backend/api/part/event/dto/PartEvent.java index f7567ae..0d648fc 100644 --- a/src/main/java/com/sampoom/backend/api/part/event/dto/PartEvent.java +++ b/src/main/java/com/sampoom/backend/api/part/event/dto/PartEvent.java @@ -24,8 +24,9 @@ public static class Payload { private String name; private String partUnit; - private Integer baseQuantity; + private Integer baseQuantity; // 안전재고 private Integer leadTime; + private Integer standardQuantity; // 기준수량 private String status; private Boolean deleted; diff --git a/src/main/java/com/sampoom/backend/api/part/event/service/PartEventBatchService.java b/src/main/java/com/sampoom/backend/api/part/event/service/PartEventBatchService.java index b6ff9a1..e3671cb 100644 --- a/src/main/java/com/sampoom/backend/api/part/event/service/PartEventBatchService.java +++ b/src/main/java/com/sampoom/backend/api/part/event/service/PartEventBatchService.java @@ -42,25 +42,33 @@ public void publishAllPartEvents() { var group = part.getPartGroup(); var category = (group != null) ? group.getCategory() : null; - PartEvent.Payload payload = PartEvent.Payload.builder() - .partId(part.getId()) - .code(part.getCode()) - .name(part.getName()) - .partUnit(part.getPartUnit()) - .baseQuantity(part.getBaseQuantity()) - .leadTime(part.getLeadTime()) - .status(part.getStatus().name()) - .deleted(false) - .groupId(group != null ? group.getId() : null) - .categoryId(category != null ? category.getId() : null) - .standardCost(part.getStandardCost()) + // 전체 PartEvent 객체 생성 + PartEvent partEvent = PartEvent.builder() + .eventId(java.util.UUID.randomUUID().toString()) + .eventType("PartCreated") + .version(part.getVersion()) + .occurredAt(java.time.OffsetDateTime.now().toString()) + .payload(PartEvent.Payload.builder() + .partId(part.getId()) + .code(part.getCode()) + .name(part.getName()) + .partUnit(part.getPartUnit()) + .baseQuantity(part.getBaseQuantity()) + .standardQuantity(part.getStandardQuantity() != null ? part.getStandardQuantity() : 1) + .leadTime(part.getLeadTime()) + .status(part.getStatus().name()) + .deleted(false) + .groupId(group != null ? group.getId() : null) + .categoryId(category != null ? category.getId() : null) + .standardCost(part.getStandardCost()) + .build()) .build(); Outbox outbox = Outbox.builder() .aggregateType("PART") .aggregateId(part.getId()) .eventType("PartCreated") - .payload(objectMapper.writeValueAsString(payload)) + .payload(objectMapper.writeValueAsString(partEvent)) .version(part.getVersion()) .occurredAt(OffsetDateTime.now()) .status(OutboxStatus.READY) diff --git a/src/main/java/com/sampoom/backend/api/part/service/PartService.java b/src/main/java/com/sampoom/backend/api/part/service/PartService.java index 4934a0b..1e07c00 100644 --- a/src/main/java/com/sampoom/backend/api/part/service/PartService.java +++ b/src/main/java/com/sampoom/backend/api/part/service/PartService.java @@ -151,27 +151,35 @@ public PartListResponseDTO createPart(PartCreateRequestDTO partCreateRequestDTO) } } - PartEvent.Payload payload = PartEvent.Payload.builder() - .partId(savedPart.getId()) - .code(savedPart.getCode()) - .name(savedPart.getName()) - .partUnit(savedPart.getPartUnit()) - .baseQuantity(savedPart.getBaseQuantity()) - .leadTime(savedPart.getLeadTime()) - .status(savedPart.getStatus().name()) - .deleted(false) - .groupId(partGroup.getId()) - .categoryId(partGroup.getCategory().getId()) - .standardCost(savedPart.getStandardCost()) + // 전체 PartEvent 객체 생성 + PartEvent partEvent = PartEvent.builder() + .eventId(java.util.UUID.randomUUID().toString()) + .eventType("PartCreated") + .version(savedPart.getVersion()) + .occurredAt(java.time.OffsetDateTime.now().toString()) + .payload(PartEvent.Payload.builder() + .partId(savedPart.getId()) + .code(savedPart.getCode()) + .name(savedPart.getName()) + .partUnit(savedPart.getPartUnit()) + .baseQuantity(savedPart.getBaseQuantity()) + .standardQuantity(savedPart.getStandardQuantity()) + .leadTime(savedPart.getLeadTime()) + .status(savedPart.getStatus().name()) + .deleted(false) + .groupId(partGroup.getId()) + .categoryId(partGroup.getCategory().getId()) + .standardCost(savedPart.getStandardCost()) + .build()) .build(); - // OutboxService 호출 + // OutboxService 호출 (전체 이벤트 객체) outboxService.saveEvent( "PART", savedPart.getId(), "PartCreated", savedPart.getVersion(), - payload + partEvent.getPayload() ); return new PartListResponseDTO(savedPart); @@ -204,18 +212,26 @@ public PartListResponseDTO updatePart(Long partId, PartUpdateRequestDTO partUpda partRepository.flush(); - PartEvent.Payload payload = PartEvent.Payload.builder() - .partId(part.getId()) - .code(part.getCode()) - .name(part.getName()) - .partUnit(part.getPartUnit()) - .baseQuantity(part.getBaseQuantity()) - .leadTime(part.getLeadTime()) - .status(part.getStatus().name()) - .deleted(false) - .groupId(part.getPartGroup().getId()) - .categoryId(part.getPartGroup().getCategory().getId()) - .standardCost(part.getStandardCost()) + // 전체 PartEvent 객체 생성 + PartEvent partEvent = PartEvent.builder() + .eventId(java.util.UUID.randomUUID().toString()) + .eventType("PartUpdated") + .version(part.getVersion()) + .occurredAt(java.time.OffsetDateTime.now().toString()) + .payload(PartEvent.Payload.builder() + .partId(part.getId()) + .code(part.getCode()) + .name(part.getName()) + .partUnit(part.getPartUnit()) + .baseQuantity(part.getBaseQuantity()) + .standardQuantity(part.getStandardQuantity() != null ? part.getStandardQuantity() : 1) + .leadTime(part.getLeadTime()) + .status(part.getStatus().name()) + .deleted(false) + .groupId(part.getPartGroup().getId()) + .categoryId(part.getPartGroup().getCategory().getId()) + .standardCost(part.getStandardCost()) + .build()) .build(); // OutboxService 호출 @@ -224,7 +240,7 @@ public PartListResponseDTO updatePart(Long partId, PartUpdateRequestDTO partUpda part.getId(), "PartUpdated", part.getVersion(), - payload + partEvent.getPayload() ); return new PartListResponseDTO(part); @@ -248,27 +264,35 @@ public void deletePart(Long partId) { partRepository.flush(); - PartEvent.Payload payload = PartEvent.Payload.builder() - .partId(part.getId()) - .code(part.getCode()) - .name(part.getName()) - .partUnit(part.getPartUnit()) - .baseQuantity(part.getBaseQuantity()) - .leadTime(part.getLeadTime()) - .status(part.getStatus().name()) // "DISCONTINUED" - .deleted(true) - .groupId(part.getPartGroup().getId()) - .categoryId(part.getPartGroup().getCategory().getId()) - .standardCost(part.getStandardCost()) + // 전체 PartEvent 객체 생성 + PartEvent partEvent = PartEvent.builder() + .eventId(java.util.UUID.randomUUID().toString()) + .eventType("PartDeleted") + .version(part.getVersion()) + .occurredAt(java.time.OffsetDateTime.now().toString()) + .payload(PartEvent.Payload.builder() + .partId(part.getId()) + .code(part.getCode()) + .name(part.getName()) + .partUnit(part.getPartUnit()) + .baseQuantity(part.getBaseQuantity()) + .standardQuantity(part.getStandardQuantity() != null ? part.getStandardQuantity() : 1) + .leadTime(part.getLeadTime()) + .status(part.getStatus().name()) // "DISCONTINUED" + .deleted(true) + .groupId(part.getPartGroup().getId()) + .categoryId(part.getPartGroup().getCategory().getId()) + .standardCost(part.getStandardCost()) + .build()) .build(); - // OutboxService 호출 + // OutboxService 호출 (전체 이벤트 객체) outboxService.saveEvent( "PART", part.getId(), "PartDeleted", part.getVersion(), - payload + partEvent.getPayload() ); } @@ -387,18 +411,26 @@ public void updateLeadTimeFromProcess(Long partId) { // 이벤트 발행 헬퍼 메서드 @Transactional(readOnly = true) public void publishPartUpdatedEvent(Part part) { - PartEvent.Payload payload = PartEvent.Payload.builder() - .partId(part.getId()) - .code(part.getCode()) - .name(part.getName()) - .partUnit(part.getPartUnit()) - .baseQuantity(part.getBaseQuantity()) - .leadTime(part.getLeadTime()) - .status(part.getStatus().name()) - .deleted(part.getStatus() == PartStatus.DISCONTINUED) - .groupId(part.getPartGroup().getId()) - .categoryId(part.getPartGroup().getCategory().getId()) - .standardCost(part.getStandardCost()) + // 전체 PartEvent 객체 생성 + PartEvent partEvent = PartEvent.builder() + .eventId(java.util.UUID.randomUUID().toString()) + .eventType("PartUpdated") + .version(part.getVersion()) + .occurredAt(java.time.OffsetDateTime.now().toString()) + .payload(PartEvent.Payload.builder() + .partId(part.getId()) + .code(part.getCode()) + .name(part.getName()) + .partUnit(part.getPartUnit()) + .baseQuantity(part.getBaseQuantity()) + .standardQuantity(part.getStandardQuantity() != null ? part.getStandardQuantity() : 1) + .leadTime(part.getLeadTime()) + .status(part.getStatus().name()) + .deleted(part.getStatus() == PartStatus.DISCONTINUED) + .groupId(part.getPartGroup().getId()) + .categoryId(part.getPartGroup().getCategory().getId()) + .standardCost(part.getStandardCost()) + .build()) .build(); outboxService.saveEvent( @@ -406,7 +438,7 @@ public void publishPartUpdatedEvent(Part part) { part.getId(), "PartUpdated", part.getVersion(), - payload + partEvent.getPayload() ); } diff --git a/src/main/java/com/sampoom/backend/common/outbox/repository/OutboxRepository.java b/src/main/java/com/sampoom/backend/common/outbox/repository/OutboxRepository.java index 368d9d7..a8fc3da 100644 --- a/src/main/java/com/sampoom/backend/common/outbox/repository/OutboxRepository.java +++ b/src/main/java/com/sampoom/backend/common/outbox/repository/OutboxRepository.java @@ -3,15 +3,14 @@ import com.sampoom.backend.common.outbox.entity.Outbox; import com.sampoom.backend.common.outbox.entity.OutboxStatus; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; public interface OutboxRepository extends JpaRepository { - List findTop10ByStatusOrderByCreatedAtAsc(OutboxStatus status); - - boolean existsByAggregateIdAndStatus(Long aggregateId, OutboxStatus status); - - // ⭐️ [추가] 부트스트랩에서 중복 저장을 방지하기 위해 추가 - boolean existsByAggregateIdAndAggregateType(Long aggregateId, String aggregateType); + // READY와 FAILED 상태를 모두 조회하되, 재시도 횟수가 maxRetryCount 미만인 것만 조회 + @Query("SELECT o FROM Outbox o WHERE (o.status = 'READY' OR (o.status = 'FAILED' AND o.retryCount < :maxRetryCount)) ORDER BY o.createdAt ASC") + List findTop10ByStatusReadyOrFailedWithRetryLimitOrderByCreatedAtAsc(@Param("maxRetryCount") int maxRetryCount); } diff --git a/src/main/java/com/sampoom/backend/common/outbox/service/OutboxEventProcessor.java b/src/main/java/com/sampoom/backend/common/outbox/service/OutboxEventProcessor.java index 4a67017..553d46d 100644 --- a/src/main/java/com/sampoom/backend/common/outbox/service/OutboxEventProcessor.java +++ b/src/main/java/com/sampoom/backend/common/outbox/service/OutboxEventProcessor.java @@ -47,14 +47,7 @@ public void processAndPublishEvent(Outbox outbox) { // AggregateType에 따라 DTO 역질렬화 및 토픽/이벤트 구성 switch (outbox.getAggregateType()) { case "PART": - PartEvent.Payload partPayload = objectMapper.readValue(outbox.getPayload(), PartEvent.Payload.class); - eventToSend = PartEvent.builder() - .eventId(outbox.getEventId()) - .eventType(outbox.getEventType()) - .version(outbox.getVersion()) - .occurredAt(outbox.getOccurredAt().toString()) - .payload(partPayload) - .build(); + eventToSend = objectMapper.readValue(outbox.getPayload(), PartEvent.class); topicName = TOPIC_PART; // Part 토픽 break; @@ -96,14 +89,8 @@ public void processAndPublishEvent(Outbox outbox) { break; case "MATERIAL": - com.sampoom.backend.api.material.event.dto.MaterialEvent.Payload materialPayload = objectMapper.readValue(outbox.getPayload(), com.sampoom.backend.api.material.event.dto.MaterialEvent.Payload.class); - eventToSend = com.sampoom.backend.api.material.event.dto.MaterialEvent.builder() - .eventId(outbox.getEventId()) - .eventType(outbox.getEventType()) - .version(outbox.getVersion()) - .occurredAt(outbox.getOccurredAt().toString()) - .payload(materialPayload) - .build(); + // 전체 MaterialEvent 객체를 직접 역직렬화 + eventToSend = objectMapper.readValue(outbox.getPayload(), com.sampoom.backend.api.material.event.dto.MaterialEvent.class); topicName = TOPIC_MATERIAL; break; diff --git a/src/main/java/com/sampoom/backend/common/outbox/service/OutboxPublisher.java b/src/main/java/com/sampoom/backend/common/outbox/service/OutboxPublisher.java index f200f3c..c52188f 100644 --- a/src/main/java/com/sampoom/backend/common/outbox/service/OutboxPublisher.java +++ b/src/main/java/com/sampoom/backend/common/outbox/service/OutboxPublisher.java @@ -19,23 +19,33 @@ public class OutboxPublisher { private final OutboxRepository outboxRepository; private final OutboxEventProcessor outboxEventProcessor; + // 최대 재시도 횟수 + private static final int MAX_RETRY_COUNT = 10; + /** - * READY 상태 Outbox 5초마다 Kafka로 발행 + * READY와 FAILED 상태 Outbox를 5초마다 Kafka로 발행 (최대 10번 재시도) */ @Scheduled(fixedDelay = 5000) - public void publishReadyEvents() { - List events = outboxRepository.findTop10ByStatusOrderByCreatedAtAsc(OutboxStatus.READY); + public void publishReadyAndFailedEvents() { + // READY 상태와 재시도 횟수가 10회 미만인 FAILED 상태 이벤트 조회 + List events = outboxRepository.findTop10ByStatusReadyOrFailedWithRetryLimitOrderByCreatedAtAsc(MAX_RETRY_COUNT); if (events.isEmpty()) return; - log.info("[PartOutboxPublisher] 발행할 이벤트 {}개 발견", events.size()); + log.info("[OutboxPublisher] 발행할 이벤트 {}개 발견 (READY + FAILED 재시도 대상)", events.size()); for (Outbox outbox : events) { try { + // 재시도 로그 출력 + if (outbox.getStatus() == OutboxStatus.FAILED) { + log.info("[OutboxPublisher] FAILED 이벤트 재시도: eventId={}, retryCount={}/{}", + outbox.getEventId(), outbox.getRetryCount(), MAX_RETRY_COUNT); + } + // 개별 트랜잭션으로 처리하기 위해 public 메서드 호출 outboxEventProcessor.processAndPublishEvent(outbox); } catch (Exception e) { - log.error("[PartOutboxPublisher] 이벤트 처리 중 심각한 오류 발생 (트랜잭션 롤백됨): eventId={}, reason={}", + log.error("[OutboxPublisher] 이벤트 처리 중 심각한 오류 발생 (트랜잭션 롤백됨): eventId={}, reason={}", outbox.getEventId(), e.getMessage()); } }