이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- event schema는 producer와 consumer 사이에서 어떤 계약을 만드는가?
- event payload에는 어떤 공통 필드와 business 필드가 필요한가?
- Java/Spring 백엔드에서 event schema를 DTO처럼 설계하면 어떤 문제가 생기는가?
- 재처리와 품질 검증을 위해 schema에 어떤 key와 timestamp가 필요한가?
개요
event schema는 메시지 모양이 아니다.
producer와 consumer 사이의 장기 계약이다.
필드 이름보다 중요한 것은 event 의미, 발생 조건, key, timestamp, version, idempotency 기준이다.
백엔드 DTO는 API 요청에 맞춰 자주 바뀔 수 있다.
event schema는 이미 발행된 과거 event와 아직 배포되지 않은 consumer까지 고려해야 한다.
원리
좋은 event schema는 세 가지 질문에 답한다.
- 이 event는 정확히 어떤 비즈니스 사건인가?
- consumer는 어떤 key로 중복과 순서를 판단하는가?
- 나중에 다시 읽어도 같은 의미로 해석할 수 있는가?
schema에는 공통 metadata와 business payload가 함께 필요하다.
metadata는 운영과 재처리를 위한 필드이고, payload는 domain 의미를 담는 필드다.
기본 구조
{
"eventId": "evt-20260630-0001",
"eventType": "OrderPaid",
"eventVersion": 1,
"aggregateType": "Order",
"aggregateId": "10042",
"occurredAt": "2026-06-30T10:15:30+09:00",
"producer": "order-service",
"traceId": "4f6c...",
"payload": {
"orderId": 10042,
"paymentId": "pay-555",
"paidAmount": "49000.00",
"currency": "KRW"
}
}eventId는 중복 방어에 필요하다.
aggregateId는 같은 주문의 순서와 partitioning에 필요하다.
occurredAt은 사건 발생 시각이고, broker publish 시각과 다를 수 있다.
eventVersion은 consumer가 schema 해석 방식을 결정하는 데 필요하다.
발생 조건
schema 문서에는 필드 목록만 있으면 부족하다.
언제 이 event가 발생하는지도 적어야 한다.
eventType: OrderPaid
emitted when:
- payment approval succeeds
- order status changes CREATED -> PAID
not emitted when:
- duplicated payment callback is ignored
- payment authorization only reserves amount
partition key: orderId
idempotency key: eventId발생 조건이 없으면 consumer마다 event 의미를 다르게 해석한다.
Java 모델 예시
public record OrderPaidEvent(
String eventId,
int eventVersion,
Long orderId,
String paymentId,
BigDecimal paidAmount,
String currency,
OffsetDateTime occurredAt
) {
}이 record는 API response DTO가 아니다.
필드 제거와 의미 변경은 consumer를 깨뜨릴 수 있으므로 schema 변경 절차를 거쳐야 한다.
Key 설계
event schema에서 key는 검색 편의가 아니라 운영 기준이다.
eventId: event 자체의 idempotency keyaggregateId: 주문, 결제, 회원 같은 domain aggregate 기준correlationId: 요청 흐름이나 saga 추적sourceId: 원천 DB row나 outbox row 추적
Kafka partition key로 aggregateId를 쓰면 같은 aggregate의 순서를 유지하기 쉽다.
하지만 너무 뜨거운 key는 partition skew를 만들 수 있다.
timestamp 설계
event에는 여러 시각이 등장할 수 있다.
occurredAt: 비즈니스 사건 발생 시각createdAt: event object 생성 시각publishedAt: broker 발행 시각processedAt: consumer 처리 시각
분석용 fact는 보통 occurredAt을 기준으로 삼는다.
운영 지연은 publishedAt - occurredAt, processedAt - publishedAt처럼 구분해서 본다.
판단 축
- event 이름과 발생 조건을 먼저 정한다.
- 공통 metadata와 business payload를 분리한다.
- consumer가 필요한 key를 payload 안에 숨기지 않는다.
- timestamp의 의미를 명확히 구분한다.
- optional field와 default 값을 문서화한다.
실패 모델
- API response DTO를 그대로 event payload로 사용한다.
- event id가 없어 consumer가 중복을 막지 못한다.
updatedAt하나만 있어 발생 시각과 발행 시각을 구분하지 못한다.- payment id가 빠져 결제 fact grain을 만들 수 없다.
- null 허용 여부와 default 값이 없어 consumer마다 다르게 해석한다.
품질 검증
schema 품질은 runtime에서도 확인해야 한다.
SELECT event_type, event_version, COUNT(*) AS count
FROM event_log
WHERE occurred_at >= now() - interval '1 day'
GROUP BY event_type, event_version;SELECT COUNT(*) AS missing_key_count
FROM event_log
WHERE event_type = 'OrderPaid'
AND (aggregate_id IS NULL OR payload->>'paymentId' IS NULL);첫 번째 SQL은 버전 분포를 보고, 두 번째 SQL은 재처리에 필요한 key 누락을 찾는다.
개인 프로젝트 기준
개인 프로젝트에서는 JSON Schema나 registry 없이 markdown 문서와 event table로 시작해도 된다.
다만 event type, version, key, required field, 발생 조건은 반드시 적는다.
테스트에서는 같은 event를 두 번 처리해도 결과가 안전한지 확인한다.
기업 운영 기준
기업에서는 schema registry, compatibility check, producer/consumer 계약 테스트가 필요할 수 있다.
event schema 변경은 API 변경보다 영향 범위가 넓을 수 있다.
배포 전에 consumer 목록과 replay 영향, 보관된 과거 event 해석 가능성을 확인한다.
실전 팁
- event payload에는 화면 표시용 label보다 business key를 넣는다.
- 금액은 숫자 타입, 통화, 반올림 기준을 함께 정의한다.
- 삭제와 익명화가 필요한 개인정보 필드는 event 보관 기간과 함께 검토한다.
- schema 문서에 예시 event와 반례 event를 함께 둔다.
- event field 설명에는 “누가 채우는가”와 “언제 비어 있을 수 있는가”를 적는다.
위험 신호!
- event schema가 controller response와 거의 같다.
- consumer가 payload 내부 필드를 추측해 key로 사용한다.
- event version은 있지만 실제 compatibility 규칙이 없다.
- timestamp가 하나뿐인데 지연 지표를 여러 의미로 계산한다.
- schema 변경이 producer 팀 안에서만 결정된다.
확인 질문
확인 질문
- event schema에서
eventId와aggregateId는 왜 둘 다 필요한가?
eventId는 사건 자체의 중복 방어에,aggregateId는 같은 domain 객체의 순서와 grouping에 필요하다.- API DTO를 event schema로 그대로 쓰면 어떤 문제가 생기는가?
- 화면 요구에 따른 필드 변경이 consumer 계약과 과거 event 해석을 깨뜨릴 수 있다.
- 분석 fact를 만들기 위해 event schema에 필요한 정보는 무엇인가?
- fact grain을 식별할 key, 금액과 수량 같은 측정값, dimension key, business occurredAt, source event id가 필요하다.
참고 문서
- Apache Kafka Documentation
- Confluent Schema Registry Documentation
- CloudEvents Specification
- Martin Kleppmann, Designing Data-Intensive Applications