Hexagonal Architecture 정리

Hexagonal Architecture 의 각 컴포넌트 별 책임과 주의 사항에 대해 정리해보겠다..

clean architecture 에서는 Application layer 와 Domain layer 를 합쳐서 Application Core 라고 부른다.

Application 은 port 와 Use case, Service 가 존재하는 layer 를 뜻하나.. Domain이 포함된 개념으로 불리기도 함.

 

Application

유스케이스 책임

1. 입력을 받는다.

2. 비즈니스 규칙을 검증한다. (도메인 엔티티와 책임 공유)

3. 모델 상태를 조작한다.

4. 출력을 반환한다.

 

각 유스케이스 마다 다른 입력/출력 모델을 가져가도록 해보자..

각 입력 모델은 입력 유효성 검증 책임을 가진다.

 

주의)

입력 유효성과 비즈니스 규칙 검증의 구분은 도메인의 현재 상태에 접근할 필요가 있는지 여부를 본다.

 

 

 

Adapter

Web Adapter 책임

1. HTTP 요청을 자바 객체로 매핑

2. 권한 검사

3. 입력 유효성 검증 (유스케이스 입력 모델로 매핑을 방해하는 유효성 검증)

4. 입력을 유스케이스의 입력 모델로 매핑

5. 유스케이스 호출

6. 유스케이스의 출력을 HTTP로 매핑

7. HTTP 응답을 반환

 

컨트롤러는 가능한 좁게..

다른 컨트롤러와 로직을 가능한 적게 공유하는 웹 어댑터 조각을 구현하자.

(각 연산에 대해 가급적 별도의 패키지 안에 별도의 컨트롤러로 메서드와 클래스명은 유스케이스를 최대한 반영)

 

Persistence Adapter 책임

1. 입력을 받는다.

2. 입력을 데이터베이스 포맷으로 매핑

3. 입력을 데이터베이스로 보낸다.

4. 데이터베이스 출력을 애플리케이션 포맷으로 매핑

5. 출력을 반환

 

DDD의 Aggregate 당 하나의 Persistence Adapter를 구현하는 방식으로 생각해보자..

나중에 Bounded context 의 영속성 요구사항을 분리하기 위한 좋은 토대가 될 수 있다.

 

주의)

서로 다른 Bounded context 이며,

서로의 무엇인가를 필요로 한다면 전용 인커밍 포드를 통해 접근해야한다.

(A Bounded Context의 Application 에서 B Bounded Context의 out port 를 접근하지 말 것)

 

 

Hexagonal Architecture Test

단위 테스트

- 각 컴포넌트(Domain Entity, Adapter, Service 등) 를 테스트 하는데 이용

- 하나의 클래스를 인스턴스화 하고 해당 클래스의 인터페이스를 통해 기능들을 테스트한다.

- 다른 클래스를 의존한다면 의존되는 클래스들은 Mock 으로 대체한다.

 

통합 테스트

- 연결된 여러 유닛을 인스턴스화 하고 시작점이 되는 클래스의 인터페이스로 데이터를 보낸 후

유닛들의 네트워크가 기대한 대로 동작되는지 검증

-  두 계층 간의 경계를 걸쳐서 테스트 할 경우 객체 네트워크가 완전하지 않을 수 있으므로 Mock으로 대체 할 수 있다.

 

시스템 테스트

- 애플리케이션을 구성하는 모든 객체  네트워크를 가동 시켜 특정 유스케이스가 전 계층에서 잘 동작하는지 검증

 

주의)

테스트 가독성을 높이기 위해 행동-주도 개발 에서 일반적으로 사용되는 방식인

given/when/then 섹션으로 나눠 보자..

 

상황 별 매핑 방식

각 계층의 모델을 매핑하는 문제에 대해 알아보자..

 

각 계층의 모델을 각각 두고 계층을 이동 할 때 마다 매핑하면..?

 

장점 :

양 계층에서 같은 모델을 사용하지 않기 때문에 느슨한 결합

 

단점 : 

보일러플레이트 코드가 많아진다.

 

 

1. 매핑하지 않기 전략(No Mapping)

단순 CRUD 유스케이스의 경우에 모든 계층의 모델을 동일하게 가져가는 매핑하지 않기 전략을 고려해보자.

 

포인트 : 비즈니스 요구사항이 복잡해지면 당장 구조를 바꿔야한다.

 

 

2. 양방향 매핑 전략(Two-Way Mapping)

Adapter 와 Application 계층이 각각 모델을 가지는 방식이다.

두 계층간 이동을 할 때는 매핑을 해줘야한다.

 

포인트 : adapter 계층에서 도메인 모델의 의존성을 가진다.

(의존성 방향에 위배되지는 않지만 도메인 로직의 누수가 그만큼 있는 것)

 

 

3. 완전 매핑 전략(Full Mapping)

도메인 모델을 Adapter layer 로 누수를 완전히 막는 방식이다.

 

포인트 : 보일러플레이트 코드가 엄청 많아진다.

 

 

4. 단방향 매핑 전략(One-Way Mapping)

DDD의 Factory 개념(어떤 특정한 상태로 부터 도메인 객체를 재구성할 책임을 가짐)과 잘 어울릴 수 있다.

 

 

설정 컴포넌트의 책임

- 웹 어댑터 인스턴스 생성

- HTTP 요청이 실제로 웹 어댑터로 전달 되도록 보장

- 유스케이스 인스턴스 생성

- 웹 어댑터에 유스케이스 인스턴스 제공

- 영속성 어댑터 인스턴스 생성

- 유스케이스에 영속성 어댑터 인스턴스 제공

- 영속성 어댑터가 실제로 데이터 베이스에 접근 할 수 있도록 보장

 

따라서.. 설정 컴포넌트는 맨 바깥 계층에 해당하며..

Hexagonal Architecture 의 모든 컴포넌트에 대한 의존성을 가지는 것이다.

-> SRP (변경할 이유가 하나!) 를 위반하는 것이지만,

애플리케이션의 나머지 부분을 깔끔하게 관리하고 유지하려면 필요하다..

 

 

도메인이 왕이다.

외부의 영향을 받지 않고 도메인 코드를 자유롭게 발전시킬 수 있다는 것은

Hexagonal Architecture 스타일이 내세우는 가장 중요한 가치이다.

-> DDD 와 잘 어울릴 수 있는 바탕이 됨

 

하지만,

Hexagonal Architecture 는 Domain layer 에 대한 강력한 제약을 가지진 않는다.

 

DDD 철학을 따르는 풍부한 도메인 모델을 구현할 것인지,

빈약한 도메인 모델을 구현할 것인지는 개발자의 선택이다.

 

'대규모 시스템 설계' 카테고리의 다른 글

시스템 설계 고민 1  (0) 2023.06.08
Event-Driven 아키텍처 와 Pub/Sub 모델  (0) 2023.06.03
CQRS Pattern  (0) 2023.02.15
Saga Pattern (feat. MSA)  (0) 2023.02.13
Two Phase Commit (feat. MSA)  (0) 2023.02.13