Search

좌충우돌 헥사고날 멀티 모듈 도입기

Tags

1. 개요

프로젝트를 진행하면서 잘 설계된 소프트웨어 아키텍쳐에 대한 야망이 있었는데요, 오늘은 뉴스레터 서비스를 멀티 모듈 아키텍쳐에 헥사고날를 어떻게 적용했는지 그 과정을 훑어보도록 하겠습니다.

2. 기존의 멀티 모듈 구조

호기롭게 시작한 멀티 모듈 구조는 다음과 같았습니다. 프론트와 API 통신을 담당하는 api-server와 일정시간 마다 크롤링을 수행하는 batch 모듈이 있었고, 두 모듈은 모두 domain과 core를 의존하고 있었습니다.
위 구조에서 헥사고날을 구현하려고 하다 보니 외부로의 접근은 api-server 모듈에서 처리하고 내부의 로직은 domain에서 처리하는 구조가 되었습니다.
헥사고날 아키텍쳐에서 주로 사용하는 패키지 구조는 adapter와 port인데요, adapter 패키지에는 Controller, Event 등 외부에서 내부로 흐르는 로직을 구현하거나 내부에서 외부로 나가는 데이터베이스를 구현하고, Port는 Usecase를 정의하고 Service 코드를 구현하는 형태로 많이 구성됩니다.
여기서 두 번째 문제가 발생합니다.

3. 순환 의존성

방금전에 말씀드렸다 싶이 api-server 모듈에는 Controller, Repository 등이 구현되었고 domain에는 Service 클래스가 구현되었는데요, Controller는 Service를 통해 내부 비즈니스 로직을 수행함으로 api-server는 domain 모듈을 의존할 수 밖에 없었으며, 반대로 Service는 Repository를 의존함으로 domain 모듈 또한 api-server을 의존할 수 밖에 없었습니다.
api-server <-> domain
순환 의존성은 무엇일까요?
둘 이상의 클래스나 빈이 서로를 참조하는 상황을 의미합니다. 객체 생성 시점에서 무한루프에 빠지게 되어 에러를 발생시킬 수 있습니다.
저 또한 무한 루프를 겪어 에러를 맞이했었고, 순환 의존성을 끊어낼 필요가 있었습니다.

4. 새로운 멀티 모듈 아키텍쳐

이를 해결 하기 위해 새로운 모듈을 만들고 코드를 재배치하는 작업을 거쳤습니다. 우선 그 전과 동일하게 api-server, batch 모듈 모두 나머지 세 개의 모듈을 의존합니다. 여기에서 달라진 점은 api-server는 더 이상 양방향 의존 관계가 아닌 단반향 의존 관계를 가지고 있다는 점입니다.
즉, api-server -> application -> domain, core 로 이루어지는 구조입니다.

4-1. 모듈 별 가져야 하는 책임

api-server
헥사고날 아키텍쳐의 Web, Event 등이 정의됩니다.
Security, Swagger 등의 설정 파일이 포함 될 수 있습니다.
나머지 네 개의 모듈을 의존합니다.
Application
Interface로 정의된 UseCase, Port등의 구현체가 정의되는 곳입니다.
domain, core 모듈을 의존합니다.
Domain
해당 모듈은 오로지 도메인에 집중합니다.
Domain과 Domain Service 등이 정의될 수 있습니다.
Domain Service에는 ‘트랜잭션의 단위’를 정의했고, ‘요청 데이터를 검증’ 했으며, ‘이벤트를 발생’하는 일 등 도메인의 비지니스를 작성합니다.
POJO로 작성합니다.
Core
시스템 내 모든 모듈들이 의존할 수 있을만큼 얇은 의존성을 제공합니다.
마찬가지로 POJO로 모든 클래스가 작성됩니다.
Usecase, Port 등의 인터페이스와 DTO 등이 정의됩니다.
대략적인 패키지 구조는 다음과 같습니다.

5. 변환 과정에서 얻었던 것

우선 adpater.in , port.out 등의 패키지 구조에 너무나 의존했던게 화두였습니다. 앞서 말씀 드렸듯 adapter 패키지 내의 구현체와 port 패키지 내의 서비스들은 서로 의존하는데 이를 단순히 나누기만 하고 의존성을 관리하지 못하였습니다.
패키지 구조는 결국 프로그램을 짜기 위해 효율적인 하나의 방법에 불과하지 너무 틀에 박혀 패키지 구조를 구성하다 보면 여러 오류가 발생할 수 있다는 사실을 깨닫고, 유동적으로 패키지를 구성하기 시작했습니다.
결과적으로 모든 구현체를 application에 구현하였지만 interface를 통해 의존 관계를 맺음으로써 입력과 출력을 처리하는 포트와 어댑터를 통해 외부와 소통하는 헥사고날 원칙을 지킬 수 있었습니다.