DDD(1) - 아키텍처 패턴(계층형 / 포트와 어댑터 / CQRS)
in DEV on DDD, Architecture-pattern, Layered-architecture, Oltp, Cqrs, Olap
지금까지는 전술적 패턴을 통해 비즈니스 로직을 모델링하고 구현하는 다양한 방법에 대해 알아봤다면 이 포스트에서는 좀 더 높은 수준으로 관점을 옮겨서 구성 요소를 구조화하는 3가지 아키텍처 패턴에 대해 알아본다.
즉, 시스템의 구성 요소 간 상호 작용과 의존성을 조율하는 다양한 방법을 살펴본다.
- 계층화된 아키텍처
- 포트와 어댑터
- CQRS
각 아키텍처 패턴의 본질과 각 패턴을 사용해야 하는 사례에 대해 알아본다.
목차
- 1. 비즈니스 로직과 아키텍처 패턴
- 2. 계층형 아키텍처(Layered architecture)
- 3. 포트와 어댑터
- 4. CQRS(Command-Query Responsibility Segregation)
- 5. 범위
- 정리하며..
- 참고 사이트 & 함께 보면 좋은 사이트
1. 비즈니스 로직과 아키텍처 패턴
소프트웨어 시스템은 비즈니스 로직 외에 다른 요소들도 많다.
코드베이스는 기능/비기능 요구사항을 구현하기 위해 많은 책임을 담당한다.
코드베이스가 처리해야 할 다양한 관심사로 인해 비즈니스 로직이 다양한 구성 요소로 흩어지기 쉽다.
관심사를 구현할 때 엄격하게 구성하지 않으면 코드베이스의 변경이 어려워진다.
예) 비즈니스 로직이 변경될 때 코드베이스의 어떤 부분이 영향을 받는지 분명하지 않을 수 있거나, 겉으로 보기에 관련없는 부분에 기대치않게 변경이 영향을 미칠 수도 있음
이러한 문제들은 코드베이스의 유지보수 비용을 크게 증가시킨다.
아키텍처 패턴은 코드베이스의 다양한 측면에 대한 구성 원칙을 도입하여 이들 사이의 명확한 경계를 제시한다.
여기서는 3가지 주요 아키텍처 패턴인 계층형 아키텍처, 포트와 어댑터, CQRS 에 대해 알아본다.
2. 계층형 아키텍처(Layered architecture)
계층형 아키텍처는 코드베이스를 수평 계층으로 조직하고, 각 계층은 사용자와 상호 작용/비즈니스 로직 구현/데이터 저장과 같은 기술적 관심사 중 하나를 다룬다.
Object Storage 는 AWS S3 또는 구글 클라우드 스토리지 등을 말한다.
메시지 버스의 경우 메시비 버스를 내부 용도로 사용하는 것을 말하며, 외부로 노출된다면 그 때는 프레젠테이션 계층에 속한다.
2.1. 프레젠테이션 계층
프레젠테이션 계층은 프로그램 동작을 촉발하는 모든 동기식/비동기식 수단과 같은 범주를 포함한다.
- GUI
- CLI
- 다른 시스템과 연동하는 API
- 메시지 브로커에서 이벤트에 대한 구독
- 나가는 이벤트를 발행하는 메시지 토픽
프레젠테이션 계층은 프로그램의 퍼블릭 인터페이스이다. (= 외부 환경으로부터 요청을 받고 결과를 소통하는 수단)
2.2. 비즈니스 로직 계층
비즈니스 로직 계층은 프로그램의 비즈니스 로직을 구현하고 묶는 것을 담당한다.
액티브 레코드 패턴 또는 도메인 모델 패턴과 같은 비즈니스 로직 패턴을 이 계층에서 구현한다.
2.3. 데이터 접근 계층
데이터 접근 계층은 영속성 메커니즘에 접근할 수 있게 해준다.
데이터 접근 계층은 프로그램의 기능을 구현하는데 필요한 다양한 외부 정보 제공자와 연동하는 것을 포함한다.
예) 언어 변역, 오디오 녹음과 같은 외부 시스템에서 제공되는 API 또는 클라우드 벤더의 관리형 서비스와 연동한다.
2.4. 계층 간 커뮤니케이션
위 그림에서 보이듯이 계층은 top-down 커뮤니케이션 모델에 따라 연동한다.
즉, 각 계층은 바로 아래 계층에만 의존한다.
이렇게 함으로써 구현 관심사의 결합도를 낮추고, 계층 간에 공유할 지식을 줄인다.
2.5. Optional: 서비스 계층
계층형 아키텍처 패턴을 확장해서 서비스 계층을 추가하는 것을 흔히 볼 수 있다.
서비스 계층은 프레젠테이션 계층과 비즈니스 로직 계층 사이의 중간 역할을 한다.
아키텍처 패턴의 컨텍스트에서 서비스 계층은 물리적 서비스가 아닌 논리적 경계이다.
서비스 계층은 비즈니스 로직 계층으로의 관문 역할을 한다.
즉, 하부 계층을 조율하는데 필요한 기능들을 감싸서 퍼블릭 인터페이스 메서드에 상응하는 인터페이스로 노출한다.
interface TestService {
boolean createTest(String tt);
}
프레젠테이션에서는 관련된 구현 상세를 포함하지 않고, 서비스 계층에서 요구하는 입력을 제공하여 결과를 호출자에게 반환하는 것까지만 책임진다.
class TestServiceImpl extends TestService {
boolean createTest(String tt) {
// ...
return true;
}
}
<서비스 계층 도입 시 장점>
- 동일한 서비스 계층을 여러 퍼블릭 인터페이스에서 재사용 가능 (= 중복된 조율 로직 제거)
- 모든 관련 메서드를 한 곳에 모음으로써 모듈화 개선
- 프레젠테이션 계층과 비즈니스 로직 계층의 결합도 낮춤
- 비즈니스 계층을 테스트하기 쉬워짐
서비스 계층이 항상 필요한 것은 아니다.
예를 들어 비즈니스 로직이 트랜잭션 스크립트 패턴으로 구현된 겅우 이미 퍼블릭 인터페이스를 구성하는 일련의 메서드를 노출함으로써 기본적으로 서비스 계층 역할을 한다.
이 때 서비스 계층을 추가해봤자 복잡성을 추상화하거나 감싸는 것이 아니라 단순히 트랜잭션 스크립트의 퍼블릭 인터페이스를 되풀이하기만 할 뿐이므로 비즈니스 로직 계층 하나로 충분하다.
반면, 액티브 레코드 패턴을 사용하는 경우 비즈니스 로직 패턴에서 외부 조율을 해야하는 경우 서비스 계층이 필요하다.
이 때 서비스 계층은 트랜잭션 스크립트 패턴을 구현하고, 이것이 실제로 동작하는 액티브 레코드는 비즈니스 로직 계층에 둔다.
2.6. 계층형 아키텍처를 사용하는 경우
계층형 아키텍처에서 비즈니스 로직과 데이터 접근 계층 간에는 의존성이 있으므로, 비즈니스 로직이 트랜잭션 스크립트나 액티브 레코드 패턴으로 구현된 경우 계층형 아키텍처 패턴이 적합나다.
반면, 도메인 모델을 구현하는데 계층형 아키텍처 패턴을 적용하기는 어렵다.
도메인 모델에서는 비즈니스 엔티티(= 애그리거트와 밸류 엔티티)가 하부의 인프라스트럭처에 대해 의존성이 없어야 하고, 몰라야 하기 때문이다.
2.7. 용어
아래 용어들은 혼용되어 사용되기도 한다.
- 프레젠테이션 계층 = 사용자 인터페이스 계층
- 서비스 계층 = 애플리케이션 계층
- 비즈니스 로직 계층 = 도메인 계층 = 모델 계층
- 데이터 접근 계층 = 인프라스트럭처 계층
2.8. 계층과 티어(Tier)
계층형 아키텍처와 N-티어 아키텍처를 혼동될 때가 많은데 이 둘은 개념적으로 다르다.
계층은 논리적 경계인 반면에, 티어는 물리적 경계이다.
계층형 아키텍처에서 모든 계층은 동일한 수명 주기를 갖는다. (= 단일 단위로 구현되고, 배포됨)
반면 티어는 독립적으로 배포될 수 있는 서비스나 서버이다.
브라우저 - 리버스 프록시 - 애플리케이션 서버 - DB 서버 는 N-티어 시스템이다.
위는 웹 기반 시스템에 관련된 물리적 서비스의 연동을 표현한다. 이 구성 요소들은 동일한 물리 서버에서 컨테이너 등으로 동작하거나, 여러 서버에 분산되어 작동할 수 있지만, 각 구성 요소는 서로 독립적으로 배포되고 관리된다. 이런 것은 계층이 아니라 티어이다.
3. 포트와 어댑터
포트와 어댑터 아키텍처는 계층형 아키텍처보다 좀 더 복잡한 비즈니스 로직을 구현하는데 적합하다.
3.1. 용어
본질적으로 프레젠테이션 계층과 데이터 접근 계층 모두 DB, 외부 서비스, 사용자 인터페이스 프레임워크 등 외부 구성요소와 연동하는 것을 표현하지만 비즈니스 로직을 반영하지는 못하므로 아래처럼 이들을 모두 단일 인프라스트럭처 계층으로 통합한다.
3.2. 의존성 역전 원칙(DIP, Dependency Inversion Principle)
의존성 역전 원칙은 상위 수준 모듈을 하위 수준 모듈에 의존해서는 안된다.
포트와 어댑터 아키텍처에서 비즈니스 로직 계층은 더 이상 어떠한 하위 계층에도 의존하지 않는다.
계층형 아키텍처에서의 서비스 계층처럼, 애플리케이션 계층은 시스템이 노출하고 있는 모든 오퍼레이션을 설명하고, 이를 실행할 때 시스템의 비즈니스 로직을 조율한다.
3.3. 인프라 구성요소의 연동
왜 포트와 어댑터라고 부르는지에 대한 답을 알려면 인프라스트럭처 구성요소가 어떻게 비즈니스 로직과 연동하는지 보아야 한다.
포트와 어댑터 아키텍처의 핵심 목적은 인프라스트럭처 구성요소로부터 비즈니스 로직을 분리하는 것이다.
인프라스트럭처 구성요소를 직접 참조하고 호출하는 대신, 비즈니스 로직 계층은 인프라스트럭처 계층이 구현해야 할 ‘포트’를 정의하고, 인프라스트럭처 계층은 ‘어댑터’를 구현한다. (= 즉, 다양한 기술을 사용하기 위해 정의된 포트의 인터페이스를 구체적으로 구현함)
추상 포트는 인프라스트럭처 계층에서 의존성 주입을 통해 구체적인 어댑터로 나타낸다.
포트와 어댑터 아키텍처는 헥사고날 아키텍처, 어니언(onion) 아키텍처, 클린 아키텍처로 알려졌다.
- 애플리케이션 계층 = 서비스 계층 = 유스케이스 계층
- 비즈니스 로직 계층 = 도메인 계층 = 핵심 게층
3.4. 포트와 어댑터를 사용하는 경우
모든 기술적 관심사로부터 비즈니스 로직을 분리하는 것이 포트와 어댑터 아키텍처의 목적이므로 포트와 어댑터 아키텍처는 도메인 모델 패턴을 사용하여 구현한 비즈니스 로직에 매우 적합하다.
4. CQRS(Command-Query Responsibility Segregation)
CQRS 패턴은 포트와 어댑터와 동일한 비즈니스 로직과 인프라스트럭처 관심사에 기반하지만 시스템의 데이터를 관리하는 방식이 다르다는 점에 차이가 있다.
DDD - CQRS 를 참고하세요.
5. 범위
계층형 아키텍터, 포트와 어댑터 아키텍처, CQRS 를 시스템 전체에 적용하는 원칙으로 취급하면 안된다.
동일한 타입의 하위 도메인도 다양한 비즈니스 로직과 아키텍처 패턴이 필요할 수 있다.
바운디드 컨텍스트에 단일 아키텍처를 강요하면 의도치않은 복잡성을 유발할 수 있다.
정리하며..
- 계층형 아키텍처
- 기술적 관심사에 따라 코드베이스 분해
- 비즈니스 로직과 데이터 접근 구현을 결합시키므로 액티브 레코드 기반 시스템에 적합함
- 포트와 어댑터 아키텍처
- 관계를 역전시킴
- 비즈니스 로직을 중심에 두고 모든 인프라스트럭처와의 의존성을 분리함
- 도메인 모델 패턴을 구현하는 비즈니스 로직에 적합함
- CQRS 패턴
- 여러 모델에서 동일한 데이터를 표현함
- 이벤트 소싱 도메인 모델에 기반한 시스템에 적합하지만, 다양한 영속 모델을 사용할 필요가 있는 모든 시스템에 사용할 수 있음
참고 사이트 & 함께 보면 좋은 사이트
본 포스트는 블라드 코노노프 저자의 도메인 주도 설계 첫걸음을 기반으로 스터디하며 정리한 내용들입니다.