이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • 이벤트 모델링 리뷰에서 이름보다 먼저 봐야 할 기준은 무엇인가?
  • 어떤 event 설계가 재처리, 중복 처리, schema 변경에서 문제를 만드는가?
  • Java/Spring 코드 리뷰에서 outbox와 event schema를 어떻게 점검하는가?
  • 이벤트 모델링 실수를 운영 장애로 번지기 전에 어떻게 발견하는가?

개요

이벤트 모델링 리뷰는 event 이름이 예쁜지 확인하는 시간이 아니다.

발생 조건, key, version, payload 의미, 취소/삭제 표현, 중복 처리, replay 가능성을 확인하는 시간이다.

좋은 event는 한 번 발행된 뒤 여러 consumer가 오래 읽을 수 있어야 한다.

나쁜 event는 처음에는 잘 동작해 보이다가 schema 변경, backfill, consumer 추가 때 문제를 만든다.

원리

event review는 다음 순서로 진행한다.

  • 이 event가 실제 비즈니스 사건인가?
  • 언제 한 번만 발생하는가?
  • 어떤 key로 partitioning, idempotency, replay를 할 수 있는가?
  • consumer가 모르는 필드나 version을 견딜 수 있는가?
  • 취소, 삭제, 보정, 늦은 도착을 어떻게 표현하는가?

이 질문에 답하지 못하면 아직 모델링이 끝난 것이 아니다.

흔한 실수

실수증상결과
상태명을 event로 사용OrderStatusChanged 남발consumer가 의미를 추측
key 누락payment id, line id 없음fact grain 생성 실패
version 없음payload 의미 변경replay 실패
삭제 event 없음row delete만 수행search/mart 잔존
DTO 재사용화면 필드 변경에 event 변경consumer 장애
audit/event 혼용actor 정보와 business event 섞임개인정보와 계약 충돌

실수는 대부분 도구 문제가 아니라 의미와 계약을 명확히 하지 않은 데서 온다.

리뷰 체크리스트

event name:
  - 과거형 비즈니스 사건인가?
  - 상태명이나 명령형 이름이 아닌가?
 
emission:
  - 어떤 transaction에서 발생하는가?
  - 중복 callback이나 retry 때 한 번만 남는가?
 
schema:
  - eventId, aggregateId, occurredAt, version이 있는가?
  - required field와 optional field가 구분되어 있는가?
 
consumer:
  - idempotency key가 명확한가?
  - replay 시 같은 결과가 나오는가?
 
change:
  - 필드 추가/삭제/의미 변경 정책이 있는가?
  - consumer owner가 리뷰했는가?

리뷰 체크리스트는 문서에만 있으면 부족하다.

PR template이나 architecture review 항목으로 들어가야 한다.

나쁜 예

{
  "type": "OrderUpdated",
  "orderId": 10042,
  "status": "PAID",
  "amount": 49000,
  "updatedAt": "2026-06-30T10:15:30+09:00"
}

이 event는 어떤 일이 발생했는지 모호하다.

결제 승인인지, 관리자 수정인지, 재동기화인지 알 수 없다.

event id와 version도 없어 중복 처리와 schema 변경에 약하다.

좋은 방향

{
  "eventId": "evt-10042-002",
  "eventType": "OrderPaid",
  "eventVersion": 1,
  "aggregateType": "Order",
  "aggregateId": "10042",
  "occurredAt": "2026-06-30T10:15:30+09:00",
  "payload": {
    "orderId": 10042,
    "paymentId": "pay-555",
    "paidAmount": "49000.00",
    "currency": "KRW"
  }
}

이 event는 발생한 사건, 중복 판단 key, version, business payload가 분리되어 있다.

consumer가 검색 색인, warehouse fact, 알림을 만들 때 필요한 최소 계약도 보인다.

Spring 코드 리뷰 지점

@Transactional
public void handlePaymentApproved(PaymentApprovedMessage message) {
    Order order = orderRepository.getById(message.orderId());
    order.markPaid(message.paymentId(), message.amount());
    outboxRepository.save(OutboxEvent.from(order.paidEvent()));
}

리뷰에서는 markPaid가 중복 callback에 안전한지 본다.

또한 state 변경과 outbox 저장이 같은 transaction 안에 있는지 확인한다.

event 발행을 transaction 밖에서 직접 Kafka로 보내면 dual write 문제가 생길 수 있다.

판단 축

  • event 이름은 past-tense business fact로 둔다.
  • command, state, audit action을 event와 섞지 않는다.
  • event id와 aggregate id를 모두 둔다.
  • payload는 consumer가 필요한 business key를 포함한다.
  • schema 변경과 replay 테스트를 같이 본다.

실패 모델

  • 같은 결제 callback이 두 번 와서 OrderPaid가 두 번 발행된다.
  • OrderUpdated만 있어 warehouse가 결제 fact와 취소 fact를 구분하지 못한다.
  • product category key가 없어 mart dimension join이 실패한다.
  • 필드 의미가 바뀌었는데 event version이 그대로다.
  • 삭제와 개인정보 익명화 event가 없어 downstream data가 남는다.

운영 지표

  • event_review_defect_count: 리뷰에서 발견한 event 설계 결함 수다.
  • duplicate_event_emission_count: 같은 business event 중복 발행 수다.
  • missing_required_key_count: 필수 key가 비어 있는 event 수다.
  • consumer_deserialization_error_count: schema 변경으로 인한 consumer 오류다.
  • replay_mismatch_count: replay 결과가 기존 projection과 다른 수다.

품질 검증

SELECT event_type, COUNT(*) AS count
FROM outbox_event
WHERE created_at >= now() - interval '1 day'
GROUP BY event_type;
SELECT event_type, COUNT(*) AS missing_event_id_count
FROM outbox_event
WHERE event_id IS NULL OR aggregate_id IS NULL
GROUP BY event_type;

event 품질도 DB 데이터처럼 검증해야 한다.

누락된 key는 장애가 난 뒤가 아니라 발행 직후 발견해야 한다.

개인 프로젝트 기준

개인 프로젝트에서는 간단한 PR checklist로 충분하다.

event 이름, event id, aggregate id, version, occurredAt, 재처리 테스트만 확인해도 많은 실수를 줄일 수 있다.

Kafka가 없어도 outbox table과 처리 이력으로 리뷰 기준을 연습할 수 있다.

기업 운영 기준

기업에서는 event catalog나 schema registry와 함께 리뷰 기준을 운영한다.

producer 팀이 event를 바꿀 때 consumer owner, 데이터 팀, 보안 요구를 함께 확인한다.

중요 event는 replay test와 compatibility check가 배포 gate가 되어야 한다.

실전 팁

  • event 이름이 Updated, Changed, Synced로 끝나면 의미가 충분한지 다시 묻는다.
  • event를 새로 만들 때 consumer가 없더라도 미래 replay를 고려한다.
  • 삭제와 취소는 처음부터 별도 event로 모델링한다.
  • schema 변경 PR에는 old event sample과 new event sample을 같이 둔다.
  • event 리뷰는 code owner뿐 아니라 downstream owner를 포함한다.

위험 신호!

  • event 설계 문서가 payload 예시 하나뿐이다.
  • event 발생 조건이 테스트 코드에만 숨어 있다.
  • consumer가 payload 필드를 null-safe하게 읽지 못한다.
  • replay를 해 본 적이 없는데 event log를 신뢰한다고 말한다.
  • audit log와 domain event가 같은 topic에 섞여 있다.

확인 질문

확인 질문

  • 이벤트 모델링 리뷰에서 이름보다 먼저 봐야 할 것은 무엇인가?
    • 발생 조건, key, version, consumer 계약, 취소/삭제 표현, replay 가능성이다.
  • OrderUpdated 같은 event 이름이 위험한 이유는 무엇인가?
    • 어떤 비즈니스 사건인지 모호해 consumer가 결제, 취소, 보정 의미를 추측하게 된다.
  • event 설계가 재처리에 안전한지 확인하려면 무엇을 테스트해야 하는가?
    • 같은 event를 두 번 처리해도 결과가 안전한지, 과거 version event를 replay해도 projection이 맞는지 테스트한다.

참고 문서