이번장 에서는 시스템의 구성요소 간의 상호작용과 의존성을 조율하는 다양한 방법을 살펴본다.
비즈니스 로직과 아키텍처 패턴
- 비즈니스 로직은 소프트웨어에서 가장 중요한 요소.
- 하지만 그 외에도 코드베이스에는 입력과 출력, 다양한 저장소에 상태를 저장하고 타 시스템 및 Service provider와의 연동 등의 요소들도 존재.
- 이렇듯 코드베이스에는 다양한 관심사가 있기 때문에 각 관심사에 맞게 엄격한 구성이 필요하며, 비즈니스 로직이 흩어지는 것을 방지해야함. (유지보수의 용이성)
- 아키텍처 패턴은 코드베이스의 다양한 측면에 대한 구성 원칙을 도입하고 이들 사이의 명확한 경계를 제시.
- 코드 베이스를 조직하는 적절한 방법 혹은 올바른 아키텍처 패턴을 선택하는 것은 단기적으로는 비즈니스 로직 구현을 지원.
- 장기적으로는 유지보수의 용이성을 위해 매우 중요.
- 아키텍처 패턴에는 계층형 아키텍처, 포트와 어댑터, 그리고 CQRS 이렇게 3가지 주요 아키텍처가 존재.
계층형 아키텍처
- 계층형 아키텍처(Layered architecture)는 가장 일반적인 아키텍처 패턴 중 하나.
- 코드베이스를 수평 계층으로 조직.(외부와 상호작용, 비즈니스 로직 구현, 데이터 저장)
- 고전적인 형태의 계층형 아키텍처는 프레젠테이션 계층(PL:Presentation layer), 비즈니스 로직 계층(BLL: Business logic layer), 데이터 접근 계층(DAL: Data access layer) 세가지 층으로 구성.
프레젠테이션 계층
- 사용자와 상호작용을 하기 위한 프로그램의 사용자 인터페이스를 구현.
- 이전 : 웹 인터페이스, 데스크톱 인터페이스와 같은 그래픽 인터페이스를 나타낸다.
- 현재 : 프로그램의 동작을 촉발하는 모든 동기식 or 비동기식 수단과 같은 더 광범위한 범주를 포함함.
(외부 환경으로부터 요청을 받고 결과를 소통하는 수단)- 그래픽 사용자 인터페이스 (GUI)
- 커맨드 라인 인터페이스 (CLI)
- 다른 시스템과 연동하는 프로그래밍 API
- 메시지 브로커에서 이벤트에 대한 구독
- 나가는 이벤트를 발행하는 메시지 토픽.
- 엄밀히 말하면 프레젠테이션 계층은 프로그램의 퍼블릭 인터페이스이다.
비즈니스 로직 계층
- 비즈니스 로직(비즈니스 의사결정 - 엔티티,규칙,프로세스)을 구현하고 묶는 역할.
- 에릭 에반스는 이 계층이 소프트웨어의 중심이라고 했음.
- 액티브 레코드 or 도메인 모델과 같은 비즈니스 로직 패턴을 이 계층에 구현.
데이터 접근 계층
- 데이터 접근 계층은 영속성 메커니즘에 접근할 수 있게 해줌.
- 이전 : 데이터베이스를 뜻함.
- 현재 : 더 넓은 범위의 책임.
- NoSQL의 출현으로 도큐먼트 저장소(실시간 데이터 처리 DB), 검색 인덱스(동적 질의), 인메모리 DB(성능 최적화)로 사용
- 클라우드 기반 오브젝트 저장소(S3), 메시지 버스.
- 이 계층은 프로그램의 기능을 구현하는 데 필요한 다양한 외부 정보 제공자와 연동하는 것을 포함.
계층간 커뮤니케이션
- 계층은 톱다운(top-down) 커뮤니케이션 모델에 따라 연동.(바로 아래 계층에만 의존)
- 구현 관심사의 결합성을 낮추고 계층간 공유해야할 지식을 줄임.
변종(Variation)
계층형 아키텍처 패턴을 확장해 서비스 계층을 추가한 케이스.
서비스 계층
가용한 오퍼레이션을 구축하고
각 오퍼레이션에서 애플리케이션의 응답을 조정하는
서비스 계층을 애플리케이션의 경계에 정의.
- 서비스 계층은 프로그램의 프레젠테이션 계층과 비즈니스 로직 계층 사이의 중간 역할을 한다.
- 아키텍처 패턴의 컨텍스트에서 서비스 계층은 논리적 경꼐. (물리적 서비스아님)
- 비즈니스 로직 계층으로의 관문 역할.(하부 계층을 조율하는 데 필요한 것들을 감싸서 퍼블릭 인터페이스의 메서드에 상응하는 인터페이스로 노출.
- 서비스를 interface로 정의하고, 이 인터페이스를 프레젠테이션 계층에서는 서비스 계층에서 요구하는 입력을 제공하고 결과를 받음.
- 서비스 계층을 명시할 경우의 장점
- 동일한 서비스 계층을 여러 public interface에서 재사용가능. (GUI, API 등)
- 관련 메서드를 한 곳에 모으면 모듈화할 수 있음.
- 프레젠테이션 계층과 비즈니스 로직 계층의 결합도를 낮춤.
- 비즈니스 기능을 테스트하기 쉬워짐.
- 비즈니스 로직이 트랜잭션 스크립트로 구현된 경우에는 서비스 계층이 불필요. (스크립트가 서비스 계층역할을 함)
- 반면 액티브 레코드 패턴을 사용하는 경우처럼 비즈니스 로직 패턴에서 외부 조율을 해야 할 경우는 서비스 계층필요.
- 서비스 계층은 트랜잭션 스크립트 패턴을 구현하되 이것이 실제로 동작하는 액티브 레코드는 비즈니스 로직 계층에 둔다.
용어
- 프레젠테이션 계층 = 사용자 인터페이스 계층
- 서비스 계층 = 애플리케이션 계층
- 비즈니스 로직 계층 = 도메인 계층 = 모델 계층
- 데이터 접근 계층 = Infrastructure 계층
계층형 아키텍처를 사용하는 경우
- 비즈니스 로직이 트랜잭션 스크립트 또는 액티브 레코드 패턴을 사용하여 구현된 시스템에서 계층형 아키텍처 패턴이 적합.
(비즈니스 로직과 데이터 접근 계층간의 의존성으로 인해.) - 도메인 모델을 구현하는 데 계층형 아키텍처 적용은 어려움.
- 도메인 모델에서는 비즈니스 엔티티(에그리게이트와 벨류 오브젝트)가 하부의 인프라스트럭처에 대해 의존성이 없어야하므로
(몰라야함)
- 도메인 모델에서는 비즈니스 엔티티(에그리게이트와 벨류 오브젝트)가 하부의 인프라스트럭처에 대해 의존성이 없어야하므로
포트와 어댑터
- 포트와 어댑터 아키텍처는 계층형 아키텍처의 단점을 해결하고 좀 더 복잡한 비즈니스 로직을 구현하는데에 적합.
- 프레젠테이션 계층, 데이터 접근 계층, 외부 서비스, 사용자 인터페이스 프레임워크 등은 비즈니스 로직은 반영하지 못하므로 인프라 스트럭처 계층으로 통합.
의존성 역전 원칙(DIP)
- DIP은 비즈니스 로직을 구현하는 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안된다는 원칙임.
- 전통적인 계층형에서는 비즈니스 로직이 인프라스트럭처 계층에 의존. 따라서 이를 역전시켜주어야함.
- DIP를 적용하게 되면 비즈니스 로직 계층은 프레젠테이션 계층과 데이터 접근 계층에 끼어있는 대신, 중심적인 역할을 담당하게됨.
- 즉 더 이상 인프라에 의존적이지 않게됨.
- 시스템 퍼블릭 인터페이스를 위한 관문으로서 애플리케이션 계층을 추가.
- 마치 계층형 아키텍처에서의 서비스 계층 처럼, 애플리케이션 계층은 시스템의 비즈니스 로직을 조율.
인프라 구성요소의 연동
- 비즈니스 로직 계층은 인프라스트럭처 계층이 구현해야 할 '포트'를 정의.
- 인프라스트럭처 계층은 '어댑터'를 구현.
- 즉 다양한 기술을 사용하기 위해 정의된 포트의 인터페이스를 구체적으로 구현.
- 추상 포트 (abstract port)는 인프라스트럭처 계층에서 의존성 주입 또는 부트스트래핑을 통해 구체적인 어댑터로 나타낸다.
namespace App.BusinessLoginLayer
{
public interface IMessaging
{
void Publish(Message payload);
void Subscribe(Message type, Action callback);
}
}
namespace App.Infrastructure.Adapters
{
public class SQSBus : IMessaging { ... }
}
변형
- 포트와 어댑터 아키텍처는 헥사고날(hexagonal) 아키텍처, 어니언(onion) 아키텍처, 클린(clean)아키텍처로 알려져있음.
- 모두 동일한 설계 원칙, 동일 구성요소, 동일관계 이지만 용어는 아래와 같이 다양할 수 있음
- 애플리케이션 계층 = 서비스 계층 = 유스케이스 계층
- 비즈니스 로직 계층 = 도메인 계층 = 핵심 계층
포트와 어댑터를 사용하는 경우
- 모든 기술적 관심사로부터 비즈니스 로직을 분리할 수 있으므로 도메인 모델 패턴을 사용하여 구현한 비즈니스 로직에 매우 적합.
CQRS (Command-Query Responsibility segregation)
- CQRS 패턴은 포트와 어댑터와 동일한 비즈니스 로직과 인프라스트럭처 관심사에 기반.
- 하지만 시스템의 데이터를 관리하는 방식이 다름.
폴리글랏 모델링
- 다양한 데이터 관련 요구사항을 구현하기 위해 여러 데이터베이스를 사용하는 것.
ex) 단일 시스템에서 실시간 데이터 처리 DB로 도큐먼트 저장소를 사용하거나 분석/보고용으로 컬럼 저장소, 그리고 검색 엔진으로 사용될 수 있음. - CQRS패턴은 이벤트 소싱과 밀접한 관련.
- 이벤트 소싱 모델의 질의 한계를 극복하기위해 정의됨. 즉 한 번에 하나의 애그리게이트 인스턴스에 대한 이벤트 질의 가능.
- CQRS 패턴은 프로젝션된 모델을 물리적 DB에 매터리얼라이즈(M View의 개념!)해서 유연한 질의에 사용할 수 있음.
구현
- 이 패턴은 시스템 모델의 책임을 분리시킴
- 커맨드 실행 모델과 읽기 모델의 두 유형이 있음.
커맨드 실행 모델 (Command Execution Model)
- 시스템의 상태를 수정하는 오퍼레이션(시스템 커맨드)을 전담으로 수행하는 단일 모델이 있음.
- 이 모델은 비즈니스 로직을 구현하고 규칙을 검사하며 불변성을 강화하는데 사용됨.
- 커맨드 실행 모델은 시스템의 원천인 강력한 일관성을 가진 데이터를 표한하는 유일한 모델.
- 비즈니스 엔티티의 일관적 상태를 읽을 수 있어야 하고 갱신할 때 낙관적 동시성을 지원해야 한다.
읽기 모델(프로젝션)
- 읽기 모델(read model)은 캐시에서 언제든 다시 추출할 수 있는 프로젝션이다.
- 이는 견고한 데이터베이스, 일반 파일, 또는 인메모리 캐시에 위치할 수 있음.
- 잘 구현된 CQRS에서는 모든 프로젝션의 모든 데이터를 삭제하고 처음부터 다시 재생성할 수 있음.
- 미래에 새로운 프로젝션을 시스템에 확장하는 것도 가능하다.
읽기 모델의 프로젝션
- 읽기 모델이 작동하려면 시스템은 커맨드 실행 모델에서 변경을 모든 읽기 모델로 프로젝션해야함.
- 읽기 모델의 프로젝션은 관계형 데이터베이스의 materialized view의 개념과 유사함.
- 원천 테이블이 갱신되면 변경사항은 미리 작성된 뷰에 반영되어야 함.
동기식 프로젝션
- 동기식 프로젝션(synchronous projection)은 격차 해소 구독 모델(catch-up subscription model)을 통해 OLTP 데이터의 변경사항을 가져옴.
- 프로젝션 엔진이 OLTP 데이터베이스로부터 마지막에 처리했던 체크포인트 이후에 추가되거나 갱신된 레코드를 조회한다.
- 프로젝션 엔진이 조화된 데이터를 이용해서 시스템의 읽기 모델을 재생성 또는 갱신한다.
- 프로젝션 엔진은 마지막으로 처리 레코드의 체크포인트를 저장하고 이 값은 다음 처리 때 추가되거나 갱신된 레코드를 조회하는데 사용됨.
- 격차 해소 구독이 작동하려면 커맨드 실행 모델이 추가되거나 갱신되는 모든 데이터베이스 레코드를 체크포인트로 관리해야함.
- 또한 저장 메커니즘도 체크포인트 기반으로 레코드를 조회하는 것을 지원해야 함.
- 체크포인트는 데이터베이스의 기능을 사용해 구현될 수 있음.
(rowversion 컬럼을 활용해 유일하고 순차적으로 증가하는 숫자를 생성할 수 있음.) - 동기식 프로젝션 메서드에서 새로운 프로젝션을 추가하고 기존의 것을 처음부터 다시 생성하는 것은 쉽다.
(후자는 checkpoint를 0으로 하면됨.)
비동기식 프로젝션
- 비동기식 프로젝션 시나리오에서 커맨드 실행 모델은 모든 커밋된 변경사항을 메시지 버스에 발행.
- 아래 그림과 같이 시스템의 프로젝션 엔진은 발행된 메시지를 구독하고 읽기 모델을 갱신하는데 사용.
도전과제
- 비동기식 프로젝션 방식의 확실한 확장성과 성능의 장점에도 불구하고, 분산 컴퓨팅에서 문제가 발생하기 더 쉬움.
- 메시지의 순서가 잘못되거나 중복 처리되면 읽기 모델에 일관성 없는 데이터가 프로젝션 됨.
- 또한 새로운 프로젝션을 추가하거나 이미 존재하는 것을 재생성하는 것이 어려움.
- 가능하면 동기식 프로젝션 방식으로 구현하는 것을 추천.
모델 분리
- CQRS에서 시스템 모델이 담당하는 책임은 그 타입에 따라 분리됨.
- 커맨드는 강한 일관성을 가진 커맨드 실행 모델에서만 동작.
- 질의는 읽기 모델과 커맨드 실행 모델을 포함하여 그 어떤 시스템의 영속 상태를 직접 수정할 수 없음.
- 커맨드는 실행이 성공했는지 또는 실패했는지를 항상 호출자에게 알려야 함.
- 실패 했다면 왜?에 대한 정보가 있어야 호출자가 커맨드를 어떻게 수정할 지 알 수 있음.
- 상황에 따라 반환값을 이용하여 다음 워크플로에 활용하여 불필요한 데이터 왕복을 없앨 수 있음.
CQRS를 사용해야 하는 경우
- CQRS 패턴은 여러 모델, 궁극적으로 다양한 종류의 데이터베이스에 저장된 동일한 데이터와 작동할 필요가 있는 애플리케이션에 유용.
- 운영 관점에서 이 패턴은 당면한 과제에 가장 효과적인 모델을 사용하고, 비즈니스 도메인 모델을 지속적으로 개선하는 도메인 주도 설계의 핵심 가치를 지원.
- 인프라스트럭처 관점에서는 다양한 종류의 데이터베이스의 장점을 활용할 수 있게 해줌.
(아래의 모든 스토리지 메커니즘이 신뢰성 있게 동기화 됨.)
- 커맨드 실행 모델에서 저장을 위한 관계형 데이터베이스.
- 전문 검색을 위한 검색 인덱스.
- 빠른 데이터 검색을 위한 사전 렌더링된 플랫 파일 등이 있음.
- CQRS는 이벤트 소싱 도메인 모델에도 적합.
- 애그리게이트의 상태에 기반한 레코드 조회가 불가능하지만 CQRS는 상태를 질의할 수 있는 데이터베이스에 상태를 프로젝션하므로 이것이 가능함.
범위
- 앞서 논의한 패턴들이 시스템 전체에 적용하는 구성 원칙으로 취급되면 안된다.
- 하위 도메인에는 핵심, 지원, 일반의 다양한 타입이 있고, 심지어 동일한 타입의 하위 도메인 일지라도 다양한 비즈니스 로직과 아키텍처 패턴이 필요할 수 있음.
- 바운디드 컨텍스트에 단일 아키텍처를 강요하면 의도치 않은 우발적 복잡성을 유발할 것임.
- 우리의 목적은 실제 필요성과 비즈니스 전략에 따라 설계 의사결정을 내리는 것.
- 시스템을 수평으로 분할하는 계층 외에도, 수직으로 나누는 것을 추가로 도입할 수 있음.
- 아래 이미지 처럼 비즈니스 하위 도메인을 묶는 모듈의 논리적 경계를 분명하게 정의하고 각각에 맞는 적합한 도구를 사용하는 것이 중요.
결론
- 계층형 아키텍처는 기술적 관심사에 따라 코드베이스를 분해.
- 비즈니스 로직과 데이터 접근 구현을 결합시키므로 액티브 레코드 기반 시스템에 적합.
- 포트와 어댑터 아키텍처는 관계를 역전시킨다.
- 비즈니스 로직을 중심에 두고 모든 인프라스트럭처와의 의존성을 분리함.
- 도메인 모델 패턴을 구현하는 비즈니스 로직에 적합.
- CQRS 패턴은 여러 모델에서 동일한 데이터를 표현.
- 이 패턴은 이벤트 소싱 도메인 모델에 기반한 시스템에 적합하지만, 다양한 영속 모델을 사용할 필요가 있는 어떤 시스템에도 사용할 수 있음.
'Design > DDD' 카테고리의 다른 글
[DDD 첫걸음] 2-5. 전술적 설계 - 커뮤니케이션 패턴 (1) | 2023.07.23 |
---|---|
[DDD 첫걸음] 2-3. 전술적 설계 - 시간 차원의 모델링. (0) | 2023.06.19 |
[DDD 첫걸음] 2-2. 전술적 설계 - 복잡한 비즈니스 로직 다루기. (0) | 2023.06.11 |
[DDD 첫걸음] 2-1. 전술적 설계 - 간단한 비즈니스 로직 구현. (0) | 2023.04.25 |
[DDD 첫걸음] 1-4. 전략적 설계 - 바운디드 컨텍스트 연동. (0) | 2023.04.16 |