-
[Architecture] 헥사고날 아키텍처(Hexagonal Architecture) (ft. 계층형 아키텍처, 클린 아키텍처, DDD)DEV/ETC 2024. 6. 5. 00:12
헥사고날 아키텍처를 설명하기전에 계층형 아키텍처와 클린 아키텍처, 그리고 도메인 주도 설계(DDD)관련하여 가볍게 짚고 넘어가야한다. 헥사고날 아키텍처는 전통 방식인 계층형 아키텍처의 단점을 보완하기위해 설계되었다.
계층형 아키텍처의 문제점
1) 데이터베이스, 영속성에 대한 의존성
도메인 계층이 데이터베이스에 의존하게 되어 데이터베이스에 변화가 일어나면 도메인 계층에도 변화가 생긴다.
서비스 계층에서도 영속성 모델을 도메인 모델처럼 사용하게 된다. 그렇다면 즉시로딩, 지연로딩, 트랜잭션, 플러시 등을 고려해야하고 영속성에 대한 의존이 프로젝트 전체적으로 퍼지게 되어 변경에 취약해진다.
2) 아키텍처 경계를 강제할 수 없다.
계층형 아키텍처에서는 상위 계층에 있는 컴포넌트에 접근할 목적으로 해당 컴포넌트를 하위 계층으로 내릴 수 있다. 이러한 행위가 반복되면 점점 경계가 모호해지고 허물어지게 된다.
3) 계층을 스킵 할 수 있다.
계층을 건너뛰는 것이 가능하다. 구현이 간단한 경우 Controller에서 바로 도메인을 참조하는 등의 로직을 작성할 수 있다. 이 경우의 문제는 기능 확장이 어렵고, 테스트가 복잡해진다.
4) 유스케이스를 숨긴다.
개발자는 유스케이스를 어느 계층에나 자유롭게 생성할 수 있다. 이는 개발자가 유스케이스의 존재 여부를 파악하기 어려워서 동일한 로직을 다른 위치에 새롭게 구현하여 코드를 더럽히게 된다.
5) 서비스의 크기를 강제할 수 없다.
계층형 구조에서는 서비스의 크기를 강제하지않는다. 수십개의 서비스 로직을 한곳에 전부 작성할 수도 있다. 이 경우에 서비스가 너무 많은 의존을 가져 수많은 웹 계층이 해당 서비스를 의존하게 된다. 결국 서비스를 테스트하기 어려워지고 작업해야할 유스케이스를 찾기도 힘들어진다.
클린 아키텍처
클린 아키텍처는 비즈니스 규칙이 외부로부터 독립적으로 만들어 테스트를 용이하게 하고, 비즈니스 규칙이 외부의 영향을 받지 않는다.
- 도메인 코드가 바깥으로 향하는 어떠한 의존성도 없어야 한다. 모든 의존성은 안쪽을 향하고 있다.
- 도메인은 어떤 영속성 프레임워크가 사용되는지 알 수 없다.
- 영속성 계층이 도메인 계층에 의존하고, 영속성 엔티티를 도메인 엔티티로 변환하는 과정이 필요하다.
- 도메인 밖에는 유스케이스가 있고, 이는 단일 책임을 갖기 위해 조금 더 세분화 되어 있다.
( ex. UserService ⇒ RegisterUserService )
헥사고날 아키텍처
헥사고날 아키텍처는 이러한 클린 아키텍처를 일반화한 구조 중 하나이며, 유지보수성과 확장성을 높이기 위해 고안되었다. 어댑터와 포트를 중심으로 구성되어 포트와 어댑터 아키텍처로 불리기도 한다. 이를 통해 비즈니스 로직을 외부로부터 격리시켜 외부 시스템과 의존성을 최소화한다.
- 핵심 비즈니스 로직은 중앙의 도메인 영역에 위치하며, 입출력을 처리하는 포트와 어댑터를 통해 외부와 소통한다.
- 도메인 로직과 인프라 스트럭처 계층을 명확히 분리함으로써 변경 사항이 도메인 로직에 미치는 영향을 줄일 수 있다.
- 해당 구조는 유지보수를 용이하게하여 시스템의 다양한 부분을 독립적으로 개발하고 테스트할 수 있는 환경을 제공한다.
- 헥사고날 아키텍처는 도메인 중심 설계와 밀접하게 연관되어있어 비즈니스 로직의 중심성을 강조한다.
헥사고날 아키텍처와 도메인 주도 설계(DDD)와 연관관계?
도메인 주도 설계는 소프트웨어 개발에서 비즈니스 도메인의 복잡성을 관리하는 방법론이다.
헥사고날 아키텍처와 DDD는 서로 보완적인 관계에 있다. DDD는 도메인 모델 중심으로 시스템을 설계하고, 헥사고날 아키텍처는 이러한 도메인 모델을 효과적으로 구현할 수 있는 구조를 제공하기 때문이다.
DDD를 실제로 구현할 때에는 크게 3가지 Layer로 구분하는 것이 핵심이다.
- Application Layer: 주로 도메인과 Repository를 바탕으로 실제 서비스(API)를 제공하는 계층
- Domain Model Layer: Entity를 활용해 도메인 로직(비즈니스 로직)이 수행되는 계층
- Infrastructure Layer: 외부와 통신(RDBMS, Redis, HttpClient, ...)을 담당하는 계층
DDD를 적용함으로써 개발자는 비즈니스 로직을 더 깊이 이해할 수 있다.
이 과정에서 헥사고날 아키텍처는 도메인 로직과 인프라 스트럭처 사이의 결합도를 낮추는 역할을 한다. 도메인 로직을 중심으로 시스템을 구성하며 이를 통해 요구사항의 변경이 시스템 전체에 미치는 영향을 최소화할 수 있기 때문이다.
헥사고날 아키텍처 주요 컴포넌트
Adapter
포트를 통해 인프라와 실제로 연결하는 부분을 담당하는 구현체를 의미
Adapter는 크게 두 종류로 구분된다.
이름 설명 예시 Driving Adapter
= Primary Adapter사용자의 요청을 받아들일 때 사용되는 Adapter AWS Lambda의 Handler
WebApplication의 ControllerDriven Adapter
= Secondary Adapter도메인 모델의 처리에 사용되는 Adapter MessageQueue, Persistence Adapter Port
서비스(또는 usecase)에 어댑터에 대한 명세만을 제공하는 계층
단순히 인터페이스 정의만 존재하며, DI를 위해 사용된다.
Application Service(usecase)
어댑터를 주입 받아 도메인 모델과 어댑터를 적절히 오케스트레이션하는 계층을 의미
Domain Model
DDD의 도메인 모델과 동일한 개념을 지닌 계층
비즈니스 로직이라 부르는 엔티티에 대한 변경은 모두 해당 계층에서만 실행된다.
원칙적으로는 어떠한 의존성도 없어야 하지만 Entity를 만들 때 Database에 적재되어 있는 데이터를 참고해야하는 경우와 같은 상황에서는 Port를 이용해 Adapter를 주입받아서 사용할 수 있다는 예외사항이 존재하기도 한다.
이는 클린 아키텍처와 동일하다.
헥사고날 아키텍처 장단점
장점
- 유연성 : 외부 시스템이나 인프라와의 의존성이 낮아 구성요소를 쉽게 교체하거나 업데이트가 가능
- 테스트 용이성 : 비즈니스 로직을 독립적으로 테스트할 수 있음
- 유지보수성 : 책임이 분리되어 있어 코드의 이해와 수정이 용이
단점
- 구현 복잡성 : 포트와 어댑터를 구성하고 관리하는데 복잡함
- 초기 개발 시간 증가
Why DDD, Clean Architecture and Hexagonal ?
결론적으로 DDD, Clean Architecture, Hexagonal Architecture에서 가장 중요한 것은 명확한 관심사의 분리이다.
- 외부와의 연결에 문제가 생기면 Adapter를 확인
- 인터페이스의 정의를 변경하고자 한다면 Port를 확인
- 처리 중간에 측정을 위해 이벤트를 보내거나 트레이스를 로그를 심고 싶다면 Service(usecase)를 확인
- 비즈니스 로직이 제대로 동작하지 않는다면 Domain Model 확인
이러한 구조는 결국 쉬운 테스트를 가능하게 해주기도 한다.
본인의 역할을 수행하기 위해 필요한 Port만 모킹하여 테스트를 쉽게 수행할 수 있다.
패키지 구조
출처 : 만들면서 배우는 클린 아키텍처
payment-system ㄴ account ㄴ adapter ㄴ in ㄴ web ㄴ AccountController ㄴ out ㄴ persistence ㄴ AccountPersistenceAdapter ㄴ SpringDataAccountRepository ㄴ domain ㄴ Account ㄴ Activity ㄴ application ㄴ SendMoneyService ㄴ port ㄴ in ㄴ SendMoneyUseCase ㄴ out ㄴ LoadAccountPort ㄴ UpdateAccountStatePort
최상위에는 Account와 관련된 UseCase를 구현하는 모듈인 account 패키지가 있고,
다음 레벨에는 adapter, domain, application 패키지가 있다.
application 패키지는 도메인 모델을 둘러싼 서비스를 포함한다.
- SendMoneyService는 인커밍 포트 인터페이스인 SendMoneyUseCase를 구현한다.
- SendMoneyService는 아웃고잉 포트 인터페이스이자 영속성 어댑터에 의해 구현되는 LoadAccountPort와 UpdateAccountStatePort를 사용한다.
adapter 패키지는 application 계층의 인커밍 포트(SendMoneyUseCase)를 호출하는 인커밍 어댑터와 아웃고잉 포트(LoadAccountPort, UpdateAccountStatePort)에 대한 구현을 제공하는 아웃고잉 어댑터를 포함한다.
자세한 패키지 구조 및 코드 확인은 아래에서 확인하길 바란다.
https://github.com/wikibook/clean-architecture
참고포스팅
https://yozm.wishket.com/magazine/detail/1813/
https://6mini.github.io/software%20architecture%20pattern/2023/01/31/monolith-micro/
https://tech.osci.kr/hexagonal-architecture/
https://cantcoding.tistory.com/107
'DEV > ETC' 카테고리의 다른 글
소프트웨어 테스트 품질 (0) 2024.07.08 소프트웨어 테스트 원칙과 유형 (0) 2024.07.04 [Git] git 커밋 메시지 규칙 (0) 2024.06.12 [Architecture] MA와 MSA 개념 / 장단점 / 차이점 (0) 2024.05.30 [JWT] JSON WEB TOKEN 개념 / 구성 요소 / 동작 방식 / 장단점 / Session과 차이점 / 저장 위치 (0) 2024.05.30