
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
spring.json.trusted.packages: "com.yeoljeong.tripmate.*"
spring.json.type.headers: true
spring.json.type.headers: true 이 설정이 핵심이다. 이 옵션이 true이면 Kafka 메시지의 헤더에 포함된 타입 정보를 우선적으로 사용해서 역직렬화할 클래스를 결정한다.
메시지를 발행(produce)할 때 JsonSerializer는 자동으로 두 개의 헤더를 추가한다.
| 헤더 키 | 예시 값 | 설명 |
|---|---|---|
__TypeId__ | com.yeoljeong.tripmate.event.OrderEvent | 대상 클래스 FQCN |
__KeyTypeId__ | java.lang.String | 키 타입 (optional) |
소비(consume)할 때 JsonDeserializer는 spring.json.type.headers: true이므로 payload 자체를 분석하지 않고 __TypeId__ 헤더 값을 읽어서 해당 클래스로 역직렬화한다.
spring.json.type.headers: true공식 문서에 따르면, JsonDeserializer는 타입을 결정하는 두 가지 전략을 가진다. (공식 문서)
true → 메시지 헤더의 __TypeId__ 를 보고 역직렬화 클래스 결정
false → 기본값으로 설정된 targetType 또는 제네릭 타입으로 역직렬화
true로 설정하면 같은 토픽에 여러 타입의 메시지를 혼재시킬 수 있다. 예를 들어 OrderEvent, PaymentEvent, UserEvent가 모두 같은 토픽을 통해 흐를 때 각각 올바른 클래스로 매핑된다.
spring.json.trusted.packages보안 상 이유로 JsonDeserializer는 헤더에 명시된 클래스라고 해서 무조건 인스턴스화하지 않는다. 공격자가 헤더를 조작해 임의 클래스를 로드하는 것을 막기 위함이다.
spring.json.trusted.packages: "com.yeoljeong.tripmate.*"
* 와일드카드를 사용하면 해당 패키지 하위 모든 클래스를 신뢰한다. 전체를 허용하려면 "*"만 쓸 수 있지만 운영 환경에서는 권장하지 않는다.
Consumer 설정이 헤더에 의존하므로, Producer도 헤더를 올바르게 심어야 한다.
spring:
kafka:
producer:
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
properties:
spring.json.add.type.headers: true # 기본값이 true이므로 생략 가능
JsonSerializer는 기본적으로 직렬화 시 __TypeId__ 헤더를 자동으로 추가한다. Producer와 Consumer가 서로 다른 서비스에 있을 경우 클래스 경로(FQCN)가 달라질 수 있는데, 이때는 타입 매핑 설정으로 해결한다.
# Producer 쪽 (서비스 A)
spring.json.type.mapping: "orderEvent:com.serviceA.event.OrderEvent"
# Consumer 쪽 (서비스 B)
spring.json.type.mapping: "orderEvent:com.serviceB.event.OrderEvent"
이렇게 하면 헤더에는 com.serviceA.event.OrderEvent 대신 orderEvent라는 논리명이 담기고, Consumer는 그것을 자신의 클래스로 매핑한다.
| 설정 | 역할 |
|---|---|
spring.json.type.headers: true | 헤더의 __TypeId__ 값으로 역직렬화 클래스 결정 |
spring.json.trusted.packages | 허용된 패키지의 클래스만 역직렬화 허용 (보안) |
spring.json.add.type.headers: true | Producer가 헤더에 타입 정보를 심도록 설정 (기본값) |
spring.json.type.mapping | 서비스 간 클래스 경로가 다를 때 논리명으로 매핑 |
결국 이 설정의 핵심은 "메시지 본문을 파싱하기 전에 헤더를 먼저 보고, 거기 적힌 클래스로 Jackson이 역직렬화한다"는 것이다. 덕분에 Consumer 코드는 ConsumerRecord<String, Object>가 아니라 처음부터 ConsumerRecord<String, OrderEvent> 같은 구체 타입을 받을 수 있다.