Skip to content

# feat(webhook): webhook-dispatcher 추가 + payout 중복 이벤트 멱등 처리 보강 - #11

Merged
NewEgoDoc merged 1 commit into
mainfrom
feat/webhook-dispatcher-and-payout-idempotency
May 7, 2026
Merged

# feat(webhook): webhook-dispatcher 추가 + payout 중복 이벤트 멱등 처리 보강#11
NewEgoDoc merged 1 commit into
mainfrom
feat/webhook-dispatcher-and-payout-idempotency

Conversation

@NewEgoDoc

Copy link
Copy Markdown
Owner

Summary

webhook-dispatcher 모듈을 신규 추가해 payout 결과 이벤트(remittance.payout.completed/failed)를 Kafka에서 소비하고 외부 webhook으로 전달하는 비동기 파이프라인을 구현했습니다. 또한 payout-worker의 중복 이벤트 처리 로직을 UNIQUE 예외 catch 방식에서 사전 조회 방식으로 바꿔 UnexpectedRollbackException으로 인한 partition 정체(stuck) 가능성을 제거했습니다.


Changes

1) webhook-dispatcher 신규 구현

  • webhook-dispatcher/build.gradle.kts
    • Spring Boot/JPA/Flyway/Kafka/WebMVC/Validation/Testcontainers 의존성 구성
  • webhook-dispatcher/src/main/kotlin/com/openremit/webhook/WebhookDispatcherApplication.kt (신규)
    • @EnableKafka, @EnableScheduling, @EnableConfigurationProperties 적용
  • webhook-dispatcher/src/main/kotlin/com/openremit/webhook/infrastructure/kafka/RemittancePayoutResultConsumer.kt (신규)
    • remittance.payout.completed/failed 소비
    • eventId = {remittanceId}:{topic} 멱등 키로 enqueue
  • webhook-dispatcher/src/main/kotlin/com/openremit/webhook/application/WebhookEnqueueService.kt (신규)
    • event_id 사전 조회 기반 enqueue 멱등 처리
  • webhook-dispatcher/src/main/kotlin/com/openremit/webhook/application/WebhookDispatcher.kt (신규)
    • 스케줄러로 due webhook 배치 폴링/디스패치
  • webhook-dispatcher/src/main/kotlin/com/openremit/webhook/application/WebhookSender.kt (신규)
    • 단건 트랜잭션 단위 발송 시도 및 상태 전이
  • webhook-dispatcher/src/main/kotlin/com/openremit/webhook/application/WebhookBackoff.kt (신규)
    • exponential backoff 계산 (base/multiplier/maxAttempts)
  • webhook-dispatcher/src/main/kotlin/com/openremit/webhook/domain/Webhook.kt (신규)
    • PENDING/SUCCESS/FAILED, attempt/next_retry_at/실패 이력 모델링
  • webhook-dispatcher/src/main/kotlin/com/openremit/webhook/infrastructure/client/WebhookClient.kt (신규)
    • RestClient 기반 webhook POST 클라이언트 + 예외 래핑
  • webhook-dispatcher/src/main/kotlin/com/openremit/webhook/infrastructure/persistence/WebhookRepository.kt (신규)
    • due webhook 조회 쿼리 추가
  • webhook-dispatcher/src/main/resources/db/migration/V1__webhook_tables.sql (신규)
    • webhooks 테이블/인덱스/event_id UNIQUE 추가
  • webhook-dispatcher/src/main/resources/application.yaml (신규)
    • datasource/flyway/kafka/webhook/backoff/polling 설정 추가

2) payout-worker 멱등 처리 보강

  • payout-worker/src/main/kotlin/com/openremit/payout/application/PayoutProcessor.kt
    • DataIntegrityViolationException catch 제거
    • findByRemittanceId 사전 조회 후 없을 때만 insert로 변경
  • payout-worker/src/test/kotlin/com/openremit/payout/PayoutWorkerIntegrationTest.kt
    • 동일 remittance 이벤트 3회 수신 시
      • attempt 1건
      • outbox 1건
      • payout API 호출 1회 검증 테스트 추가

3) 로컬 mock webhook 인프라 추가

  • docker-compose.yml
    • mock-webhook (WireMock, 9997) 서비스 추가
  • mock-webhook/mappings/webhook-success.json (신규)
    • /webhook 200 응답 매핑 추가

4) webhook-dispatcher 통합 테스트 추가

  • webhook-dispatcher/src/test/kotlin/com/openremit/webhook/WebhookDispatcherIntegrationTest.kt (신규)
    • 성공 전송 시 SUCCESS 전이 검증
    • 502 응답 시 백오프 재시도 후 FAILED 전이 검증
    • 중복 이벤트 enqueue/발송 멱등 검증
  • webhook-dispatcher/src/test/kotlin/com/openremit/webhook/WebhookDispatcherTestcontainersConfig.kt (신규)
    • MySQL/Kafka Testcontainers 설정

Checklist

  • webhook-dispatcher 모듈 및 실행 엔트리포인트 추가
  • payout 결과 Kafka consumer + webhook enqueue 구현
  • webhook 발송/재시도/상태 전이 로직 구현
  • webhook 테이블 마이그레이션 및 설정 추가
  • payout-worker 중복 이벤트 멱등 처리 보강
  • mock-webhook docker-compose 서비스 추가
  • payout-worker/webhook-dispatcher 통합 테스트 추가

How to Test

# payout-worker 중복 이벤트 멱등 테스트
./gradlew :payout-worker:test --tests "*PayoutWorkerIntegrationTest"

# webhook-dispatcher 통합 테스트
./gradlew :webhook-dispatcher:test --tests "*WebhookDispatcherIntegrationTest"

# 전체 테스트
./gradlew test

# 로컬 mock webhook 포함 인프라 실행
docker compose up -d

## Summary

`webhook-dispatcher` 모듈을 신규 추가해 payout 결과 이벤트(`remittance.payout.completed/failed`)를 Kafka에서 소비하고 외부 webhook으로 전달하는 비동기 파이프라인을 구현했습니다.
또한 `payout-worker`의 중복 이벤트 처리 로직을 UNIQUE 예외 catch 방식에서 사전 조회 방식으로 바꿔 `UnexpectedRollbackException`으로 인한 partition 정체(stuck) 가능성을 제거했습니다.

---

### Changes

#### 1) webhook-dispatcher 신규 구현

- `webhook-dispatcher/build.gradle.kts`
  - Spring Boot/JPA/Flyway/Kafka/WebMVC/Validation/Testcontainers 의존성 구성
- `webhook-dispatcher/src/main/kotlin/com/openremit/webhook/WebhookDispatcherApplication.kt` (신규)
  - `@EnableKafka`, `@EnableScheduling`, `@EnableConfigurationProperties` 적용
- `webhook-dispatcher/src/main/kotlin/com/openremit/webhook/infrastructure/kafka/RemittancePayoutResultConsumer.kt` (신규)
  - `remittance.payout.completed/failed` 소비
  - `eventId = {remittanceId}:{topic}` 멱등 키로 enqueue
- `webhook-dispatcher/src/main/kotlin/com/openremit/webhook/application/WebhookEnqueueService.kt` (신규)
  - `event_id` 사전 조회 기반 enqueue 멱등 처리
- `webhook-dispatcher/src/main/kotlin/com/openremit/webhook/application/WebhookDispatcher.kt` (신규)
  - 스케줄러로 due webhook 배치 폴링/디스패치
- `webhook-dispatcher/src/main/kotlin/com/openremit/webhook/application/WebhookSender.kt` (신규)
  - 단건 트랜잭션 단위 발송 시도 및 상태 전이
- `webhook-dispatcher/src/main/kotlin/com/openremit/webhook/application/WebhookBackoff.kt` (신규)
  - exponential backoff 계산 (`base/multiplier/maxAttempts`)
- `webhook-dispatcher/src/main/kotlin/com/openremit/webhook/domain/Webhook.kt` (신규)
  - `PENDING/SUCCESS/FAILED`, attempt/next_retry_at/실패 이력 모델링
- `webhook-dispatcher/src/main/kotlin/com/openremit/webhook/infrastructure/client/WebhookClient.kt` (신규)
  - `RestClient` 기반 webhook POST 클라이언트 + 예외 래핑
- `webhook-dispatcher/src/main/kotlin/com/openremit/webhook/infrastructure/persistence/WebhookRepository.kt` (신규)
  - due webhook 조회 쿼리 추가
- `webhook-dispatcher/src/main/resources/db/migration/V1__webhook_tables.sql` (신규)
  - `webhooks` 테이블/인덱스/`event_id` UNIQUE 추가
- `webhook-dispatcher/src/main/resources/application.yaml` (신규)
  - datasource/flyway/kafka/webhook/backoff/polling 설정 추가

#### 2) payout-worker 멱등 처리 보강

- `payout-worker/src/main/kotlin/com/openremit/payout/application/PayoutProcessor.kt`
  - `DataIntegrityViolationException` catch 제거
  - `findByRemittanceId` 사전 조회 후 없을 때만 insert로 변경
- `payout-worker/src/test/kotlin/com/openremit/payout/PayoutWorkerIntegrationTest.kt`
  - 동일 remittance 이벤트 3회 수신 시
    - attempt 1건
    - outbox 1건
    - payout API 호출 1회
    검증 테스트 추가

#### 3) 로컬 mock webhook 인프라 추가

- `docker-compose.yml`
  - `mock-webhook` (WireMock, `9997`) 서비스 추가
- `mock-webhook/mappings/webhook-success.json` (신규)
  - `/webhook` 200 응답 매핑 추가

#### 4) webhook-dispatcher 통합 테스트 추가

- `webhook-dispatcher/src/test/kotlin/com/openremit/webhook/WebhookDispatcherIntegrationTest.kt` (신규)
  - 성공 전송 시 `SUCCESS` 전이 검증
  - 502 응답 시 백오프 재시도 후 `FAILED` 전이 검증
  - 중복 이벤트 enqueue/발송 멱등 검증
- `webhook-dispatcher/src/test/kotlin/com/openremit/webhook/WebhookDispatcherTestcontainersConfig.kt` (신규)
  - MySQL/Kafka Testcontainers 설정

---

### Checklist

- [x] webhook-dispatcher 모듈 및 실행 엔트리포인트 추가
- [x] payout 결과 Kafka consumer + webhook enqueue 구현
- [x] webhook 발송/재시도/상태 전이 로직 구현
- [x] webhook 테이블 마이그레이션 및 설정 추가
- [x] payout-worker 중복 이벤트 멱등 처리 보강
- [x] mock-webhook docker-compose 서비스 추가
- [x] payout-worker/webhook-dispatcher 통합 테스트 추가

---

### How to Test

```bash
# payout-worker 중복 이벤트 멱등 테스트
./gradlew :payout-worker:test --tests "*PayoutWorkerIntegrationTest"

# webhook-dispatcher 통합 테스트
./gradlew :webhook-dispatcher:test --tests "*WebhookDispatcherIntegrationTest"

# 전체 테스트
./gradlew test

# 로컬 mock webhook 포함 인프라 실행
docker compose up -d
@NewEgoDoc
NewEgoDoc merged commit 3158f12 into main May 7, 2026
1 check passed
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