https://product.kyobobook.co.kr/detail/S000061352142, Ch 8
코드베이스를 조직하는 적절한 방법 혹은 올바른 아키텍처 패턴을 선택하는 것은 단기적으로는 비즈니스 로직 구현을 지원하고, 장기적으로 유지보수를 돕기 위해 매우 중요하다.
그것을 가능하게 해주는 세가지 주요 아키텍처 패턴인 계층형 아키텍처, 포트와 어댑터, 그리고 CQRS에 대해 알아보자.
계층은 톱다운(top-down) 커뮤니케이션 모델에 따라 연동한다. 이렇게 하면 구현 관심사의 결합성을 낮추고 계층 간에 공유할 지식을 줄인다.
서비스 계층은 프로그램의 프레젠테이션 계층과 비즈니스 로직 계층 사이의 중간 역할을 한다.
MVC 컨트롤러는 프레젠테이션 계층에 속한다.
서비스 계층을 명시적으로 갖추면 몇 가지 장점이 생긴다.
서비스 계층이 항상 필요한 것은 아니다. 예를 들어, 비즈니스 로직이 트랜잭션 스크립트로 구현된 경우 이미 시스템의 퍼블릭 인터페이스를 구성하는 일련의 메서드를 노출하므로 기본적으로 서비스 계층 역할을 한다. 이 경우 서비스 계층의 API는 어떤 복잡성을 추상화하거나 감싸지 않고 단순히 트랜잭션 스크립트의 퍼블릭 인터페이스를 되풀이 한다. 따라서 서비스 계층 또는 비즈니스 로직 계층 중 하나면 충분하다.
액티브 레코드 패턴을 사용하는 경우처럼 비즈니스 로직 패턴에서 외부 조율을 해야 할 경우 서비스 계층이 필요하다. 이 경우 서비스 계층은 트랜잭션 스크립트 패턴을 구현하되 이것이 실제로 동작하는 액티브 레코드는 비즈니스 로직 계층에 둔다.
비즈니스 로직과 데이터 접근 계층 간에는 의존성이 있다. 따라서 비즈니스 로직이 트랜잭션 스크립트 또는 액티브 레코드 패턴을 사용하여 구현된 시스템에 계층형 아키텍처 패턴이 적합하다.
반면, 도메인 모델을 구현하는 데 계층형 아키텍처 패턴을 적용하는 것은 어렵다. 도메인 모델에서는 비즈니스 엔티티(애그리게이트와 벨류 오브젝트)가 하부의 인프라스트럭처에 대한 의존성이 없어야 하고, 그것을 몰라야 하기 때문이다.
포트와 어댑터(port & adapter) 아키텍처는 계층형 아키텍처의 단점을 해결하고 좀 더 복잡한 비즈니스 로직을 구현하는데 적합하다. 흥미롭게도 두 패턴은 매우 비슷하다.
본질적으로 프레젠테이션 계층과 데이터 접근 계층 모두 데이터베이스, 외부 서비스, 사용자 인터페이스 프레임워크 등의 외부 구성요소와 연동하는 것을 표현한다. 하지만 이와 같은 기술적 구현 상세는 시스템의 비즈니스 로직을 반영하지 못하므로 이 같은 모든 인프라 관심사를 단일 '인프라스트럭처 계층'으로 통합해봤다.
이렇게 하면 비즈니스 로직 계층은 프레젠테이션 계층과 데이터 접근 계층에 끼어 있는 대신, 중심적인 역할을 맡는다. 더이상 시스템의 인프라스트럭처 구성요소에 의지하지 않는다.
포트와 어댑터 아키텍처의 핵심 목적은 인프라스트럭처 구성요소로부터 시스템의 비즈니스 로직을 분리하는 것이다.
인프라스트럭처 구성료소를 직접 참조하고 호출하는 대신, 비즈니스 로직 계층은 인프라스트럭처 계층이 구현해야 할 '포트'를 정의한다. 인프라스트럭처 계층은 '어댑터'를 구현한다.
추상 포트(abstract port)는 인프라스트럭처 계층에서 의존성 주입 또는 부트스트래핌을 통해 구체적인 어댑터로 나타난다.
포트와 어댑터 아키텍처는 헥사고날(hexagonal) 아키텍처, 어니어(onion) 아키텍처, 그리고 클린(clean) 아키텍처로 알려졌다.
모든 기술적 관심사로부터 비즈니스 로직을 분리하는 것이 포트와 어댑터 아키텍처의 목적이므로 아키텍처는 도메인 모델 패턴을 사용하여 구현한 비즈니스 로직에 매우 적합하다.
CQRS(command-query responsibility segregation) 패턴은 포트와 어댑터와 동일한 비즈니스 로직과 인프라스트럭처 관심사에 기반한다. 하지만 시스템의 데이터를 관리하는 방식이 다르다.
단일 시스템에서 실시간 데이터 처리 데이터베이스로 도큐먼트 저장소를 사용하거나 분석/보고용으로 칼럼 저장소, 그리고 견고한 검색 기능을 위해 검색 엔진을 사용할 수 있다.
CQRS 패턴은 이벤트 소싱과 밀접하게 관련이 있다. 원래 CQRS는 이벤트 소싱 모델의 질의 한계를 극복하려고 정의됐다.
격차 해소 구독이 작동하려면 커맨드 실행 모델이 추가되거나 갱신되는 모든 데이터베이스 레코드를 체크포인트로 관리해야 한다. 또한 저장 메커니즘도 체크포인트 기반으로 레코드를 조회하는 것을 지원해야 한다.
동기식 프로젝션 메서드에서 새로운 프로젝션을 추가하고 기존의 것을 처음부터 다시 생성하는 것은 쉽다. 후자의 경우, 할 일을 단지 체크포인트를 0으로 초기화하는 것뿐이다. 그러면 프로젝션 엔진이 레코드를 읽고 처음부터 프로젝션을 다시 만들 것이다.
비동기식 프로젝션 시나리오에서 커맨드 실행 모델은 모든 커밋된 변경사항을 메시지 버스에 발행한다. 시스템의 프로젝션 엔진은 발행된 메시지를 구독하고 읽기 모델을 갱신하는 데 사용한다.
비동기식 프로젝션 방식의 확실한 확장성과 성능의 장점에도 불구하고, 분산 컴퓨팅에서 문제가 발생하기 더 쉽다. 메시지의 순서가 잘못되거나 중복 처리되면 읽기 모델에 일관성 없는 데이터가 프로젝션된다.
또한 이 방식은 새로운 프로젝션을 추가하거나 이미 존재하는 것을 재성성하는 것이 어렵다. 그러므로 가능하면 동기식 프로젝션 방식을 구현하고, 그 위에 선택적으로 비동기식 프로젝션 방식을 추가하는 것을 권장한다.
CQRS 기반 시스템에 대한 일반적인 오해는 커맨드가 데이터를 수정만 할 수 있고, 데이터를 오직 표현 용도로 읽기 모델을 통해서만 조회할 수 있다는 것이다. 다시 말해, 커맨드 실행 메서드는 어떤 데이터도 반환해서는 안 된다는 것이다. 하지만 이것을 잘못된 것이다.
대부분의 경우 커맨드는 데이터를 반환해야 한다.
이 방버의 유일한 단점이자 한계는 반환 데이터가 강한 일관성 모델(커맨드 실행 모델)에서 비롯되어야 한다는 것이다. 즉, 데이터가 궁극적으로 일관성을 갖는 프로젝션의 경우에는 데이터에 대한 즉각적인 갱신을 기대할 수 없다.
CQRS 패턴은 여러 모델, 궁극적으로 다양한 종류의 데이터베이스에 저장된 동일한 데이터와 작동할 필요가 있는 애플리케이션에 유용하다.
커맨드 실행 모델 저장을 위한 관계형 데이터 베이스, 전문 검색을 위한 검색 인덱스, 빠른 데이터 검색을 위한 사전 렌더링된 플랫 파일 등이 있으며, 이러한 모든 스토리지 메커니즘이 신뢰성 있게 동기화 된다.
또한 CQRS는 이벤트 소싱 도메인 모델에도 적합하다. 이벤트 소싱 모델에서는 애그리게이트의 상태에 기반한 레코드 조회가 불가능하지만 CQRS는 상태를 질의할 수 있는 데이터베이스에 상태를 프로젝션하므로 이것이 가능하다.
앞서 논의한 패턴, 즉 계층형 아키텍처, 포트와 어댑터 아키텍처, CQRS는 시스템 전체에 적용하는 구성 원칙으로 취급하면 안된다.