모의 주식 투자 서비스를 만들면서 처음으로 아키텍처를 선택해야 했다. 근데 "어떤 게 더 좋냐"가 아니라 "어떤 상황에 무엇을 쓰냐"가 진짜 질문이었다.
대부분의 Spring Boot 입문 프로젝트가 이 구조다.
위에서 아래로 단방향으로 의존한다. 각 계층은 바로 아래 계층만 바라본다. 단순하고 직관적이다.
단순한 만큼 결합도에 대한 고민이 생긴다. Service가 Repository에 직접 의존하는 구조이기 때문이다.
단순한 CRUD라면 괜찮다. 문제는 외부 연동 방식이나 인프라 구현 세부사항이 Service의 관심사까지 들어오기 시작할 때다. 그 순간부터 인프라 쪽에 변화가 생기면 Service도 같이 흔들린다.
프로젝트를 하면서 유저 서비스에서 실제로 이 고민을 했다. Repository 쪽에 문제가 생기면 Service 레이어까지 영향이 오는 상황이 신경 쓰였다. 당시엔 이게 왜인지 설명은 못 했는데, 지금 생각해보면 의존 방향 때문이었다.
Ports and Adapters 패턴이라고도 불린다. 구조가 다르다.
레이어드 vs 헥사고날 구조 비교는 happycoders.eu - Hexagonal Architecture 에 원본 기반으로 잘 정리돼 있다.
레이어드와 결정적으로 다른 게 하나 있다. 모든 의존이 도메인(Service)을 향한다.
레이어드에서는 Service가 Repository를 부른다. 헥사고날에서는 반대다. Repository가 Service가 정의한 인터페이스(Port)를 따라간다. Service는 DB가 뭔지, 외부 API가 어느 업체인지 전혀 모른다.
이걸 의존성 역전(DIP, Dependency Inversion Principle) 이라고 한다. 객체지향의 SOLID 원칙 중 하나인데, 핵심은 "고수준 모듈(도메인)이 저수준 모듈(인프라)에 의존하면 안 된다. 둘 다 추상화(인터페이스)에 의존해야 한다"는 것이다.
헥사고날은 이 원칙을 아키텍처 레벨에서 구조화한 거다.
모의 주식 투자 서비스를 MSA로 구성했다. 여러 서비스 중 stock-service에만 헥사고날을 적용하고, 나머지는 레이어드로 갔다.
선택 기준은 단순했다. 외부 의존성이 도메인의 핵심인가.
stock-service는 실시간 시세를 외부 증권 API에서 받아온다. 외부 의존성 자체가 서비스의 존재 이유다. 레이어드로 짜면 Service 코드 안에 특정 API 호출 코드가 박혀있고, 업체가 바뀌거나 테스트할 때 진짜 API를 못 쓰면 Service를 직접 건드려야 한다.
헥사고날로 짜면 Service는 "시세 줘" 라는 Port(인터페이스)만 선언한다. 실제로 어느 API를 붙이는지는 어댑터가 담당한다. 외부 API가 바뀌어도 어댑터만 교체하면 된다.
반면 나머지 서비스들은 DB 읽고 쓰는 게 주된 역할이었다. 외부 의존성이 핵심이 아니었으니 레이어드가 훨씬 단순하고 빠르게 짤 수 있었다. 헥사고날로 짰다면 Port 인터페이스 선언하고 어댑터 만들고 — 복잡도만 올라갔을 거다.
같은 프로젝트 안에서도 서비스마다 다르게 가도 된다. 실제로 그게 맞을 때가 많다.
결국 두 아키텍처의 차이는 계층 수에 있지 않다. 결합의 방향에 있다. 레이어드는 의존이 위에서 아래로 흐르고, 헥사고날은 모든 의존이 안쪽 도메인을 향해 수렴한다. 레이어드는 명확성과 빠른 개발을 주고, 헥사고날은 외부 의존성으로부터 도메인을 격리한다.
레이어드가 맞는 경우
헥사고날이 맞는 경우
처음엔 헥사고날이 더 정교한 구조니까 더 좋은 거 아닌가 싶었다. 근데 직접 프로젝트를 하면서 보니 모든 서비스에 필요한 건 아니었다. 결국 중요한 건 어떤 아키텍처를 쓰느냐보다, 왜 그 구조를 선택했는지 설명할 수 있느냐였다.