Skip to content

[DABOM-447] usage-events 정산 단순화 및 알림 outbox 도입#55

Merged
ChoiSeungeon merged 8 commits into
developfrom
feat/DABOM-447
Mar 17, 2026
Merged

[DABOM-447] usage-events 정산 단순화 및 알림 outbox 도입#55
ChoiSeungeon merged 8 commits into
developfrom
feat/DABOM-447

Conversation

@ChoiSeungeon
Copy link
Copy Markdown
Contributor

🍀 이슈 & 티켓 넘버


🎯 목적

usage-events 처리 경로에서 Kafka 사용으로 인해 생기는 멱등성, 정합성, 실패 복구 문제를 서비스 내부에서 더 명확하게 통제하기 위해 구조를 정리했습니다.

기존에는 usage-persist를 통한 간접 정산 구조와 notification 발행 책임 분리가 모호했고, 실패 시 어느 지점부터 어떻게 복구되는지 설명하기 어려웠습니다. 이번 변경에서는 DB 정산을 usage-events 소비 서비스가 직접 수행하도록 단순화하고, notification은 Outbox + 즉시 비동기 발행 + 배치 복구 구조로 재정리했습니다.

📝 변경 사항

  • usage-persist producer/consumer 경로를 제거하고 usage-events 처리 중 DB 직접 정산으로 변경
  • usage_event_outbox 엔티티/리포지토리/서비스 추가
  • notification 발행을 즉시 비동기 발행 + PUBLISH_PENDING 복구 구조로 변경
  • Lua 상태 문자열 해석을 중앙 매퍼로 통합하고 알 수 없는 상태는 즉시 실패하도록 변경
  • familyId-customerId 관계를 Redis membership cache + DB fallback으로 초입 검증하도록 추가
  • usage alert/block dedup key를 family:{familyId}:customer:{customerId}:alert:... 형식으로 정리
  • NORMAL 등 알림 비대상 이벤트는 payload 생성 없이 SKIPPED로 종료되도록 수정
  • usage 관련 단위 테스트 전반 갱신

📂 변경 범위

도메인 controller service repository entity infra global
usage / policy [x] [x] [x] [x] [x]

🖥️ 주요 코드 설명

// UsageSyncServiceImpl 핵심 흐름
public void syncUsage(String eventId, String eventTime, UsagePayload payload) {
    validateFamilyMembership(eventId, familyId, customerId);
    usageEventOutboxService.ensurePrepared(eventId, familyId, customerId);

    UsageUpdateResult parsed = usageLuaExecutor.execute(...);

    UsageProcessingDecision decision =
            usageProcessingDecisionMapper.fromLuaStatus(parsed.status());

    boolean publishNotification = decision.publishNotification() && parsed.shouldNotify();

    if (!parsed.duplicate() || hasPreparedRow) {
        usagePersistService.persistFromUsageEvent(
                eventId, eventTime, payload, decision.persistProcessResult());
    }

    NotificationPayload notificationPayload =
            publishNotification
                    ? usageNotificationPayloadMapper.toNotificationPayload(...)
                    : null;

    usageEventOutboxService
            .stageAfterRedisApplied(eventId, notificationPayload, publishNotification)
            .ifPresent(this::publishAsync);
}
  • membership 검증을 Redis/DB 기준으로 초입에서 수행해 잘못된 familyId-customerId 조합이 Redis/Lua/알림까지 내려가지 않도록 했습니다.
  • DB 정산은 usage_record unique를 기준으로 멱등하게 처리되며, duplicate 재처리 시에도 안전하게 재진입할 수 있습니다.
  • notification은 Outbox에 PUBLISH_PENDING으로 남긴 뒤 즉시 비동기 발행을 시도하고, 실패 시 배치 서버가 복구 발행합니다.

💬 리뷰어에게

  • usage-events 처리 책임을 직접 정산 구조로 단순화한 점과 notification Outbox 상태 전이(PREPARED -> PUBLISH_PENDING/SKIPPED -> SENT/FAILED)가 의도대로 보이는지 중점적으로 봐주시면 좋겠습니다.
  • membership cache 검증을 초입으로 올린 선택과 duplicate 재처리 흐름이 현재 트래픽/운영 방식에 맞는지도 같이 봐주시면 좋겠습니다.

📋 체크리스트

기본

  • Merge 대상 브랜치가 올바른가?
  • ./gradlew build가 정상적으로 통과하는가?
  • Spotless / Checkstyle을 통과하는가? (./gradlew spotlessApply checkstyleMain)
  • 전체 변경사항이 500줄을 넘지 않는가?

코드 품질

  • 의존성 방향을 준수하는가? (Controller → Service → Repository → Entity)
  • Setter 없이 비즈니스 메서드로 상태를 변경하는가?
  • @Transactional은 Service에만 선언했는가?

테스트

  • 신규 비즈니스 로직에 대한 단위 테스트를 작성했는가?

📌 참고 사항

@ChoiSeungeon ChoiSeungeon self-assigned this Mar 16, 2026
@github-actions github-actions Bot added size/XL PR size: XL feat refactor 리팩토링 labels Mar 16, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the usage-events processing pipeline to improve data consistency, idempotency, and failure recovery. It simplifies the accounting structure by removing the indirect usage-persist approach and enabling direct DB accounting within the usage-events consumption service. Additionally, it introduces an Outbox pattern for notifications to ensure reliable delivery through asynchronous publishing and batch recovery mechanisms.

Highlights

  • 정산 로직 단순화: usage-persist producer/consumer 경로를 제거하고 usage-events 처리 중 DB 직접 정산으로 변경하여 멱등성, 정합성, 실패 복구 문제를 서비스 내부에서 통제합니다.
  • 알림 Outbox 도입: notification 발행을 즉시 비동기 발행 + PUBLISH_PENDING 복구 구조로 변경하여 알림 전송의 안정성을 높입니다.
  • Redis 멤버십 캐시 검증 추가: familyId-customerId 관계를 Redis membership cache + DB fallback으로 초입 검증하도록 추가하여 잘못된 조합이 Redis/Lua/알림까지 전파되는 것을 방지합니다.
  • Lua 상태 문자열 해석 중앙화: Lua 상태 문자열 해석을 중앙 매퍼로 통합하고 알 수 없는 상태는 즉시 실패하도록 변경하여 DB 정산과 알림 판단을 일치시킵니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • build.gradle
    • Updated lib-kafka dependency version to v1.0.0.
  • src/main/java/com/project/domain/usage/entity/UsageEventOutbox.java
    • Added UsageEventOutbox entity for managing notification statuses.
  • src/main/java/com/project/domain/usage/enums/UsageOutboxStatus.java
    • Added UsageOutboxStatus enum to represent the state of a usage event in the outbox.
  • src/main/java/com/project/domain/usage/enums/UsagePersistProcessResult.java
    • Added APP_BLOCK to UsagePersistProcessResult enum.
  • src/main/java/com/project/domain/usage/infra/messaging/UsagePersistKafkaConsumer.java
    • Removed UsagePersistKafkaConsumer as the persist logic is now handled directly.
  • src/main/java/com/project/domain/usage/repository/UsageEventOutboxRepository.java
    • Added UsageEventOutboxRepository for database interactions related to the UsageEventOutbox entity.
  • src/main/java/com/project/domain/usage/service/UsagePersistService.java
    • Modified UsagePersistService to use UsagePayload directly instead of Kafka event envelope.
  • src/main/java/com/project/domain/usage/service/UsagePersistServiceImpl.java
    • Modified UsagePersistServiceImpl to directly persist usage events and validate family membership.
  • src/main/java/com/project/domain/usage/service/UsageSyncServiceImpl.java
    • Modified UsageSyncServiceImpl to handle usage events, manage Redis interactions, and publish notifications asynchronously.
  • src/main/java/com/project/domain/usage/service/dto/UsagePersistPayload.java
    • Added UsagePersistPayload DTO for transferring data to the persistence layer.
  • src/main/java/com/project/domain/usage/service/dto/UsageUpdateResult.java
    • Modified UsageUpdateResult to include a flag indicating whether a notification should be sent.
  • src/main/java/com/project/domain/usage/service/helper/CustomerQuotaWriter.java
    • Modified CustomerQuotaWriter to use UsagePersistPayload DTO.
  • src/main/java/com/project/domain/usage/service/helper/UsageEventOutboxService.java
    • Added UsageEventOutboxService to manage the state transitions and publishing of usage event notifications.
  • src/main/java/com/project/domain/usage/service/helper/UsageEventPublisher.java
    • Removed UsageEventPublisher as notification publishing is now handled via the outbox pattern.
  • src/main/java/com/project/domain/usage/service/helper/UsageEventValidator.java
    • Modified UsageEventValidator to improve validation of usage event payloads.
  • src/main/java/com/project/domain/usage/service/helper/UsageFamilyMembershipCacheHelper.java
    • Added UsageFamilyMembershipCacheHelper to validate family-customer relationships using Redis and DB fallback.
  • src/main/java/com/project/domain/usage/service/helper/UsageLuaExecutor.java
    • Modified UsageLuaExecutor to handle new Lua script results and improve error handling.
  • src/main/java/com/project/domain/usage/service/helper/UsageNotificationPayloadMapper.java
    • Added UsageNotificationPayloadMapper to create notification payloads based on usage event data.
  • src/main/java/com/project/domain/usage/service/helper/UsageNotificationPublisher.java
    • Added UsageNotificationPublisher to asynchronously publish notifications to Kafka.
  • src/main/java/com/project/domain/usage/service/helper/UsagePersistEventValidator.java
    • Modified UsagePersistEventValidator to use UsagePersistPayload DTO.
  • src/main/java/com/project/domain/usage/service/helper/UsageProcessingDecisionMapper.java
    • Added UsageProcessingDecisionMapper to map Lua status strings to processing decisions.
  • src/main/java/com/project/domain/usage/service/helper/UsageRecordWriter.java
    • Modified UsageRecordWriter to use UsagePersistPayload DTO.
  • src/main/java/com/project/global/util/RedisKeyGenerator.java
    • Modified RedisKeyGenerator to generate new keys for alerts and family membership.
  • src/main/resources/lua/usage_update.lua
    • Modified Lua script to handle new alert keys, improve input validation, and cache results.
  • src/test/java/com/project/domain/policy/service/PolicyConstraintSyncServiceImplTest.java
    • Modified PolicyConstraintSyncServiceImplTest to align with updated event envelope structure.
  • src/test/java/com/project/domain/usage/service/UsageEventValidatorTest.java
    • Modified UsageEventValidatorTest to improve validation of usage event payloads.
  • src/test/java/com/project/domain/usage/service/UsagePersistServiceImplTest.java
    • Modified UsagePersistServiceImplTest to align with updated service methods and DTOs.
  • src/test/java/com/project/domain/usage/service/UsageSyncServiceImplTest.java
    • Modified UsageSyncServiceImplTest to align with updated service methods, outbox pattern, and exception handling.
  • src/test/java/com/project/domain/usage/service/helper/UsageEventOutboxServiceTest.java
    • Added UsageEventOutboxServiceTest to test the functionality of the UsageEventOutboxService.
  • src/test/java/com/project/domain/usage/service/helper/UsageEventPublisherTest.java
    • Removed UsageEventPublisherTest as notification publishing is now handled via the outbox pattern.
  • src/test/java/com/project/domain/usage/service/helper/UsageLuaExecutorTest.java
    • Modified UsageLuaExecutorTest to align with updated Lua script results and exception handling.
  • src/test/java/com/project/domain/usage/service/helper/UsageProcessingDecisionMapperTest.java
    • Added UsageProcessingDecisionMapperTest to test the functionality of the UsageProcessingDecisionMapper.
Activity
  • The pull request introduces significant changes to the usage event processing pipeline.
  • Reviewers should focus on the simplification of the accounting structure and the correctness of the notification Outbox implementation.
  • Pay close attention to the Redis/DB membership validation and the state transitions within the notification Outbox.
  • Consider whether the membership cache validation and duplicate handling align with current traffic and operational practices.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

전반적으로 매우 훌륭한 리팩토링입니다. usage-events 처리 로직을 직접 정산 방식으로 단순화하고, 알림 발행에 Outbox 패턴을 도입하여 시스템의 안정성과 데이터 정합성을 크게 향상시켰습니다. 특히 복잡했던 Lua 스크립트를 구조화하고, 실패 시 복구 흐름을 명확하게 설계한 점이 인상적입니다. 코드 품질 향상을 위해 중복 로직 제거 및 매직넘버 상수화 등 몇 가지 개선점에 대한 코멘트를 남겼으니 확인 부탁드립니다.

Comment thread src/main/java/com/project/domain/usage/repository/UsageEventOutboxRepository.java Outdated
@github-actions
Copy link
Copy Markdown

SonarQube Quality Summary (Community)

Quality Gate FAILED

Branch: feat/DABOM-447
Compared to: default branch

Issues

  • 🐞 Bugs: 8
  • 🔐 Vulnerabilities: 0
  • 📎 Code Smells: 92

Measures

  • Coverage: 0%
  • Duplication: 0%

🔗 Dashboard: https://sonarqube.swthewhite.store/dashboard?id=dabom-processor-usage&branch=feat/DABOM-447

Generated automatically by GitHub Actions.

@dabom-devops-bot
Copy link
Copy Markdown

👋 PR 리마인드 알림입니다

이 PR이 몇 시간 동안 업데이트되지 않았어요.

  • 🙋 PR 작성자: 추가 작업 중이거나 설명이 필요하면 코멘트 남겨주세요
  • 👀 리뷰어 / 팀원: 이미 봤다면 LGTM 한 줄도 좋아요!

바쁘실 수 있다는 점 이해합니다 🙏
다만 작은 한 줄이 PR 흐름을 살려줘요 🙂

@ChoiSeungeon
Copy link
Copy Markdown
Contributor Author

/gemini review

@github-actions
Copy link
Copy Markdown

SonarQube Quality Summary (Community)

Quality Gate FAILED

Branch: feat/DABOM-447
Compared to: default branch

Issues

  • 🐞 Bugs: 8
  • 🔐 Vulnerabilities: 0
  • 📎 Code Smells: 92

Measures

  • Coverage: 0%
  • Duplication: 0%

🔗 Dashboard: https://sonarqube.swthewhite.store/dashboard?id=dabom-processor-usage&branch=feat/DABOM-447

Generated automatically by GitHub Actions.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

이번 PR은 usage-events 처리 로직을 대폭 개선하여 정산 구조를 단순화하고, 알림 발행을 위해 Outbox 패턴을 도입한 점이 인상적입니다. 전반적으로 코드의 안정성과 유지보수성이 크게 향상되었습니다. 특히 아래와 같은 점들이 돋보입니다.

  • usage-persist Kafka 단계를 제거하고 usage-events 소비 시점에 DB에 직접 정산하도록 변경하여 흐름을 단순화했습니다.
  • 알림 발행에 Outbox 패턴과 비동기 발행, 배치 재시도 로직을 도입하여 메시지 유실 가능성을 크게 줄였습니다.
  • Lua 스크립트를 구조화하고 상태 문자열 해석 로직을 Java 코드로 중앙화하여 가독성과 유지보수성을 높였습니다.
  • 캐시와 DB를 함께 사용하는 family-customer 관계 검증 로직을 초입에 추가하여 잘못된 데이터의 후속 처리를 미리 방지했습니다.

코드 리뷰에서는 스타일 가이드에 따라 RuntimeException을 직접 사용하는 부분을 ApplicationException으로 변경하도록 두 가지 제안을 드렸습니다. 이 부분을 수정하면 코드의 일관성과 예외 처리 정책 준수 수준이 더욱 높아질 것입니다. 훌륭한 리팩터링입니다.

@github-actions
Copy link
Copy Markdown

SonarQube Quality Summary (Community)

Quality Gate PASSED

Branch: feat/DABOM-447
Compared to: default branch

Issues

  • 🐞 Bugs: 7
  • 🔐 Vulnerabilities: 0
  • 📎 Code Smells: 93

Measures

  • Coverage: 0%
  • Duplication: 0%

🔗 Dashboard: https://sonarqube.swthewhite.store/dashboard?id=dabom-processor-usage&branch=feat/DABOM-447

Generated automatically by GitHub Actions.

Copy link
Copy Markdown
Member

@k0081915 k0081915 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!

@ChoiSeungeon ChoiSeungeon merged commit f06daae into develop Mar 17, 2026
9 checks passed
@ChoiSeungeon ChoiSeungeon deleted the feat/DABOM-447 branch March 17, 2026 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat refactor 리팩토링 size/XL PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DABOM-447] Kafka 사용량 처리에 Outbox 도입

2 participants