Skip to content

Conversation

@pmh5574
Copy link
Collaborator

@pmh5574 pmh5574 commented Dec 19, 2025

πŸ“Œ Summary

πŸ’¬ Review Points

βœ… Checklist

πŸ“Ž References

Summary by CodeRabbit

릴리슀 λ…ΈνŠΈ

  • New Features
    • 결제 성곡 및 μ‹€νŒ¨ 이벀트 λ°œν–‰ μ‹œμŠ€ν…œ μΆ”κ°€
    • 이벀트 기반 λ©”μ‹œμ§€ 처리 인프라 κ΅¬ν˜„
    • Kafkaλ₯Ό ν†΅ν•œ 비동기 이벀트 흐름 지원

✏️ Tip: You can customize this high-level summary in your review settings.

@pmh5574 pmh5574 requested a review from chapakook December 19, 2025 04:48
@coderabbitai
Copy link

coderabbitai bot commented Dec 19, 2025

μš”μ•½

결제 도메인에 이벀트 기반 μ•„ν‚€ν…μ²˜λ₯Ό λ„μž…ν•˜λŠ” λ³€κ²½μ‚¬ν•­μž…λ‹ˆλ‹€. Kafka λͺ¨λ“ˆμ„ μ˜μ‘΄μ„±μ— μΆ”κ°€ν•˜κ³ , 결제 성곡/μ‹€νŒ¨ 이벀트λ₯Ό μ •μ˜ν•œ ν›„ Kafkaλ₯Ό 톡해 λ°œν–‰ν•©λ‹ˆλ‹€. μ£Όλ¬Έκ³Ό μƒν’ˆ λ„λ©”μΈμ˜ 이벀트 λ¦¬μŠ€λ„ˆκ°€ μ΄λŸ¬ν•œ 이벀트λ₯Ό μ†ŒλΉ„ν•©λ‹ˆλ‹€.

Walkthrough

결제 κ²°κ³Όλ₯Ό Kafka 기반 이벀트둜 λ°œν–‰ν•˜λŠ” κΈ°λŠ₯이 μΆ”κ°€λ©λ‹ˆλ‹€. PaymentEvent와 PaymentEventPublisherκ°€ 도메인 κ³„μΈ΅μ—μ„œ μ •μ˜λ˜κ³ , PaymentCoreEventPublisherκ°€ 이λ₯Ό κ΅¬ν˜„ν•˜μ—¬ Kafka 토픽에 λ©”μ‹œμ§€λ₯Ό λ°œν–‰ν•©λ‹ˆλ‹€. PaymentFacadeκ°€ μˆ˜μ •λ˜μ–΄ 결제 성곡/μ‹€νŒ¨ μ‹œ ν•΄λ‹Ή 이벀트λ₯Ό λ°œν–‰ν•˜λ©°, μ£Όλ¬Έκ³Ό μƒν’ˆ λ¦¬μŠ€λ„ˆκ°€ 이벀트λ₯Ό μ†ŒλΉ„ν•©λ‹ˆλ‹€.

Changes

Cohort / File(s) λ³€κ²½ μš”μ•½
λΉŒλ“œ 및 μ„€μ •
apps/commerce-api/build.gradle.kts, modules/kafka/src/main/resources/kafka.yml
Kafka λͺ¨λ“ˆ μ˜μ‘΄μ„± μΆ”κ°€, Kafka ν”„λ‘œλ“€μ„œ idempotence ν™œμ„±ν™” 및 acks μ„€μ •
도메인 계측 - 이벀트 μ •μ˜
apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEvent.java, apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEventPublisher.java
PaymentPaid 및 PaymentFailed λ ˆμ½”λ“œλ₯Ό 담은 PaymentEvent ν΄λž˜μŠ€μ™€ 이벀트 λ°œν–‰ μΈν„°νŽ˜μ΄μŠ€ 생성
인프라 계측 - 이벀트 λ°œν–‰μž
apps/commerce-api/src/main/java/com/loopers/infrastructure/payment/PaymentCoreEventPublisher.java
PaymentEventPublisher κ΅¬ν˜„μ²΄λ‘œμ„œ KafkaTemplate을 μ‚¬μš©ν•΄ payment.paid 및 payment.failed 토픽에 이벀트 λ°œν–‰
μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 계측
apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentFacade.java
PaymentEventPublisher μ˜μ‘΄μ„± μ£Όμž…, 결제 성곡/μ‹€νŒ¨ μ‹œ ν•΄λ‹Ή 이벀트 λ°œν–‰ 둜직 μΆ”κ°€
μΈν„°νŽ˜μ΄μŠ€ 계측 - 이벀트 λ¦¬μŠ€λ„ˆ
apps/commerce-api/src/main/java/com/loopers/interfaces/event/order/OrderEventListener.java, apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java
payment.paid ν† ν”½μ—μ„œ 배치 λͺ¨λ“œλ‘œ λ©”μ‹œμ§€λ₯Ό μˆ˜μ‹ ν•˜λŠ” Kafka λ¦¬μŠ€λ„ˆ 클래슀 μΆ”κ°€

Sequence Diagram

sequenceDiagram
    participant Client
    participant PaymentFacade
    participant PaymentEventPublisher
    participant KafkaTemplate
    participant Kafka
    participant OrderListener
    participant ProductListener

    Client->>PaymentFacade: processPayment()
    
    alt Payment Success
        PaymentFacade->>PaymentEventPublisher: publish(PaymentPaid)
        PaymentEventPublisher->>KafkaTemplate: send(payment.paid topic)
        KafkaTemplate->>Kafka: publish message
        
        Kafka->>OrderListener: consume message (batch)
        Kafka->>ProductListener: consume message (batch)
        
        OrderListener->>OrderListener: process & acknowledge
        ProductListener->>ProductListener: process & acknowledge
    else Payment Failed
        PaymentFacade->>PaymentEventPublisher: publish(PaymentFailed)
        PaymentEventPublisher->>KafkaTemplate: send(payment.failed topic)
        KafkaTemplate->>Kafka: publish message
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • 주의 ν•„μš” μ˜μ—­:
    • PaymentFacade μˆ˜μ •: μƒμ„±μž μ„œλͺ… λ³€κ²½μœΌλ‘œ μΈν•œ μ˜μ‘΄μ„± μ£Όμž… 확인 ν•„μš”
    • 이벀트 λ¦¬μŠ€λ„ˆ κ΅¬ν˜„: ν˜„μž¬ λ©”μ‹œμ§€λ₯Ό μ²˜λ¦¬ν•˜μ§€ μ•Šκ³  μ¦‰μ‹œ acknowledgeν•˜λ―€λ‘œ μ‹€μ œ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 κ΅¬ν˜„ ν•„μš” μ—¬λΆ€ κ²€ν† 
    • Kafka ν† ν”½λͺ… 일관성: payment.paid/payment.failed ν† ν”½λͺ…이 λͺ¨λ“  κ΅¬μ„±μš”μ†Œμ—μ„œ λ™μΌν•˜κ²Œ μ‚¬μš©λ˜λŠ”μ§€ 확인
    • 배치 λ¦¬μŠ€λ„ˆ μ„€μ •: KafkaConfig.BATCH_LISTENER μ„€μ •μ˜ μ μ ˆμ„± κ²€ν† 

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 결제의 성곡이 이제 울렀 퍼지고,
μΉ΄ν”„μΉ΄μ˜ 흐름을 따라 ν˜λŸ¬κ°€λ„€.
μ£Όλ¬Έκ³Ό μƒν’ˆμ΄ κ·€λ₯Ό μ«‘κΈ‹μ„Έμš°λ©°,
이벀트의 좀을 ν•¨κ»˜ μΆ”λ‚˜λ‹ˆ,
λ©”μ‹œμ§€ ν•œ 점이 세상을 μž‡λŠ”κ΅¬λ‚˜! ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning PR μ„€λͺ…은 ν…œν”Œλ¦Ώ ꡬ쑰λ₯Ό λ”°λ₯΄κ³  μžˆμ§€λ§Œ, λͺ¨λ“  ν•„μˆ˜ μ„Ήμ…˜(Summary, Review Points, Checklist, References)이 λ‚΄μš© 없이 λΉ„μ–΄μžˆμŠ΅λ‹ˆλ‹€. Summary μ„Ήμ…˜μ— λ³€κ²½ 사항 μš”μ•½, Review Points에 ꡬ체적인 κ²€ν†  포인트, Checklist에 μ™„λ£Œ ν•­λͺ© 체크, References에 κ΄€λ ¨ λ¬Έμ„œλ₯Ό μΆ”κ°€ν•΄μ£Όμ„Έμš”.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
βœ… Passed checks (1 passed)
Check name Status Explanation
Title check βœ… Passed PR 제λͺ©μ€ μ£Όμš” λ³€κ²½ 사항인 'Kafkaλ₯Ό ν†΅ν•œ λ””μ»€ν”Œλ§'을 λͺ…ν™•ν•˜κ²Œ μš”μ•½ν•˜κ³  있으며, λ³€κ²½ 사항 전체와 μ§μ ‘μ μœΌλ‘œ 관련이 μžˆμŠ΅λ‹ˆλ‹€.
✨ Finishing touches
  • πŸ“ Generate docstrings
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEventPublisher.java (2)

3-3: import μŠ€νƒ€μΌμ˜ 일관성을 κ°œμ„ ν•˜μ„Έμš”.

PaymentPaidλŠ” λͺ…μ‹œμ μœΌλ‘œ importλ˜μ–΄ μ‚¬μš©λ˜μ§€λ§Œ, PaymentFailedλŠ” μ™„μ „ν•œ νŒ¨ν‚€μ§€ 경둜둜 μ°Έμ‘°λ©λ‹ˆλ‹€. 일관성을 μœ„ν•΄ 두 이벀트 νƒ€μž… λͺ¨λ‘ λ™μΌν•œ λ°©μ‹μœΌλ‘œ importν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.

πŸ”Ž μˆ˜μ • μ œμ•ˆ
 package com.loopers.domain.payment;
 
 import com.loopers.domain.payment.PaymentEvent.PaymentPaid;
+import com.loopers.domain.payment.PaymentEvent.PaymentFailed;
 
 public interface PaymentEventPublisher {
     void publish(PaymentPaid paymentCreated);
-    void publish(PaymentEvent.PaymentFailed paymentFailed);
+    void publish(PaymentFailed paymentFailed);
 }

Also applies to: 7-7


6-6: λ©”μ„œλ“œ νŒŒλΌλ―Έν„° 이름을 μˆ˜μ •ν•˜μ„Έμš”.

첫 번째 publish λ©”μ„œλ“œμ˜ νŒŒλΌλ―Έν„° 이름이 paymentCreated인데, μ‹€μ œλ‘œλŠ” PaymentPaid νƒ€μž…μ„ λ°›μŠ΅λ‹ˆλ‹€. νŒŒλΌλ―Έν„° 이름을 paymentPaid둜 λ³€κ²½ν•˜μ—¬ νƒ€μž…κ³Ό μΌμΉ˜μ‹œν‚€λŠ” 것이 λͺ…ν™•ν•©λ‹ˆλ‹€.

πŸ”Ž μˆ˜μ • μ œμ•ˆ
-    void publish(PaymentPaid paymentCreated);
+    void publish(PaymentPaid paymentPaid);
apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java (1)

13-24: 이벀트 처리 둜직 κ΅¬ν˜„μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

λ¦¬μŠ€λ„ˆκ°€ 배치 λͺ¨λ“œλ‘œ μ˜¬λ°”λ₯΄κ²Œ μ„€μ •λ˜μ—ˆμ§€λ§Œ, λ©”μ‹œμ§€ 처리 둜직이 주석 μ²˜λ¦¬λ˜μ–΄ μ¦‰μ‹œ acknowledgment만 μˆ˜ν–‰ν•©λ‹ˆλ‹€. 결제 μ™„λ£Œ μ΄λ²€νŠΈμ— λŒ€ν•œ μƒν’ˆ κ΄€λ ¨ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직(예: 재고 μ—…λ°μ΄νŠΈ, 톡계 집계 λ“±)을 κ΅¬ν˜„ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Based on learnings, 이 μ½”λ“œλ² μ΄μŠ€μ—μ„œλŠ” Kafka μ»¨μŠˆλ¨Έκ°€ μ—λŸ¬ 처리λ₯Ό EventInboxAspectλ₯Ό 톡해 μ„œλΉ„μŠ€ λ ˆμ΄μ–΄μ— μœ„μž„ν•˜λ―€λ‘œ, μ„œλΉ„μŠ€ λ©”μ„œλ“œμ— @InboxEvent μ• λ…Έν…Œμ΄μ…˜μ„ μΆ”κ°€ν•˜μ—¬ μ‹€νŒ¨ μ‹œ μžλ™μœΌλ‘œ μ²˜λ¦¬λ˜λ„λ‘ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

이벀트 처리 둜직 κ΅¬ν˜„μ„ λ„μ™€λ“œλ¦΄κΉŒμš”? λ˜λŠ” 이 μž‘μ—…μ„ 좔적할 이슈λ₯Ό μƒμ„±ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?

apps/commerce-api/src/main/java/com/loopers/interfaces/event/order/OrderEventListener.java (1)

13-24: μ£Όλ¬Έ 이벀트 처리 둜직 κ΅¬ν˜„μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

ProductEventListener와 λ™μΌν•œ νŒ¨ν„΄μœΌλ‘œ, λ©”μ‹œμ§€ 처리 둜직이 주석 μ²˜λ¦¬λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. 결제 μ™„λ£Œ μ΄λ²€νŠΈμ— λŒ€ν•œ μ£Όλ¬Έ κ΄€λ ¨ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직(예: μ£Όλ¬Έ μƒνƒœ μ—…λ°μ΄νŠΈ, μ•Œλ¦Ό λ°œμ†‘ λ“±)을 κ΅¬ν˜„ν•΄μ•Ό ν•©λ‹ˆλ‹€.

payment.paid 토픽을 κ΅¬λ…ν•˜λŠ” μ—¬λŸ¬ 컨슈머 κ·Έλ£Ή(order, product)이 μ‘΄μž¬ν•˜μ—¬ νŒ¬μ•„μ›ƒ νŒ¨ν„΄μ„ μ˜¬λ°”λ₯΄κ²Œ κ΅¬ν˜„ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

Based on learnings, EventInboxAspectλ₯Ό ν™œμš©ν•œ μ—λŸ¬ 처리λ₯Ό μœ„ν•΄ μ„œλΉ„μŠ€ λ©”μ„œλ“œμ— @InboxEvent μ• λ…Έν…Œμ΄μ…˜μ„ μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

apps/commerce-api/src/main/java/com/loopers/infrastructure/payment/PaymentCoreEventPublisher.java (1)

14-15: ν† ν”½ 이름을 μ„€μ • 파일둜 μ™ΈλΆ€ν™”ν•˜μ„Έμš”.

ν•˜λ“œμ½”λ”©λœ ν† ν”½ 이름은 ν™˜κ²½λ³„λ‘œ λ‹€λ₯Έ 토픽을 μ‚¬μš©ν•˜κ±°λ‚˜ ν† ν”½ 이름을 λ³€κ²½ν•  λ•Œ μ½”λ“œ μˆ˜μ •μ΄ ν•„μš”ν•©λ‹ˆλ‹€. application.yml λ˜λŠ” λ³„λ„μ˜ μ„€μ • 클래슀둜 μ™ΈλΆ€ν™”ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.

πŸ”Ž μ„€μ • μ™ΈλΆ€ν™” μ˜ˆμ‹œ

application.yml에 μΆ”κ°€:

payment:
  kafka:
    topics:
      paid: payment.paid
      failed: payment.failed

μ½”λ“œ μˆ˜μ •:

+@Value("${payment.kafka.topics.paid}")
-private static final String paidTopic = "payment.paid";
+private final String paidTopic;

+@Value("${payment.kafka.topics.failed}")
-private static final String failedTopic = "payment.failed";
+private final String failedTopic;
πŸ“œ Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 5968dc0 and 6b1c348.

πŸ“’ Files selected for processing (8)
  • apps/commerce-api/build.gradle.kts (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentFacade.java (4 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEvent.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEventPublisher.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/payment/PaymentCoreEventPublisher.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/interfaces/event/order/OrderEventListener.java (1 hunks)
  • apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java (1 hunks)
  • modules/kafka/src/main/resources/kafka.yml (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
πŸ““ Common learnings
Learnt from: HongChangMo
Repo: Loopers-dev-lab/loopers-spring-java-template PR: 156
File: apps/pg-simulator/src/main/kotlin/com/loopers/interfaces/api/payment/PaymentApi.kt:51-59
Timestamp: 2025-12-05T04:37:02.942Z
Learning: User HongChangMo prefers to skip detailed code reviews for the pg-simulator module (apps/pg-simulator/**) in the loopers-spring-java-template repository.
πŸ“š Learning: 2025-12-18T13:24:54.339Z
Learnt from: kilian-develop
Repo: Loopers-dev-lab/loopers-spring-java-template PR: 190
File: apps/commerce-streamer/src/main/java/com/loopers/applications/streamer/consumer/product/IncreaseProductViewKafkaConsumer.java:25-35
Timestamp: 2025-12-18T13:24:54.339Z
Learning: In this codebase, Kafka consumers delegate error handling and event tracking to the service layer via EventInboxAspect. Service methods annotated with InboxEvent are intercepted by the aspect, which handles failures by saving failed EventInbox entries and logging errors. This centralized approach avoids duplicating error handling logic across multiple consumers.

Applied to files:

  • apps/commerce-api/src/main/java/com/loopers/interfaces/event/order/OrderEventListener.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java
πŸ“š Learning: 2025-12-18T01:01:55.894Z
Learnt from: jikimee64
Repo: Loopers-dev-lab/loopers-spring-kotlin-template PR: 65
File: modules/kafka/src/main/resources/kafka.yml:25-32
Timestamp: 2025-12-18T01:01:55.894Z
Learning: In Spring Boot Kafka configuration YAML, properties inside spring.kafka.producer.properties and spring.kafka.consumer.properties maps must use exact Kafka client property names with dot notation, and must be quoted with bracket notation like "[enable.idempotence]": true and "[enable.auto.commit]": false to prevent YAML from parsing dots as nested keys. Spring Boot's relaxed binding only applies to top-level Spring Kafka properties, not to the properties map.

Applied to files:

  • modules/kafka/src/main/resources/kafka.yml
πŸ“š Learning: 2025-12-07T12:45:45.556Z
Learnt from: toongri
Repo: Loopers-dev-lab/loopers-spring-kotlin-template PR: 52
File: apps/commerce-api/src/main/kotlin/com/loopers/domain/payment/Payment.kt:160-181
Timestamp: 2025-12-07T12:45:45.556Z
Learning: In the Payment.confirmPayment method in com.loopers.domain.payment.Payment, the when-branch order is intentionally designed with business logic checks before timeout: (1) check SUCCESS/FAILED status first, (2) check for null match (business failure), (3) check timeout only as last resort for PENDING transactions. If matched transaction is PENDING and not timed out, no branch matches and payment stays IN_PROGRESS for scheduler retry. This design prioritizes business outcome determination over time-based fallback.

Applied to files:

  • apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentFacade.java
πŸ“š Learning: 2025-11-30T15:44:28.336Z
Learnt from: ghojeong
Repo: Loopers-dev-lab/loopers-spring-kotlin-template PR: 48
File: apps/commerce-api/src/main/kotlin/com/loopers/application/order/OrderFacade.kt:80-83
Timestamp: 2025-11-30T15:44:28.336Z
Learning: User ghojeong prefers explicit state management over transaction rollback for handling payment failures in order creation flow. When payment fails after order creation, the order should be explicitly marked as cancelled and persisted, rather than relying on transaction rollback.

Applied to files:

  • apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentFacade.java
🧬 Code graph analysis (5)
apps/commerce-api/src/main/java/com/loopers/interfaces/event/order/OrderEventListener.java (1)
apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java (1)
  • Component (10-25)
apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentFacade.java (1)
apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEvent.java (1)
  • PaymentEvent (3-14)
apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java (1)
apps/commerce-api/src/main/java/com/loopers/interfaces/event/order/OrderEventListener.java (1)
  • Component (10-25)
apps/commerce-api/src/main/java/com/loopers/infrastructure/payment/PaymentCoreEventPublisher.java (3)
apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEvent.java (1)
  • PaymentEvent (3-14)
apps/commerce-api/src/main/java/com/loopers/interfaces/event/order/OrderEventListener.java (1)
  • Component (10-25)
apps/commerce-api/src/main/java/com/loopers/interfaces/event/product/ProductEventListener.java (1)
  • Component (10-25)
apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEventPublisher.java (1)
apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEvent.java (1)
  • PaymentEvent (3-14)
πŸ”‡ Additional comments (6)
modules/kafka/src/main/resources/kafka.yml (1)

10-10: μ‹ λ’°μ„± μžˆλŠ” Kafka 섀정이 μ μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

acks: allκ³Ό idempotence μ„€μ •(λŒ€κ΄„ν˜Έ ν‘œκΈ°λ²• μˆ˜μ • ν›„)의 쑰합은 λ©”μ‹œμ§€ 손싀을 λ°©μ§€ν•˜κ³  쀑볡 전솑을 λ°©μ§€ν•˜λŠ” 쒋은 κ΅¬μ„±μž…λ‹ˆλ‹€. μžλ™ ν† ν”½ 생성이 ν™œμ„±ν™”λ˜μ–΄ μžˆμœΌλ―€λ‘œ ν”„λ‘œλ•μ…˜ ν™˜κ²½μ—μ„œλŠ” 토픽을 사전에 μƒμ„±ν•˜λŠ” 것이 ꢌμž₯λ©λ‹ˆλ‹€.

Also applies to: 18-20

apps/commerce-api/build.gradle.kts (1)

5-5: Kafka λͺ¨λ“ˆ μ˜μ‘΄μ„±μ΄ μ˜¬λ°”λ₯΄κ²Œ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

commerce-apiμ—μ„œ Kafka 기반 이벀트 λ°œν–‰ κΈ°λŠ₯을 μ‚¬μš©ν•  수 μžˆλ„λ‘ μ μ ˆν•˜κ²Œ μ˜μ‘΄μ„±μ΄ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentFacade.java (2)

93-93: 이벀트 λ°œν–‰ μ‹€νŒ¨ μ‹œ νŠΈλžœμž­μ…˜ 처리λ₯Ό κ²€ν† ν•˜μ„Έμš”.

이벀트 λ°œν–‰μ΄ @Transactional λ©”μ„œλ“œ λ‚΄μ—μ„œ λ™κΈ°μ μœΌλ‘œ μˆ˜ν–‰λ©λ‹ˆλ‹€. Kafkaκ°€ μ‚¬μš© λΆˆκ°€λŠ₯ν•˜κ±°λ‚˜ λ°œν–‰μ— μ‹€νŒ¨ν•˜λ©΄ 전체 결제 νŠΈλžœμž­μ…˜μ΄ 둀백될 수 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒ 사항듀을 κ³ λ €ν•΄μ•Ό ν•©λ‹ˆλ‹€:

  • 이벀트 λ°œν–‰ μ‹€νŒ¨κ°€ 결제 νŠΈλžœμž­μ…˜μ„ λ‘€λ°±ν•΄μ•Ό ν•˜λŠ”μ§€ 확인
  • 이벀트 λ°œν–‰μ„ λΉ„λ™κΈ°λ‘œ μ²˜λ¦¬ν•˜κ±°λ‚˜ νŠΈλžœμž­μ…˜ 컀밋 ν›„ λ°œν–‰ν•˜λŠ” λ°©μ•ˆ κ²€ν† 
  • KafkaTemplate.send()의 μ—λŸ¬ 처리 μΆ”κ°€

Based on learnings, 결제 μ‹€νŒ¨ μ‹œ λͺ…μ‹œμ μΈ μƒνƒœ 관리λ₯Ό μ„ ν˜Έν•˜λ―€λ‘œ, 이벀트 λ°œν–‰ μ‹€νŒ¨ μ‹œμ—λ„ 결제 μƒνƒœλŠ” μœ μ§€λ˜μ–΄μ•Ό ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


122-122: λ™μΌν•œ 이벀트 λ°œν–‰ νŒ¨ν„΄μ΄ μ μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

결제 μ‹€νŒ¨ μ‹œμ—λ„ 이벀트 λ°œν–‰μ΄ λ™κΈ°μ μœΌλ‘œ μˆ˜ν–‰λ©λ‹ˆλ‹€. Line 93의 성곡 μΌ€μ΄μŠ€μ™€ λ™μΌν•œ νŠΈλžœμž­μ…˜ 및 μ—λŸ¬ 처리 고렀사항이 μ μš©λ©λ‹ˆλ‹€.

apps/commerce-api/src/main/java/com/loopers/domain/payment/PaymentEvent.java (1)

3-14: 도메인 이벀트 λͺ¨λΈμ΄ 잘 μ„€κ³„λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

Java recordλ₯Ό ν™œμš©ν•˜μ—¬ λΆˆλ³€ 이벀트 νƒ€μž…μ„ μ •μ˜ν•˜κ³ , 각 μ΄λ²€νŠΈμ— λŒ€ν•œ νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•˜λŠ” κΉ”λ”ν•œ κ΅¬ν˜„μž…λ‹ˆλ‹€. Payment μ—”ν‹°ν‹°λ‘œλΆ€ν„° 이벀트λ₯Ό μΌκ΄€λ˜κ²Œ 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.

apps/commerce-api/src/main/java/com/loopers/infrastructure/payment/PaymentCoreEventPublisher.java (1)

20-22: 이벀트 λ°œν–‰ μ‹€νŒ¨μ— λŒ€ν•œ μ—λŸ¬ 처리λ₯Ό μΆ”κ°€ν•˜μ„Έμš”.

kafkaTemplate.send()의 λ°˜ν™˜κ°’(CompletableFuture)을 λ¬΄μ‹œν•˜κ³  μžˆμ–΄ λ°œν–‰ μ‹€νŒ¨λ₯Ό 감지할 수 μ—†μŠ΅λ‹ˆλ‹€. 이벀트 λ°œν–‰ μ‹€νŒ¨ μ‹œ λ‘œκΉ…, μž¬μ‹œλ„, λ˜λŠ” λŒ€μ²΄ μ²˜λ¦¬κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

πŸ”Ž μ—λŸ¬ 처리 μΆ”κ°€ μ˜ˆμ‹œ
 @Override
 public void publish(final PaymentPaid paymentCreated) {
-    kafkaTemplate.send(paidTopic, paymentCreated.payment().getId(), paymentCreated);
+    kafkaTemplate.send(paidTopic, paymentCreated.payment().getId(), paymentCreated)
+        .whenComplete((result, ex) -> {
+            if (ex != null) {
+                log.error("Failed to publish PaymentPaid event for payment ID: {}", 
+                    paymentCreated.payment().getId(), ex);
+                // Consider: throw exception, retry, or save to dead letter queue
+            } else {
+                log.info("Successfully published PaymentPaid event for payment ID: {}", 
+                    paymentCreated.payment().getId());
+            }
+        });
 }

Also applies to: 25-27

β›” Skipped due to learnings
Learnt from: kilian-develop
Repo: Loopers-dev-lab/loopers-spring-java-template PR: 190
File: apps/commerce-streamer/src/main/java/com/loopers/applications/streamer/consumer/product/IncreaseProductViewKafkaConsumer.java:25-35
Timestamp: 2025-12-18T13:24:54.339Z
Learning: In this codebase, Kafka consumers delegate error handling and event tracking to the service layer via EventInboxAspect. Service methods annotated with InboxEvent are intercepted by the aspect, which handles failures by saving failed EventInbox entries and logging errors. This centralized approach avoids duplicating error handling logic across multiple consumers.

Comment on lines +19 to +20
properties:
enable.idempotence: true
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Kafka ν”„λ‘œλ“€μ„œ μ†μ„±μ˜ ν‘œκΈ°λ²•μ„ μˆ˜μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Spring Boot의 spring.kafka.producer.properties λ§΅ λ‚΄λΆ€μ—μ„œλŠ” 점(dot)을 ν¬ν•¨ν•œ Kafka ν΄λΌμ΄μ–ΈνŠΈ 속성 이름을 λŒ€κ΄„ν˜Έ ν‘œκΈ°λ²•μœΌλ‘œ μž‘μ„±ν•΄μ•Ό ν•©λ‹ˆλ‹€. ν˜„μž¬ enable.idempotence: trueλŠ” YAML νŒŒμ„œκ°€ μ€‘μ²©λœ ν‚€λ‘œ 해석할 수 μžˆμŠ΅λ‹ˆλ‹€.

Based on learnings, Kafka ν΄λΌμ΄μ–ΈνŠΈ 속성은 μ •ν™•ν•œ μ΄λ¦„μœΌλ‘œ μΈμ‹λ˜λ„λ‘ λŒ€κ΄„ν˜Έλ‘œ 감싸야 ν•©λ‹ˆλ‹€.

πŸ”Ž μˆ˜μ • μ œμ•ˆ
     producer:
       key-serializer: org.apache.kafka.common.serialization.StringSerializer
       value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
       retries: 3
       acks: all
       properties:
-        enable.idempotence: true
+        "[enable.idempotence]": true
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
properties:
enable.idempotence: true
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
retries: 3
acks: all
properties:
"[enable.idempotence]": true
πŸ€– Prompt for AI Agents
In modules/kafka/src/main/resources/kafka.yml around lines 19 to 20, the Kafka
producer property key is written as enable.idempotence which YAML may parse as a
nested key; update the key to use Spring Boot's bracket notation for Kafka
client properties (i.e., wrap the full dotted property name in brackets and
quotes as the map key) so the client receives the correct "enable.idempotence"
setting and not a nested YAML structure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant