아키텍처 패턴은 코드베이스의 다양한 측면에 대한 구성 원칙을 도입하고 이들 사이의 명확한 경계를 제시한다. 예를 들어 비즈니스 로직이 어떻게 시스템의 입출력, 그리고 다른 기반의 구성요소와 연결되는가와 같은 것이 코드베이스의 다양한 측면 중 하나다. 단기적으로는 비즈니스 로직 구현을 지원하고, 장기적으로 유지보수를 돕기 위해 매우 중요하다. 그것을 가능하게 해주는 3가지 주요 아키텍처 패턴인 계층형 아키텍처, 포트와 어댑터, 그리고 CQRS에 대해 알아보자.
1. 계층형 아키텍처
- 가장 일반적인 아키텍처 패턴 중 하나이다.
- 코드베이스를 수평 계층으로 조직하고, 각 계층은 사용자와 상호작용, 비즈니스 로직의 구현, 그리고 데이터의 저장과 같은 기술적 관심사 중 하나를 다룬다.
- 프레젠테이션 계층
- 사용자와 상호작용을 하기 위한 사용자 인터페이스를 구현한다.
- GUI, CLI, 다른 시스템과 연동하는 프로그래밍 API, 메시지 브로커에서 이벤트에 대한 구독, 나가는 이벤트를 발행하는 메시지 Topic
- 비즈니스 로직 계층
- 프로그램의 비즈니스 로직을 구현하고 묶는 것을 담당한다.
- 비즈니스 의사결정을 구현한다. → 소프트웨어의 중심
- 데이터 접근 계층
- 영속성 메커니즘에 접근할 수 있게 해준다.
- 원래 패턴에서는 DB를 가리킨다. → 현대 시스템에서는 좀 더 넓은 범위를 책임진다.
- NoSQL이 출현한 이래로 여러 DB를 사용하는 시스템이 보편화 되었다.
→ Document 저장소는 실시간 데이터 처리 DB의 역할을 하고 검색 인덱스는 동적 쿼리에 쓰이며, In-memory DB는 최적화된 성능을 내는 동작에 활용된다.
→ 클라우드 기반 오브젝트 저장소는 시스템의 파일을 저장할 수 있고, 메시지 버스는 프로그램의 다양한 기능의 커뮤니케이션을 조율
→ 프로그램의 기능을 구현하는 데 필요한 다양한 외부 정보 제공자와 연동하는 것을 포함한다.
- 계층간 커뮤니케이션
- 계층은 Top-down 커뮤니케이션 모델에 따라 연동한다.
프레젠테이션 계층→ 비즈니스 로직 계층→ 데이터 접근 계층
- 변종(Variation)
- 서비스 계층
- 프로그램의 프레젠테이션 계층과 비즈니스 로직 계층 사이의 중간 역할을 한다.
- 아키텍처 패턴의 컨텍스트에서 서비스 계층은 논리적 경계라는 것이 중요하다.
- 장점
- 동일한 서비스 계층을 여러 퍼블릭 인터페이스에서 재사용가능하다.
- 모든 관련 메서드를 한 곳에 모으면 모듈화가 개선된다.
- 프레젠테이션 계층과 비즈니스 로직 계층의 결합도를 낮춘다.
- 비즈니스 기능을 테스트하기 쉬워진다.
- 계층형 아키텍처를 사용하는 경우
- 비즈니스 로직과 데이터 접근 계층 간에는 의존성이 있다. → 비즈니스 로직이 트랜잭션 스크립트 또는 액티브 레코드 패턴을 사용하여 구현된 시스템에 계층형 아키텍처 패턴이 적합하다.
- 도메인 모델을 구현하는 데 계층형 아키텍처를 적용하는 것이 어렵다.
- 도메인 모델에서는 비즈니스 엔티티(Aggregate & VO)가 하부의 인프라스트럭처에 대해 의존성이 없어야 하고 그것을 몰라야 하기 때문이다.
- 계층과 티어
- 계층형 아키텍처와 N-Tier 아키텍처는 혼동될 때가 많다. → 계층은 논리적 경계, 티어는 물리적 경계
- 모든 계층은 동일한 수명주기를 갖는다. → 단일 단위로 구현되고 발전되고 배포한다.
- 티어는 독립적으로 배포될 수 없는 서버나, 또는 시스템이다.
- N-tier System : Browser - Reverse Proxy - Application Server - DB Server
2. Port & Adapter
본질적으로 프레젠테이션 계층과 데이터 접근 계층 모두 DB, 외부 서비스, UI 프레임워크 등의 외부 구성요소와 연동하는 것을 표현한다. 이와 같은 기술적 구현 상세는 시스템의 비즈니스 로직을 반영하지 못하므로 이 같은 모든 인프라 관심사를 단일 ‘InfraStructure 계층’으로 통합했다.
→ 핵심적인 목적은 인프라스트럭처 구성요소로부터 시스템의 비즈니스 로직을 분리하는 것이다.
- DIP(의존성 역전 원칙)에 따라 기존에 데이터 접근 계층에 의존하던 비즈니스 로직 계층의 의존 관계를 반대로 바꿨다. → 비즈니스 로직계층은 이제 인프라스트럭처의 구성요소에 의존되지 않는다.
- 인프라 스트럭처 계층 → 애플리케이션 계층 → 비즈니스 로직 계층
-
연동
-
변형
- Port & Adapater 아키텍처는 hexagonal , onion, clean 아키텍처로 불리기도 한다.
- Application Layer = Service Layer = Use Case Layer
- BusinessLogic Layer = Domain Layer = Core Layer
-
사용하는 경우
이 아키텍처는 도메인 모델 패턴을 사용하여 구현한 비즈니스 로직에 매우 적합하다.
3. CQRS (Command-Query Responsibility Segregation)
포트와 어댑터와 동일한 비즈니스 로직과 인프라스트럭처 관심사에 기반한다. 하지만 시스템의 데이터를 관리하는 방식이 다르므로 이 패턴으로 여러 영속 모델에 시스템의 데이터를 표현할 수 있다.
- Polyglot Modeling
- 사용 이유
- 대부분의 경우 단일 비즈니스 도메인 모델로 모든 요구사항을 해결하기는 어려울 수 있다. (OLTP, OLAP)
- 다양한 언어를 사용하는 영속성 개념과 관련이 있다. → 확장성이나 일관성, 또는 지원하는 쿼리 모델간 균형이 필요하다.
- 완전한 DB의 대안으로 Polyglot Persistence Model이 있다. → Document, Colum, 검색엔진 등등 여러 DB를 사용함.
- CQRS 패턴은 이벤트 소싱과 관련되어있다. → 탄생한 이유가 이것 때문이다.
- 구현
- 커맨드 실행 모델(Command)
- 시스템의 상태를 수정하는 오퍼레이션을 전담으로 수행하는 단일 모델이 있다.
- 비즈니스 로직을 구현하고 규칙을 검사하며 불변성을 강화하는 데 사용된다.
- 비즈니스 엔티티의 일관적 상태를 읽을 수 있어야 하고 갱신할 때 낙관적 동시성을 지원해야 한다.
- 읽기 모델(Projection)
- 사용자에게 데이터를 보여주거나 다른 시스템에 정보를 제공하기 위해 필요한 만큼 모델을 정의할 수 있다.
- 읽기 모델은 캐시에서 언제든 다시 추출할 수 있는 프로젝션이다. → DB, 일반 파일, 또는 In-memory 캐시등에 위치
- 잘 구현된 CQRS에서는 모든 프로젝션의 모든 데이터를 삭제하고 처음부터 다시 재생성할 수 있다. → 또한 지금 예측하지 못한 미래에 새로운 프로젝션을 시스템에 확장하는 것도 가능하다.
- 읽기 모델은 Readonly이다.
- 프로젝션
- 읽기 모델의 프로젝션
- 작동하려면 시스템은 커맨드 실행 모델에서 변경을 모든 읽기 모델로 프로젝션해야 한다.
- 커맨드 실행 모델 → 프로젝션 엔진 → 읽기 모델
- 원천 테이블이 갱신되면 변경사항은 미리 작성된 뷰에 반영되야만 한다 → 읽기 모델의 프로젝션은 RDB의 meterialized view의 개념과 유사
- 동기식 프로젝션
- 격차 해소 구독 모델(catch-up subscription model)을 통해 OLTP 데이터의 변경사항을 가져온다.
- 프로젝션 엔진이 OLTP DB로부터 마지막에 처리했던 체크포인트 이후에 추가되거나 갱신된 레코드를 조회한다.
- 프로젝션 엔진이 조회된 데이터를 이용해서 시스템의 읽기 모델을 재생성 또는 갱신한다.
- 프로젝션 엔진은 마지막으로 처리 레코드의 체크포인트를 저장한다. 이 값은 다음 처리 때 추가되거나 갱신 된 레코드를 조회하는 데 사용된다.
- 격차 해소 구독이 작동하기 위해서는 커맨드 실행 모델이 추가 되거나 갱신되는 모든 레코드를 체크포인트로 관리 해야 한다. → 저장 매커니즘도 체크포인트 기반으로 레코드를 조회하는 것을 지원해야 한다.
- 비동기식 프로젝션 비동기식 프로젝션 시나리오에서 커맨드 실행 모델은 모든 커밋된 변경사항을 메시지 버스에 발행한다.
- 장점
- 단점
- 분산 컴퓨팅에서 메시지의 순서와 중복 처리에 관련해서 문제가 발생 할 수 있다.
- 새로운 프로젝션을 추가하거나 이미 존재하는 것을 재생성 하는 것이 어렵다.
→ 가능하면 동기식 프로젝션 방식을 구현하고, 그 위에 선택적으로 비동기식 프로젝션 방식을 추가하는 것을 권장.
- 모델 분리
- Command : Create, Update, Delete
- Query: Read
- 대부분의 경우 커맨드는 데이터를 반환해야 한다.
- 사용성 높아짐
- 반환 값을 사용자의 다음 workflow에 활용해서 불필요한 데이터 왕복을 없앰
- 단점: 반환 데이터가 강한 일관성 모델(Command)에서 비롯되어야 한다는 것이다.
→ 데이터가 궁극적으로 일관성을 갖는 프로젝션의 경우, 즉각적인 갱신은 기대할 수 없다.
- 사용해야 하는 경우
- 여러 모델, 다양한 종류의 DB에 저장된 동일한 데이터와 작동할 필요가 있는 애플리케이션에 유용하다.
- 커맨드 실행 모델: 저장을 위한 RDB
- Query: 전문 검색을 위한 검색인덱스
- 이벤트 소싱 도메인 모델에도 적합
3. 결론
- 계층형 아키텍처: 기술적 관심사에 따라 코드베이스를 분해한다. 이 패턴은 비즈니스 로직과 데이터 접근 구현을 결합시키므로 액티브 레코드 기반 시스템에 적합하다.
- 포트와 어댑터 아키텍처: 관계를 역전시킨다. 비즈니스 로직을 중심에 두고 모든 인프라스트럭처와의 의존성을 분리한다. 이 패턴은 도메인 모델 패턴을 구현하는 비즈니스 로직에 적합하다.
- CQRS 패턴은 여러 모델에서 동일한 데이터를 표현한다. 이 패턴은 이벤트 소싱 도메인 모델에 기반한 기스템에 적합하지만 다양한 영속 모델을 사용할 필요가 있는 어떤 시스템에도 사용할 수 있다.
※ 참고자료
Hexagonal Architecture with Java and Spring
CQRS 패턴 - Azure Architecture Center