DDD - 실무에서의 DDD


이 포스트에서는 브라운 필드 프로젝트에 DDD 를 적용할 때 당면하는 어려움에 대해 알아본다.
또한 브라운필드 프로젝트에 DDD 패턴과 실무를 점진적으로 도입하고 통합하는 법에 대해 알아본다.

그린필드 프로젝트

아무런 제약없이 완전히 새롭게 시작하는 프로젝트로 레거시가 존재하지 않아 자유롭게 최신 기술 스택, 최적의 설계 방식, 현대적인 아키텍처 적용 가능
초기엔 개발 속도가 느리지만 점점 빨라짐

장점

  • 기술적 부채 없음
  • 유연한 설계 가능
  • 현대적인 아키텍처 채택 쉬움

단점

  • 처음부터 모든 걸 구축해야 하므로 초기 리소스와 소모가 큼
  • 예상치못한 문제를 많이 부딪힘
  • 팀의 경험 부족이 결과에 큰 영향을 줄 수 있음

브라운필드 프로젝트

기존에 이미 시스템이나 코드, 인프라가 존재하는 상태에서 그 위에 개선하거나 확장하는 프로젝트
레거시 시스템과의 호환성 유지, 점진적 리팩토링, 기술 부채 해결 등이 주요 과제임
초기엔 개발 속도가 빠르지만 이후에 제약이 많아짐

장점

  • 기존 시스템이 있으므로 빠르게 시작 가능
  • 이미 검증된 기능이나 데이터 활용 가능

단점

  • 레거시 코드 및 아키텍처의 제약이 큼
  • 기술 부채 존재, 코드 복잡성

설계 엔트로피(Design Entropy)

시간이 지날수록 시스템이 점점 복잡해지고, 이해하기 어렵고, 유지보수가 힘들어지는 현상

엔트로피가 높아지면 생기는 문제

  • 버그 발생 확률 증가
  • 신규 기능 추가 시 시간 증가
  • 코드 가독성 하락
  • 테스트 어려움
  • 팀 생산성 저하

설계 엔트로피 줄이는 방법

  • 리팩토링을 통해 주기적으로 구조를 정리하고, 중복 제거
  • 코드 리뷰를 통해 설계와 일관성 유지, 원칙 준수 점검
  • 테스트 도입으로 설계가 바뀌어도 안전성 확보
  • 문서화를 통해 시스템 구조와 의도를 공유하여 무질서 방지
  • DDD 를 통해 도메인 중심의 구조화로 복잡도 관리

목차


1. 전략적 분석

실무에 DDD 를 도입하는 첫 순서는 조직의 비즈니스 전략과 시스테 아키텍처의 현 상황을 이해하는데 시간을 투자하는 것이다.


1.1. 비즈니스 도메인 이해

  • 조직의 비즈니스 도메인은 무엇인지?
  • 고객은 누구이며, 조직이 고객에게 제공하는 서비스는 무엇인지?
  • 경쟁 회사의 제품은 무엇인지?

위 질문에 대해 이해하면 회사의 전반적인 목표에 대한 조감도를 얻을 수 있다.

다음으로는 도메인을 확대해서 조직이 상위 목표인 하위 도메인을 달성하기 위해 사용하는 비즈니스 구성 요소를 찾는다.

가장 먼저 활용할만한 휴리스틱은 회사의 조직도이고, 그 다음은 특정 유형의 하위 도메인의 신호를 찾는 것이다.

  • 핵심 하위 도메인
    • 경쟁업체와 차별화되는 점을 찾을 것
  • 일반 하위 도메인
    • 상용 솔루션이나 구독 서비스, 연동할 수 있는 오픈소스 소프트웨어를 찾을 것
  • 지원 하위 도메인
    • 상용 솔루션으로 대체할 수 없지만 직접 경쟁 우위를 제공하지 않는 나머지 소프트웨어 컴포넌트

1.2. 현재의 설계 탐색

상위 수준 컴포넌트부터 탐색을 시작한다.

그리고 컴포넌트의 특성 중 수명주기를 분리할 수 있는지 찾는다.
하위 시스템이 같은 저장소에 있더라도 어느 것이 다른 컴포넌트와 독립적으로 개선되고 테스트되고 배포될 수 있는지 확인한다.

  • 전술적 설계 평가
    • 각 상위 수준 컴포넌트에 대해 그것이 어느 비즈니스 하위 도메인을 포함하고 어떤 기술적 설계 의사결정을 내렸는지 확인함
    • 비즈니스 로직 구현, 아키텍처 정의를 위해 어떤 패턴을 사용했는지 확인함
    • 위 내용들을 확인하여 해당 솔루션이 적절한지, 더 정교한 설계 패턴 혹은 비용을 절약할 수 있는 방법이나 기존 상용 솔루션을 사용할 수 있는 하위 도메인이 있는지 좀 더 현명한 전술적 의사 결정을 내림
  • 전략적 설계 평가
    • 상위 수준 컴포넌트에 대한 지식을 사용하여 이 컴포넌트가 바운디드 컨텍스트인 것처럼 현재 설계의 컨텍스트 맵을 차트로 표시하고, 바운디드 컨텍스트 연동 패턴 관점에서 컴포넌트 간의 관계를 식별함
    • 결과 컨텍스트 맵을 보고 DDD 관점에서 아키텍처를 평가함 (예를 들어 아래오 같이)
      • 동일한 상위 수준의 컴포넌트에 대해 작업하는 여러 팀
      • 핵심 하위 도메인의 중복 구현
      • 하청 회사가 핵심 하위 도메인을 구현
      • 자주 실패하는 연동으로 인한 마찰
      • 외부 서비스와 레거시 시스템에서 확산되는 어색한 모델

2. 현대화 전략

전체 시스템을 처음부터 다시 올바르게 설계하고 구현하기란 매우 어렵다.
기존 시스템의 설계를 개선하기 위한 좀 더 안전한 접근 방식은 크게 생각하되 작게 시작하는 것이다.
시스템의 현대화를 위해 어디에 노력을 투자할지 전략적으로 결정해야 하는데 이 결정을 내리기 위한 전제 조건은 시스템의 하위 도메임을 나누는 경계를 찾는 것이다.
물리적으로 경계를 나눌 필요는 없으며, 아래와 같이 최소한의 논리적 경계(모듈, 패키지 등)가 하위 도메인의 경계와 일치하는지 확인하는 것부터 시작한다.

아래는 기술적 구현 패턴이 아닌 비즈니스 하위 도메인의 경계를 반영하도록 바운디드 컨텍스트의 모듈을 재구성하는 예시이다.

  • AS-IS
    • 마케팅.애플리케이션
    • 마케팅.인프라스트퍽처
    • 마케팅.모델
    • 마케팅.모바일
    • 마케팅.서비스
    • 마케팅.UI
  • TO-BE
    • 마케팅.광고소재
    • 마케팅.캠페인
    • 마케팅.퍼블리싱

이렇게 시스템의 모듈을 조정하는 것은 비즈니스 로직을 수정하는 것이 아니라, 더 잘 구성된 구조로 유형을 재배치하는 것으로 비교적 안전한 형태의 리팩토링이다.


2.1. 전략적 현대화

이제 논리적 경계를 물리적 경계로 전환하여 가장 많은 가치를 얻을 수 있는 곳을 찾는다.

  • 여러 팀이 동일한 코드베이스에서 작업하고 있다면 각 팀에 대한 바운디드 컨텍스트를 정의하여 개발 수명주기를 분리함
  • 서로 다른 컴포넌트에서 충돌하는 모델을 사용하고 있다면, 충돌하는 모델을 별도의 바운디드 컨텍스트로 재배치함

그리고 최소 바운디드 컨텍스트가 있으면 이들 간의 관계와 연동 패턴을 조사한다.

  • 사용자-제공자 관계
    • 파트너십 관계를 위해 설계되었지만 파트너십이 더 이상 유지되지 않는 컴포넌트의 경우 적절한 사용자-제공자 관계(순응주의자, 충돌 방지 계층, 오픈 호스트 서비스)로 리팩터링
  • 충돌 방지 계층
    • 레거시 시스템이 다운스트림 컴포넌트로 확상되는 경향이 있는 비효율적인 모델을 사용하는 경우 레거시 시스템에서 바운디드 컨텍스트를 보호하는데 유용함
    • 업스트림 서비스의 퍼블릭 인터페이스에 대한 잦은 변경으로부터 바운디드 컨텍스트를 보호할 때도 유용함
  • 오픈 호스트 서비스
    • 한 컴포넌트 구현 상세에 대한 변경사항이 사용자에게 영향을 미치는 경우 오픈 호스트 서비스로 전환 (= 구현 모델을 퍼블릭 API 에서 분리)
  • 분리형 노선
    • 불화의 씨앗이 되는 기능이 비즈니스에 중요하지 않은 경우(= 핵심 하위 도메인이 아닌 경우) 분리선 노선 패턴을 적용하여 마찰의 원인 제거

2.2. 유비쿼터스 언어 육성

성공적인 현대화 설계의 전제 조건은 비즈니스 도메인 지식과 비즈니스 도메인의 효과적인 모델을 만드는 것이다.
도메인 지식과 해당 모델을 갖췄다면 비즈니스 기능에 가장 적합한 비즈니스 로직 구현 패턴을 결정한다.
시작점으로 설계 휴리스틱을 사용한다.

다음으로는 시스템의 전체 컴포넌트(스트랭글러 패턴)을 점진적으로 교체하거나, 기존 솔루션을 점진적으로 리팩터링하는 것이다.

<스트랭글러 패턴>

  • 새로운 바운디드 컨텍스트인 스트랭글러를 생성하고, 이 스트랭글러를 사용하여 새로운 요구사항을 구현하여 점차적으로 레거시 컨텍스트의 기능을 해당 컨텍스트로 마이그레이션함
  • 동시에 핫픽스와 기타 긴급 상황을 제외하고, 레거시 바운디드 컨텍스트의 개선과 개발은 중지함
  • 결국 모든 기능은 새로운 바운디드 컨텍스트인 스트랭글러로 마이그레이션되고, 숙주가 죽는 것과 유사하게 레거시 코드베이스는 제거됨

일반적으로 스트랭글러 패턴은 퍼사드 패턴(facade pattern) 과 함께 사용된다.
퍼사드 패턴의 얇은 추상화 계층은 퍼블릭 인터페이스 역할을 하며, 레거시 혹은 현대화된 바운디드 컨텍스트로 요청을 전달해 처리하는 역할을 한다.

레거시에서 현대화된 시스템으로 기능을 마이그레이션하는 상태에 따라 요청을 전달하는 퍼사드 레이어

퍼사드 (facade)

복잡한 서브 시스템을 단순하게 제공하기 위한 인터페이스 제공
즉, 시스템 구현 내부 내용을 몰라도 쉽게 사용할 수 있도록 하는 패턴

DDD - 진화하는 설계 의사 결정 에서 전술적 설계 의사결정을 마이그레이션하는 다양한 측면에 대해 알아보았다.
하지만 레거시 코드베이스를 현대화할 때는 2가지 미묘한 차이점이 있다.

  • 작은 점진적인 조치가 대규모 재작성보다 안전함
    • 트랜잭션 스크립트나 액티브 레코드를 이벤트 소싱 도메인 모델로 직접 리팩터링하지 말고, 대신 상태 기반 애그리거트를 설계하는 중간 단계를 진행함
    • 이 때 효과적인 애그리거트 경계를 찾는데 노력을 투자하고, 모든 관련 비즈니스 로직이 해당 경계 내에 있는지 확인함
    • 상태 기반 애그리거트에서 이벤트 소싱 애그리거트로 이동하는 것이 이벤트 소싱 애그리거트에서 잘못된 트랜잭션 경계를 발견하는 것보다 훨씬 더 안전함
  • 도메인 모델로의 리팩터링이 한 번에 이루어질 필요는 없음
    • 대신 도메인 모델 패턴의 요소들을 점진적으로 도입함
    • 가능하면 밸류 오브젝트를 찾는 것부터 시작함

트랜잭션 요구사항을 철저히 분석한 후에 애그리거트의 경계를 설계해야 한다.

마지막으로 필요 시 충돌 방지 계층을 사용하여 이전 모델로부터 새 코드베이스를 보호하고, 오픈 호스트 서비스를 구현하고 공표된 언어를 노출하여 레거시 코드베이스의 변경으로부터 사용자를 보호할 수 있다.


3. 실무에 활용하는 DDD

전술적 설계 패턴에 대해 설명할 때 아래처럼 논리에 기반한다.

  • 명시적 트랜잭션 경계가 중요한 이유는?
    • 데이터의 일관성을 보호하기 위해
  • DB 트랜잭션이 애그리거트의 둘 이상의 인스턴스를 수정할 수 없는 이유는?
    • 일관성 경계가 올바른지 확인하기 위해
  • 외부 컴포넌트에서 애그리거트의 상태를 직접 수정할 수 없는 이유는?
    • 모든 관련 비즈니스 로직을 함께 배치하고, 중복되지 않게 하기 위해
  • 애그리거트 기능 중 일부를 저장 프로시저로 넘길 수 없는 이유는?
    • 로직이 중복되지 않게 하기 위해
  • 왜 작은 애그리거트 경계가 필요한가?
    • 넓은 트랜잭션 범위는 애그리거트의 복잡성을 증가시키고, 성능에 부정적인 영향을 주므로
  • 이벤트 소싱 대신에 이벤트를 로그 파일에 기록할 수 없는 이유는?
    • 장기적으로 데이터 일관성이 보장되지 않으므로

참고 사이트 & 함께 보면 좋은 사이트

본 포스트는 블라드 코노노프 저자의 도메인 주도 설계 첫걸음을 기반으로 스터디하며 정리한 내용들입니다.






© 2020.08. by assu10

Powered by assu10