비즈니스 흐름이 보이는 곳에서 이벤트 발행하기
완료된 비즈니스 행위를 이해하는 계층에서 이벤트를 발행하되, 구현과 밀접한 예외는 명시적으로 구분하는 방법을 다룬다.
출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다.
gpt-5.6-sol모델을 사용해 생성·편집했습니다.
주문이 완료됐고 시스템은 이벤트를 발행해야 한다. 엔티티의 상태가 바뀌는 순간 발행할 수도 있고, 구현의 일부로 하위 프로세서가 발행할 수도 있으며, 전체 작업이 끝난 뒤 비즈니스 서비스가 발행할 수도 있다. 세 위치 모두 구현은 가능하다. 하지만 비즈니스 흐름을 똑같이 잘 보여 주는 것은 아니다.
내 기본 선택은 비즈니스 행위를 볼 수 있는 계층에서 비즈니스 이벤트를 발행하는 것이다. 이벤트가 주문 완료를 뜻한다면 주문 완료를 책임지는 코드에서 보내는 편이 대체로 가장 명확하다. 이는 코드의 가시성을 기준으로 한 선호이지, 모든 예외를 없애는 규칙은 아니다.
주변 구현을 모르는 상태에서는 답도 조건부여야 한다. 이벤트의 목적과 이를 받는 컴포넌트에 따라 다른 위치가 더 적합할 수 있다.
엔티티는 이 결정을 내리기에 너무 낮을 수 있다
상태 전이가 엔티티에서 일어나므로 그곳에서 이벤트를 발행하는 구조는 그럴듯해 보인다. 그러나 그렇게 하면 엔티티는 자기 상태를 바꾸고 지키는 일보다 더 많은 책임을 맡는다. 엔티티를 데이터베이스에 가까운 계층으로 내리고 엔티티 역할에만 집중하게 했다면 이벤트 발행은 더 어색해진다. 그 위치에서는 이벤트를 보내야 하는 비즈니스 이유가 보이지 않기 때문이다.
그래서 나는 엔티티 내부에서 이벤트를 발행하는 구조를 선호하지 않는다. 엔티티를 더 넓은 비즈니스 흐름 아래로 내린 뒤에는 특히 복잡도가 높아진다. 엔티티는 상태가 바뀌었다는 사실을 알지만, 비즈니스 계층은 왜 바뀌었으며 이 유스케이스에서 완료가 무엇을 뜻하는지 안다.
발행 위치를 위로 올리면 모델을 조금 손봐야 할 수도 있다. 현재 프로세서가 식별자 하나만 반환하는데 이벤트에는 처리 결과의 다른 정보도 필요하다면 반환 타입을 바꿔야 한다. 이것은 발행을 아래에 숨겨야 할 이유가 아니다. 상위 흐름이 자신이 조정하는 결과를 정직하게 표현해야 한다는 신호에 가깝다.
발행 위치와 발행 구현을 분리한다
비즈니스 계층에서 발행한다고 해서 프레임워크의 퍼블리셔를 그 계층 전체에 직접 노출할 필요는 없다. 스프링의 애플리케이션 이벤트 퍼블리셔를 그대로 주입하면 안정적인 비즈니스 흐름을 표현하려는 코드에서 프레임워크 구현이 지나치게 두드러진다.
나는 보통 그 앞에 작은 컴포넌트를 둔다. 애플리케이션이 소유하는 이벤트 퍼블리셔일 수도 있고, 비즈니스 행위에 맞춰 이름을 붙인 협력자일 수도 있다. 비즈니스 서비스는 그 컴포넌트에 의존하고, 실제 발행 방식은 컴포넌트가 처리한다. 이후 시스템이 분리되거나 전달 방식이 달라질 때도 경계가 더 명확하다.
이름은 독자가 행위를 이해하는 데 도움을 줘야 한다. 범용적인 EventPublisher만으로는 여전히 무엇을 하는지 거의 드러나지 않을 수 있다. 좋아요 흐름이라면 프레임워크 API를 노출하는 것보다 좋아요 이후의 동작을 표현하는 협력자가 더 유용할 수 있다. 구체성의 정도는 코드에 따라 달라지지만, 비즈니스 결과를 알리기 위해 비즈니스 계층이 반드시 프레임워크 용어로 말할 필요는 없다.
한 애플리케이션의 흐름을 이벤트 체인으로 숨기지 않는다
여러 도메인이 하나의 결과를 받아 처리해야 하는 경우처럼 애플리케이션 이벤트가 적합한 상황은 분명히 있다. 문제는 한 애플리케이션 안의 모든 단계를 연결하는 기본 수단으로 이벤트를 쓰기 시작할 때 생긴다.
주문 상태를 바꾼 뒤 문자 알림도 보내야 한다고 가정해 보자. 애플리케이션 이벤트를 발행하고, 다른 곳의 리스너가 이를 받아 발송기를 호출하게 만들 수 있다. 또는 눈에 보이는 비즈니스 흐름에서 SmsSender나 역할이 분명한 후처리 컴포넌트를 직접 호출할 수도 있다. 두 번째 구조는 추상화가 덜해 보일 수 있지만, 독자는 이벤트를 받을 모든 리스너를 찾지 않고도 작업의 순서를 따라갈 수 있다.
한 리스너가 다시 이벤트를 발행해 애플리케이션 전체가 간접 반응의 체인이 되면 이 비용은 더 커진다. 하나의 작업을 이해하려고도 유지보수자는 모든 발행 지점과 핸들러를 찾아 순서를 재구성해야 한다. 그렇다고 이벤트 기반 코드 자체가 잘못이라는 뜻은 아니다. 간접화가 주는 이점이 눈에 보이는 호출 경로를 잃는 비용보다 커야 한다는 뜻이다.
이벤트를 보내는 이유로 계층을 고른다
실용적인 구분 기준은 이벤트가 존재하는 이유다.
- 완료된 비즈니스 행위를 나타낸다면 그 흐름을 소유한 비즈니스 서비스나 계층에서 발행한다.
- 발행이 구현 세부사항과 밀접하다면 하위 프로세서가 더 정확한 위치일 수 있다.
- 여러 도메인이 실제로 그 결과를 받아 처리해야 한다면 애플리케이션 이벤트가 협업 방식에 맞을 수 있다.
어느 경우든 두 가지를 더 묻고 싶다. 코드가 비즈니스를 명확하게 보여 주는가. 다음에 일어나는 일을 파악하려고 애플리케이션 전체를 뒤지지 않아도 되는가. 일반적인 상황에서는 완료된 비즈니스 행위 옆에 발행 코드를 두는 것으로 두 질문에 잘 답할 수 있다. 그렇지 않은 경우의 예외는 모든 반응을 이벤트 버스에 올리는 습관이 아니라, 실제 구현과 협업의 필요에서 나와야 한다.