-
Notifications
You must be signed in to change notification settings - Fork 34
[volume - 8] Decoupling with Kafka #194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: pmh5574
Are you sure you want to change the base?
Conversation
μμ½κ²°μ λλ©μΈμ μ΄λ²€νΈ κΈ°λ° μν€ν μ²λ₯Ό λμ νλ λ³κ²½μ¬νμ λλ€. Kafka λͺ¨λμ μμ‘΄μ±μ μΆκ°νκ³ , κ²°μ μ±κ³΅/μ€ν¨ μ΄λ²€νΈλ₯Ό μ μν ν Kafkaλ₯Ό ν΅ν΄ λ°νν©λλ€. μ£Όλ¬Έκ³Ό μν λλ©μΈμ μ΄λ²€νΈ 리μ€λκ° μ΄λ¬ν μ΄λ²€νΈλ₯Ό μλΉν©λλ€. Walkthroughκ²°μ κ²°κ³Όλ₯Ό Kafka κΈ°λ° μ΄λ²€νΈλ‘ λ°ννλ κΈ°λ₯μ΄ μΆκ°λ©λλ€. PaymentEventμ PaymentEventPublisherκ° λλ©μΈ κ³μΈ΅μμ μ μλκ³ , PaymentCoreEventPublisherκ° μ΄λ₯Ό ꡬννμ¬ Kafka ν ν½μ λ©μμ§λ₯Ό λ°νν©λλ€. PaymentFacadeκ° μμ λμ΄ κ²°μ μ±κ³΅/μ€ν¨ μ ν΄λΉ μ΄λ²€νΈλ₯Ό λ°ννλ©°, μ£Όλ¬Έκ³Ό μν 리μ€λκ° μ΄λ²€νΈλ₯Ό μλΉν©λλ€. Changes
Sequence DiagramsequenceDiagram
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
Estimated code review effortπ― 3 (Moderate) | β±οΈ ~25 minutes
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touchesβ Failed checks (2 warnings)
β Passed checks (1 passed)
β¨ Finishing touches
π§ͺ Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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
π 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.javaapps/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.
| properties: | ||
| enable.idempotence: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
π Summary
π¬ Review Points
β Checklist
π References
Summary by CodeRabbit
λ¦΄λ¦¬μ€ λ ΈνΈ
βοΈ Tip: You can customize this high-level summary in your review settings.