모놀리식 -> 헥사고날 전환 도메인 경계 문제 해결
문제발생
비밀로그 프로젝트를 헥사고날 아키텍처로 정리하는 과정에서 가장 먼저 눈에 들어온 문제는 도메인 경계의 침범이었다. 회원가입과 로그인 흐름을 담당하는 auth 도메인의 AuthDataAdapter가 알림 도메인의 구현체인 FcmTokenRepository와 FcmToken 엔티티를 직접 사용하고 있었고, 이로 인해 FCM 토큰의 저장과 삭제까지 auth가 떠맡고 있었다. 더 나아가 포트 설계도 이 결합을 부추겼다. ManageAuthDataPort가 본래 인증 데이터만 다뤄야 함에도 String fcmToken을 파라미터로 받으며 책임이 섞여 있었다. 심지어 JWT에도 fcmTokenId를 클레임으로 넣고, JwtFilter에서 이를 꺼내 재발급에 사용하면서 인증과 알림이 구조적으로 강결합된 상태였다. 한마디로 의존 방향이 auth -> notification으로 새고 있었고, 헥사고날의 원칙을 무시하고 있었다.
고민
해결책의 방향은 두 가지가 떠 올랐다. 하나는 이벤트 기반의 비동기 분리였고, 다른 하나는 동기 포트 호출로 “기기 토큰 등록”을 별도의 Out-Port로 끊어내는 방식이었다. 전자는 auth가 “토큰을 어떻게 저장할지”를 전혀 알지 못한 채 그저 “토큰이 생겼다”는 사실만 알리는 모델이다. 후자는 auth가 자신의 포트를 통해 “토큰 등록을 요청”하면 인프라 계층에서 적절히 notification 저장소를 이용해 처리해 주는 절충안이다. 흐름을 자세히 들여다보니, 굳이 로그인 시점에 fcmTokenId를 즉시 JWT에 실어야 할 이유는 크지 않았다. 발송 시점에 userId로 토큰을 조회해도 충분했고, 그 편이 훨씬 느슨한 결합을 보장했다. 결국 난 이벤트 기반을 선택했다. 인증은 인증만, 알림은 알림만 알도록 만들자는 방향성이 조직의 장기 유지보수성과도 맞닿아 있었기 때문이다.
해결
첫 단계는 경계부터 바로 세우는 일이었다. ManageAuthDataPort에서 fcmToken 파라미터를 제거해 인증 포트가 타 도메인의 데이터를 취급하지 않도록 정리했다. 이어 AuthDataAdapter에서 FcmTokenRepository 의존을 끊고, 가입/로그인 시 FCM 토큰을 직접 저장하던 로직과 로그아웃/탈퇴 시 FCM 토큰을 직접 지우던 로직을 모두 제거했다. 반대로 알림 쪽에는 이벤트를 받아 스스로 처리하는 유입점을 만들었다. FcmTokenRegisteredEvent(userId, fcmToken)라는 이벤트를 신설하고, notification 도메인에 FcmTokenEventListener를 두어 이 이벤트를 받아 FcmToken을 저장하도록 했다. 이미 존재하던 UserLoggedOutEvent, UserWithdrawnEvent도 수신해 해당 유저의 FCM 토큰을 정리하게 했다. 이렇게 하면 인증 흐름에서는 “토큰 등록”이라는 사실만 방송하고, 실제 저장/삭제는 알림 도메인이 알아서 책임진다.
이벤트 방식으로 바꾸며 부수적인 정리도 함께 이뤄졌다. JWT에서 fcmTokenId 클레임을 제거했고, JwtHandler의 getFcmTokenIdFromToken 메소드와 이를 사용하던 JwtFilter의 관련 코드도 정리했다. ClientDTO의 fcmTokenId는 선택적 속성으로 낮춰, 기존과의 호환성을 유지하면서도 새로운 흐름을 방해하지 않도록 했다. 서비스 레벨에서는 UserAccountService가 회원가입 완료 후, SocialLoginService가 기존 사용자 로그인 완료 후 각각 FcmTokenRegisteredEvent를 발행하도록 바꿨다. 이렇게 리듬을 맞추니 도메인 경계가 자연스럽게 제자리를 찾았다. 이렇게 도메인 간 결합을 잘라내면서도 기존 기능을 해치지 않는 선에서 흐름을 재조정했다.
이번 정리를 통해 엔티티 배치에 대한 감각이 생긴 것 같다. Token은 사용자 인증 자산이므로 user 도메인에 있는 것이 맞고, FcmToken은 알림 전송을 위한 자산이니 notification에 있어야 한다. UserRole과 SocialProvider는 변동 가능성이 낮고 여러 도메인이 참조하는 안정된 값이므로 global에 두는 편이 실용적이다. 결국 핵심은 이름이나 위치 그 자체가 아니라, 의존 방향이 단방향으로 잘 흐르느냐, 그리고 각 도메인이 자신의 책임만 알고 있느냐다.