[MSA] kafka 이벤트 처리 문제

남순식·2026년 5월 1일

Kafka MSA 환경에서 이벤트 객체 역직렬화 문제

Kafka Header 타입 메타데이터 방식이 멀티 MSA 환경에서 터진 이야기


문제 상황

Kafka로 MSA 간 통신할 때 단순한 문자열이 아닌 객체를 주고받아야 했다.
이를 위해 메시지 Header에 Type 메타데이터를 심어 역직렬화하는 방식을 택했는데, 여기서 예상치 못한 문제가 터졌다.

메시지를 발행한 서비스에서는 아무 문제가 없었지만,
다른 MSA에서 해당 메시지를 수신하는 순간 클래스를 찾을 수 없다는 예외가 발생했다.

메타데이터에 적힌 클래스 경로가 수신 서비스에는 존재하지 않았기 때문이다.
설상가상으로 이 예외가 언체크 예외(Unchecked Exception) 라 Kafka의 재시도 로직이 무한으로 돌기 시작했다.


1차 시도 — 패키지 구조를 맞춰 클래스를 각 서비스에 중복 작성

역직렬화가 실패하는 이유는 수신 측에 해당 클래스가 없기 때문이니,
동일한 패키지 구조로 이벤트 클래스를 각 서비스에 직접 두는 방법을 떠올렸다.

동작은 했지만, 바로 명확한 단점이 보였다.

이벤트 구조가 변경되면 해당 이벤트를 구독하는 모든 서비스에서 똑같이 수정해줘야 한다.
서비스가 3개든 10개든 변경이 전파되는 범위가 선형으로 늘어나고,
하나라도 빠뜨리면 조용히 버그가 생긴다.

유지보수 측면에서 받아들이기 어려운 구조였다.


2차 시도 — common-event 모듈로 이벤트 객체 중앙화

"변경이 여러 곳으로 퍼지는 게 문제라면, 한 곳에서만 관리하면 되지 않을까?"

이 생각으로 common-event 모듈을 만들어 이벤트 객체를 중앙화했다.
모든 서비스가 이 모듈에 의존하면 변경은 단 한 곳에서만 일어난다.

그런데 이 방법도 새로운 문제를 낳았다.

문제 1 — 팩터리 메서드를 어디에 둬야 하나?

이벤트 객체가 각 서비스 안에 있을 때는 infra 계층에서 팩터리 메서드를 두고 자연스럽게 이벤트를 생성할 수 있었다.
하지만 이벤트 클래스가 common 모듈로 옮겨가면서,
서비스 고유의 도메인 로직이 담긴 팩터리 메서드를 common 모듈에 작성하는 건 구조적으로 맞지 않았다.

문제 2 — application 계층에서 만들면 계층 경계가 무너진다

그렇다면 application 계층에서 이벤트 객체를 직접 만들어야 하나?
그렇게 하면 application 계층이 infrastructure 성격의 객체에 의존하게 되어 계층 간 경계가 무너진다.

문제 3 — common-event 모듈의 비대화

서비스가 계속 늘어나면 common-event 모듈이 감당해야 할 이벤트 수도 폭발적으로 늘어난다.
결국 모듈 자체가 거대한 짐이 되어버리는 문제도 피하기 어렵다.


임시 조치 — private 생성 메서드로 우선 막기

일단 서비스가 돌아가야 했기 때문에,
이벤트 객체 생성 로직을 private 메서드로 분리해 클래스 내부에 숨기는 방식으로 임시 처리했다.

계층 간 의존성 문제를 외부로 드러내지 않으면서 동작은 보장할 수 있었다.

다만 이건 어디까지나 임시방편이다.
근본적인 구조 문제는 그대로 남아있다.


앞으로의 방향 — Mapper 도입

근본 해결책으로 Mapper 패턴을 도입할 계획이다.

도메인 객체 → 이벤트 객체로의 변환 책임을 Mapper에 위임하면,
application 계층은 Mapper 인터페이스만 알면 되고 infrastructure 구현체를 직접 참조하지 않아도 된다.
이벤트 생성 로직도 Mapper 안에 명확하게 자리잡는다.

계층 간 경계를 지키면서도 common 모듈 중앙화의 이점을 유지할 수 있는 방향이라고 생각한다.

Mapper 적용 후의 구조는 다음 글에서 다룰 예정이다.


정리

시도방식문제점
1차각 서비스에 이벤트 클래스 중복 작성변경 시 모든 서비스 수정 필요
2차common-event 모듈로 중앙화팩터리 메서드 위치 문제, 계층 경계 붕괴, 모듈 비대화
임시private 생성 메서드근본 해결 아님
예정Mapper 도입계층 경계 유지 + 변환 책임 분리
profile
응집력있는 시간을 보내기 위한 블로그

0개의 댓글