본문 바로가기

Design/DDD

[DDD 첫걸음] 2-4. 전술적 설계 - 아키텍처 패턴

 
이번장 에서는 시스템의 구성요소 간의 상호작용과 의존성을 조율하는 다양한 방법을 살펴본다.

비즈니스 로직과 아키텍처 패턴

  • 비즈니스 로직은 소프트웨어에서 가장 중요한 요소.
  • 하지만 그 외에도 코드베이스에는 입력과 출력, 다양한 저장소에 상태를 저장하고 타 시스템 및 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 패턴은 여러 모델에서 동일한 데이터를 표현.
    • 이 패턴은 이벤트 소싱 도메인 모델에 기반한 시스템에 적합하지만, 다양한 영속 모델을 사용할 필요가 있는 어떤 시스템에도 사용할 수 있음.