From 6b1c348a0a35cb57fff9afcb2c0481c6dc43441e Mon Sep 17 00:00:00 2001 From: pmh5574 Date: Fri, 19 Dec 2025 13:47:30 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EC=B9=B4=ED=94=84=EC=B9=B4=20?= =?UTF-8?q?=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/commerce-api/build.gradle.kts | 1 + .../application/payment/PaymentFacade.java | 6 ++++ .../loopers/domain/payment/PaymentEvent.java | 14 ++++++++++ .../domain/payment/PaymentEventPublisher.java | 8 ++++++ .../payment/PaymentCoreEventPublisher.java | 28 +++++++++++++++++++ .../event/order/OrderEventListener.java | 25 +++++++++++++++++ .../event/product/ProductEventListener.java | 25 +++++++++++++++++ modules/kafka/src/main/resources/kafka.yml | 5 +++- 8 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEvent.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEventPublisher.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/payment/PaymentCoreEventPublisher.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/interfaces/event/order/OrderEventListener.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java diff --git a/apps/commerce-api/build.gradle.kts b/apps/commerce-api/build.gradle.kts index 7a0e834b2..16226afbd 100644 --- a/apps/commerce-api/build.gradle.kts +++ b/apps/commerce-api/build.gradle.kts @@ -2,6 +2,7 @@ dependencies { // add-ons implementation(project(":modules:jpa")) implementation(project(":modules:redis")) + implementation(project(":modules:kafka")) implementation(project(":supports:jackson")) implementation(project(":supports:logging")) implementation(project(":supports:monitoring")) diff --git a/apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentFacade.java index 163fa9c80..3baffed69 100644 --- a/apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentFacade.java +++ b/apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentFacade.java @@ -8,6 +8,9 @@ import com.loopers.domain.payment.PaymentApproveInfo; import com.loopers.domain.payment.PaymentApproveResponse; import com.loopers.domain.payment.PaymentApproveResponse.Meta.Result; +import com.loopers.domain.payment.PaymentEvent.PaymentPaid; +import com.loopers.domain.payment.PaymentEvent.PaymentFailed; +import com.loopers.domain.payment.PaymentEventPublisher; import com.loopers.domain.payment.PaymentGateway; import com.loopers.domain.payment.PaymentService; import com.loopers.domain.payment.TransactionStatus; @@ -43,6 +46,7 @@ public class PaymentFacade { private final PaymentGateway paymentGateway; private final ProductCacheService productCacheService; private final PointService pointService; + private final PaymentEventPublisher paymentEventPublisher; @Transactional public PaymentInfo requestPaidPayment(Long userId, Long paymentId) { @@ -86,6 +90,7 @@ private PaymentApproveResponse approvePayment(Payment payment) { private void paymentSuccess(final Payment payment) { payment.paid(); + paymentEventPublisher.publish(PaymentPaid.from(payment)); orderService.paid(payment.getOrderId()); Order order = orderService.findById(payment.getOrderId()); List orderItems = orderService.getOrderItemsByOrderId(order.getId()); @@ -114,6 +119,7 @@ private void paymentSuccess(final Payment payment) { } private void paymentFail(final Payment payment, final String message) { payment.fail("PG 승인 중 오류 발생: " + message); + paymentEventPublisher.publish(PaymentFailed.from(payment)); orderService.fail(payment.getOrderId()); } } diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEvent.java b/apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEvent.java new file mode 100644 index 000000000..d2d2ccabb --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEvent.java @@ -0,0 +1,14 @@ +package com.loopers.domain.payment; + +public class PaymentEvent { + public record PaymentPaid(Payment payment) { + public static PaymentPaid from(final Payment payment) { + return new PaymentPaid(payment); + } + } + public record PaymentFailed(Payment payment) { + public static PaymentFailed from(final Payment payment) { + return new PaymentFailed(payment); + } + } +} diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEventPublisher.java b/apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEventPublisher.java new file mode 100644 index 000000000..632eb4135 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEventPublisher.java @@ -0,0 +1,8 @@ +package com.loopers.domain.payment; + +import com.loopers.domain.payment.PaymentEvent.PaymentPaid; + +public interface PaymentEventPublisher { + void publish(PaymentPaid paymentCreated); + void publish(PaymentEvent.PaymentFailed paymentFailed); +} diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/payment/PaymentCoreEventPublisher.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/payment/PaymentCoreEventPublisher.java new file mode 100644 index 000000000..a64b7f764 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/payment/PaymentCoreEventPublisher.java @@ -0,0 +1,28 @@ +package com.loopers.infrastructure.payment; + +import com.loopers.domain.payment.PaymentEvent.PaymentPaid; +import com.loopers.domain.payment.PaymentEvent.PaymentFailed; +import com.loopers.domain.payment.PaymentEventPublisher; +import lombok.RequiredArgsConstructor; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class PaymentCoreEventPublisher implements PaymentEventPublisher { + + private static final String paidTopic = "payment.paid"; + private static final String failedTopic = "payment.failed"; + private final KafkaTemplate kafkaTemplate; + + + @Override + public void publish(final PaymentPaid paymentCreated) { + kafkaTemplate.send(paidTopic, paymentCreated.payment().getId(), paymentCreated); + } + + @Override + public void publish(final PaymentFailed paymentFailed) { + kafkaTemplate.send(failedTopic, paymentFailed.payment().getId(), paymentFailed); + } +} diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/event/order/OrderEventListener.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/event/order/OrderEventListener.java new file mode 100644 index 000000000..2e33c40c1 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/event/order/OrderEventListener.java @@ -0,0 +1,25 @@ +package com.loopers.interfaces.event.order; + +import com.loopers.confg.kafka.KafkaConfig; +import java.util.List; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.Acknowledgment; +import org.springframework.stereotype.Component; + +@Component +public class OrderEventListener { + + @KafkaListener( + topics = {"payment.paid"}, + containerFactory = KafkaConfig.BATCH_LISTENER, + groupId = "order" + ) + public void orderPaidListener( + List> messages, + Acknowledgment acknowledgment + ){ +// messages.stream() + acknowledgment.acknowledge(); + } +} diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java new file mode 100644 index 000000000..bfbd35140 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java @@ -0,0 +1,25 @@ +package com.loopers.interfaces.event.product; + +import com.loopers.confg.kafka.KafkaConfig; +import java.util.List; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.Acknowledgment; +import org.springframework.stereotype.Component; + +@Component +public class ProductEventListener { + + @KafkaListener( + topics = {"payment.paid"}, + containerFactory = KafkaConfig.BATCH_LISTENER, + groupId = "product" + ) + public void productPaidListener( + List> messages, + Acknowledgment acknowledgment + ){ +// messages.stream() + acknowledgment.acknowledge(); + } +} diff --git a/modules/kafka/src/main/resources/kafka.yml b/modules/kafka/src/main/resources/kafka.yml index 9609dbf85..b360ef1a7 100644 --- a/modules/kafka/src/main/resources/kafka.yml +++ b/modules/kafka/src/main/resources/kafka.yml @@ -7,7 +7,7 @@ spring: request.timeout.ms: 20000 retry.backoff.ms: 500 auto: - create.topics.enable: false + create.topics.enable: true register.schemas: false offset.reset: latest use.latest.version: true @@ -15,6 +15,9 @@ spring: key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.springframework.kafka.support.serializer.JsonSerializer retries: 3 + acks: all + properties: + enable.idempotence: true consumer: group-id: loopers-default-consumer key-deserializer: org.apache.kafka.common.serialization.StringDeserializer From 1644e68735bde9a0b8d522391c6fdcf72683b976 Mon Sep 17 00:00:00 2001 From: pmh5574 Date: Mon, 5 Jan 2026 23:42:15 +0900 Subject: [PATCH 2/2] feat: --- .../application/product/ProductFacade.java | 19 +++++++++++- .../com/loopers/domain/product/EventType.java | 13 ++++++++ .../loopers/domain/product/ProductEvent.java | 9 ++++++ .../domain/product/ProductEventPublisher.java | 5 +++ .../domain/product/ProductEventService.java | 22 +++++++++++++ .../domain/product/ProductOutboxEvent.java | 31 +++++++++++++++++++ .../product/ProductOutboxEventRepository.java | 5 +++ .../domain/product/ProductOutboxStatus.java | 15 +++++++++ .../product/ProductCoreEventPublisher.java | 20 ++++++++++++ .../ProductOutboxEventJpaRepository.java | 8 +++++ .../ProductOutboxEventRepositoryImpl.java | 17 ++++++++++ .../event/product/ProductEventListener.java | 23 +++++++++++++- 12 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/product/EventType.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEvent.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEventPublisher.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEventService.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxEvent.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxEventRepository.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxStatus.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductCoreEventPublisher.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductOutboxEventJpaRepository.java create mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductOutboxEventRepositoryImpl.java diff --git a/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java index 6553cb4d0..4019a8e79 100644 --- a/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java +++ b/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java @@ -2,7 +2,12 @@ import com.loopers.domain.brand.Brand; import com.loopers.domain.brand.BrandService; +import com.loopers.domain.product.EventType; import com.loopers.domain.product.Product; +import com.loopers.domain.product.ProductEvent; +import com.loopers.domain.product.ProductEventPublisher; +import com.loopers.domain.product.ProductEventService; +import com.loopers.domain.product.ProductOutboxEvent; import com.loopers.domain.product.ProductService; import java.time.Duration; import java.util.List; @@ -13,6 +18,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @@ -23,7 +29,9 @@ public class ProductFacade { private final ProductCacheService productCacheService; private final ProductService productService; + private final ProductEventService productEventService; private final BrandService brandService; + private final ProductEventPublisher productEventPublisher; public ProductWithBrandInfo getProductDetail(final Long productId) { ProductWithBrandInfo cached = productCacheService.readDetail(productId); @@ -36,7 +44,7 @@ public ProductWithBrandInfo getProductDetail(final Long productId) { ProductWithBrandInfo result = ProductWithBrandInfo.from(product, brand); productCacheService.createOrUpdateDetail(productId, result, TTL); - + productEventPublisher.publish(ProductEvent.ProductViewed.from(productId)); return result; } @@ -70,4 +78,13 @@ private Map getBrandMapByBrandIds(final Set brandIds) { .collect(Collectors.toMap(Brand::getId, b -> b)); } + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void productViewOutboxHandle(final ProductEvent.ProductViewed event) { + productEventService.saveOutboxEvent(ProductOutboxEvent.create(EventType.VIEWED, event.productId())); + } + + @Transactional + public void productViewPublishKafka(final ProductEvent.ProductViewed event) { + productEventService.findByProductEvent(event); + } } diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/product/EventType.java b/apps/commerce-api/src/main/java/com/loopers/domain/product/EventType.java new file mode 100644 index 000000000..311598fa6 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/domain/product/EventType.java @@ -0,0 +1,13 @@ +package com.loopers.domain.product; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum EventType { + VIEWED("상품 상세 조회"), + ; + + private final String description; +} diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEvent.java b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEvent.java new file mode 100644 index 000000000..98ef92191 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEvent.java @@ -0,0 +1,9 @@ +package com.loopers.domain.product; + +public record ProductEvent() { + public record ProductViewed(Long productId) { + public static ProductViewed from(Long productId) { + return new ProductViewed(productId); + } + } +} diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEventPublisher.java b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEventPublisher.java new file mode 100644 index 000000000..ef2562485 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEventPublisher.java @@ -0,0 +1,5 @@ +package com.loopers.domain.product; + +public interface ProductEventPublisher { + void publish(ProductEvent.ProductViewed event); +} diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEventService.java b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEventService.java new file mode 100644 index 000000000..e9b2d7c38 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductEventService.java @@ -0,0 +1,22 @@ +package com.loopers.domain.product; + +import com.loopers.domain.product.ProductEvent.ProductViewed; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Component +public class ProductEventService { + private final ProductOutboxEventRepository productOutboxEventRepository; + + @Transactional + public ProductOutboxEvent saveOutboxEvent(final ProductOutboxEvent productOutboxEvent) { + return productOutboxEventRepository.save(productOutboxEvent); + } + + public void findByProductEvent(final ProductViewed event) { + productOutboxEventRepository.findBy + } +} diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxEvent.java b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxEvent.java new file mode 100644 index 000000000..5d7482287 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxEvent.java @@ -0,0 +1,31 @@ +package com.loopers.domain.product; + +import com.loopers.domain.BaseEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; +import lombok.Getter; + +@Getter +@Table(name = "product_outbox_events") +@Entity +public class ProductOutboxEvent extends BaseEntity { + + @Enumerated(EnumType.STRING) + private EventType eventType; + + @Enumerated(EnumType.STRING) + private ProductOutboxStatus productOutboxStatus; + + private Long productId; + + public static ProductOutboxEvent create(final EventType eventType, final Long productId) { + ProductOutboxEvent productOutboxEvent = new ProductOutboxEvent(); + productOutboxEvent.eventType = eventType; + productOutboxEvent.productOutboxStatus = ProductOutboxStatus.PENDING; + productOutboxEvent.productId = productId; + return productOutboxEvent; + } +} + diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxEventRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxEventRepository.java new file mode 100644 index 000000000..520a47f0e --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxEventRepository.java @@ -0,0 +1,5 @@ +package com.loopers.domain.product; + +public interface ProductOutboxEventRepository { + ProductOutboxEvent save(ProductOutboxEvent productOutboxEvent); +} diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxStatus.java b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxStatus.java new file mode 100644 index 000000000..9a40ec756 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductOutboxStatus.java @@ -0,0 +1,15 @@ +package com.loopers.domain.product; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ProductOutboxStatus { + PENDING("이벤트 발행 대기 중"), + PUBLISHED("이벤트 발행 완료"), + FAILED("이벤트 발행 실패"), + ; + + private final String description; +} diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductCoreEventPublisher.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductCoreEventPublisher.java new file mode 100644 index 000000000..261180843 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductCoreEventPublisher.java @@ -0,0 +1,20 @@ +package com.loopers.infrastructure.product; + +import com.loopers.domain.product.ProductEvent; +import com.loopers.domain.product.ProductEventPublisher; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class ProductCoreEventPublisher implements ProductEventPublisher { + + private final ApplicationEventPublisher applicationEventPublisher; +// private final KafkaTemplate kafkaTemplate; + + @Override + public void publish(final ProductEvent.ProductViewed event) { + applicationEventPublisher.publishEvent(event); + } +} diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductOutboxEventJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductOutboxEventJpaRepository.java new file mode 100644 index 000000000..1f0cdab88 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductOutboxEventJpaRepository.java @@ -0,0 +1,8 @@ +package com.loopers.infrastructure.product; + +import com.loopers.domain.product.ProductOutboxEvent; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Component; + +public interface ProductOutboxEventJpaRepository extends JpaRepository { +} diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductOutboxEventRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductOutboxEventRepositoryImpl.java new file mode 100644 index 000000000..9035da46c --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductOutboxEventRepositoryImpl.java @@ -0,0 +1,17 @@ +package com.loopers.infrastructure.product; + +import com.loopers.domain.product.ProductOutboxEvent; +import com.loopers.domain.product.ProductOutboxEventRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class ProductOutboxEventRepositoryImpl implements ProductOutboxEventRepository { + private final ProductOutboxEventJpaRepository productOutboxEventJpaRepository; + + @Override + public ProductOutboxEvent save(final ProductOutboxEvent productOutboxEvent) { + return productOutboxEventJpaRepository.save(productOutboxEvent); + } +} diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java index bfbd35140..3de75538c 100644 --- a/apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java +++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java @@ -1,25 +1,46 @@ package com.loopers.interfaces.event.product; +import com.loopers.application.product.ProductFacade; import com.loopers.confg.kafka.KafkaConfig; +import com.loopers.domain.product.ProductEvent; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.context.event.EventListener; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.support.Acknowledgment; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; +@RequiredArgsConstructor @Component public class ProductEventListener { + private final ProductFacade productFacade; + @KafkaListener( topics = {"payment.paid"}, containerFactory = KafkaConfig.BATCH_LISTENER, groupId = "product" ) - public void productPaidListener( + public void handle( List> messages, Acknowledgment acknowledgment ){ // messages.stream() acknowledgment.acknowledge(); } + + @EventListener + public void productViewOutboxHandle(ProductEvent.ProductViewed event) { + productFacade.productViewOutboxHandle(event); + } + + @Async + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void productViewPublishKafka(ProductEvent.ProductViewed event) { + productFacade.productViewPublishKafka(event); + } }