고기 대신 SW 한점/MSA

MSA - Aggregate introduce & design

지식한점 2023. 6. 20. 16:26
반응형

Aggregate 설계 관련 내용 정리(from Book : 도메인 주도 설계 핵심 by 반 버논)

왜 필요할까?

엔터티는 무엇인가?

엔터티는 독립적인 것이다. 각 엔터티는 같은 형태를 띠거나 다른 형태의 엔터티들과의 특성을 구별할 수 있는 고유한 식별성을 갖는다. 엔터티는 변할 수 있는 것이며, 여러 번, 아니 항상 그 상태는 계속해서 변할 수 있다. 하지만 엔터티가 꼭 변하는 것만은 아니고, 변하지 않을 수도 있다. 다른 모델링 수단들과 엔터티를 구분해주는 주 요인은 유일성, 즉 그것의 독립성에 있다.

 
 
값 객체는 무엇인가?

값 객체 또는 간단히 말해 값은 불변의 개념적 완전성을 모델링한다. 모델에서 값은 그야말로 값이다. 엔터티와 달리 고유한 식별성이 없으며, 값 형태로 캡슐화된 속성을 비교함으로써 동일함이 결정된다. 그뿐만 아니라 값 객체가 어떤 것을 나타낸다기 보다는 엔터티를 서술하고, 수량화하거나 측정하는 데 사용된다.

Aggregate(트랜잭션처리의 경계)은 무엇인가?

  • 각 애그리게잇은 1개 이상의 엔터티로 구성되고, 그중 한 엔터티는 에그리게잇 루트라고 부른다.
  • 애그리게잇은 그 구성에 값 객체를 포함할 수 있다.
  • 각 애그리게잇의 루트 엔터티는 애그리게잇 안의 다른 모든 요소를 소유한다.
  • 루트 엔터티의 명칭은 애그리게잇의 개념적 명칭이다. 애그리게잇이 모델링하는 개념적 완전성을 적절하게 표현할 수 있는 명칭으로 루트 엔터티 명칭을 정의해야 한다.
  • 각 애그리게잇은 일관성 있는 트랜잭션 경계를 형성한다. 트랜잭션 제어가 데이터베이스에 커밋될 때, 한 애그리게잇 내의 모든 구성 요소는 반드시 비즈니스 규칙을 따르면서 일관성 있게 처리된다는 것을 의미한다.
    • 다만, 애그리게잇 내에 트랜잭션 이후 일관성이 지켜질 필요가 없는 다른 요소를 포함해서는 안 된다는 뜻은 아니다. 애그리게잇은 개념적으로 완전하게 모델링 되어야 한다.
  • 하나의 트랜잭션에는 오직 1개의 애그리게잇만을 수정하고 커밋한다는 것이 애그리게잇 설계 규칙이라고 가정하자.
넓은 의미의 트랜잭션
  • 애플리케이션에 대한 트랜잭션 사용은 어느 정도 구현에 대한 세부 사항이다. 반드시 원자적 트랜잭션을 의미하는 것은 아니다.
  • 트랜잭션이란 애그리게잇에 대한 변경을 독립시키고, 소프트웨어가 언제나 충실히 준수해야 하는 규칙인 비즈니스 불변성을 각 비즈니스 오퍼레이션에 맞게 일관성을 보장하는 방법이다.
  • 이 요구사항을 원자적 데이터베이스 트랜잭션으로 처리하거나 다른 방법으로 처리하는 것과는 상관없이, 애그리게잇의 상태나 이벤트 소싱은 항상 안전하고 정확하게 트랜잭션으로 처리하고 관리해야 한다.

 

 

Aggregate 경험 법칙

애그리게잇 설계의 네 가지 기본 규칙

  1. 애그리게잇 경계 내에서 비즈니스 불변사항들을 보호하라.
  2. 작은 애그리게잇을 설계하라.
  3. 오직 ID를 통해 다른 애그리게잇을 참고하라.
  4. 결과적 일관성을 사용해 다른 애그리게잇을 갱신하라.

이 규칙들을 반드시 엄격하게 지키라고 강요하는 것은 아니지만, DDD를 적용할 때, 효과적으로 동작하는 애그리게잇을 설계할 수 있도록 도움을 주는 가이드로 받아들이자.

규칙 1: 애그리게잇 경계 내의 비즈니스 불변 사항을 보호하라

애그리게잇 하위에 있는 모든 것이 반드시 애그리게잇 루트와 일관되게 처리되도록 설계한다.

예) "모든 Task(작업) 인스턴스의 hours Remaining(남은 시간)이 0일 때, BacklogItem의 status(상태)는 반드시 DONE(완료)으로 설정해야 한다."

규칙 2: 작은 Aggregate을 설계하라

  • 각 애그리게잇의 메모리 사용량과 트랜잭션 범위는 비교적 작아야 한다.
  • 큰 애그리게잇을 설계하면 시간이 지나면서 내부에 큰 컬렉션을 가지게 되어 엄청나게 불어날 수 있는데 일반적으로 이런 설계 방식은 매우 나쁜 선택이다.
  • 큰 애그리게잇을 작은 애그리게잇 여러 개로 쪼개면 더 빠르게 로드되고, 더 작은 메모리를 차지하며 더 자주 성공적인 트랜잭션을 수행한다.
  • 이 규칙을 따르면, 각 애그리게잇을 파악하고 테스트하기 좀 더 쉬워지는 부가적인 이득을 얻을 수 있다.
  • 애그리게잇을 설계할 때는 SRP를 생각하자. 애그리게잇이 너무 많은 일을 한다면 SRP를 따르지 않는 것이다.

규칙 3: 오직 식별자로만 다른 애그리게잇을 참고하라

  • 쪼개진 각각의 작은 애그리게잇들은 동일한 식별자를 유지하여 서로를 참고할 수 있다.
  • 이 방법을 적용하면 애그리게잇을 작게 유지하고, 동일한 트랜잭션 내에 여러 애그리게잇을 수정하려는 접근을 방지해준다.
  • 식별자만을 통해 레퍼런스를 얻는 규칙의 또 다른 이점은 애그리게잇을 관계형 데이터베이스, 문서 데이터베이스, 키/밸류 저장소 그리고 데이터 그리드/패브릭과 같은 다른 형태의 저장 매커니즘으로도 쉽게 저장할 수 있다는 것이다.

규칙 4: 결과적 일관성을 사용해 다른 애그리게잇을 갱신하라

  • 한 애그리게잇이 변경되었을 때 트랜잭션의 일부로 도메인 이벤트를 발행한다.
  • 다른 애그리게잇에 도메인 이벤트가 전달되면 새로운 트랜잭션이 시작되어 다른 애그리게잇도 갱신이 된다.
결과적 일관성이 두렵게 느껴진다면
  • 결과적 일관성을 사용함에 있어 엄청나게 힘든 점은 없지만 실제 경험해보기 전까지는 걱정이 있을 수 있다.
  • 그렇다고 해도 모델을 애그리게잇으로 분리시켜야 한다. 이는 초기 단계부터 너무 거대한 처리를 만들지 않도록 해주는 기법이다. 이것이 애그리게잇을 사용하는 근본적인 이유는 아니지만, 결과적으로 트랜잭션의 실패 경험을 줄여줄 수 있을 것이다.

Aggregate 모델링

  • 도메인 모델 관련, 애그리게잇 구현에 대한 작업을 할 때 가장 만나기 쉬운 함정은 빈약한 도메인 모델이다.
    • 모든 애그리게잇이 비즈니스 행위가 아닌 읽고 쓰는 공개 접근자만을 갖는 것이다.
  • 비즈니스 로직이 도메인 모델을 넘어 애플리케이션 서비스까지 새어 나가지 않도록 주의해야 한다.
    • 비즈니스 로직을 helper 클래스나 utility 클래스에 위임하는 것은 원하는대로 동작하지 않는다.
함수형 프로그래밍의 경우에는?
  • 함수형 프로그래밍은 데이터와 행위의 분리를 장려하기 때문에 빈약한 도메인 모델을 갖는 것이 일반적일 수 있다.
  • 함수형 프로그래밍에서는 불변하는 데이터 구조나 레코드 유형으로 데이터를 설계하고, 이렇게 설계한 데이터를 제어하는 순수한 함수로 행위를 구현한다.
  • 함수가 인자로 받은 데이터를 수정하는 대신, 함수가 새로운 값을 반환한다. 이 새로운 값은 애그리게잇의 상태 변환을 나타내주는 도메인 이벤트 또는 애그리게잇의 새로운 상태가 된다.
  • 함수형 언어를 사용하여 DDD를 적용하고자 한다면 이 책의 일부 내용은 해당되지 않거나 규칙을 재정의해야 할 수도 있다.

 

애그리게잇 구현

  1. 애그리게잇 루트 엔터티 클래스를 생성한다. 모든 애그리게잇 루트 엔터티는 전체 시스템에서 고유한 식별성을 가져야 한다. 식별자는 값 객체로 모델링할 수 있다.
  2. 다음으로 애그리게잇을 찾는 데 필요한 본질적 속성이나 필드들을 정의한다. 본질적 속성을 위해 getter와 같은 간단한 행위를 추가할 수 있다. 하지만 setter는 정말 필요한 경우가 아니면 공개하지 않는다.
  3. 비즈니스 단위의 복잡한 행위를 추가한다. 애그리게잇의 모든 부분은 Ubiquitous Language에 의해 모델링해야 한다. 모든 것이 조화를 이룰 수 있도록 도메인 전문가와 개발자 사이에 긴밀한 협업이 필요하다.

 

 

 

 

추상화를 조심스럽게 선택하라

Ubiquitous Language와 관련된 가이드를 따른다면 적절한 추상화를 설정할 수 있다. 하지만 가끔은 잘못된 문제를 푸는 것에 지나치게 몰두한 나머지, 소프트웨어 개발자가 지나칠 정도로 추상화를 적용하기도 한다.

이렇게 과도한 추상화를 적용할 경우의 문제점은 다음과 같다.

  • 소프트웨어 모델의 언어가 도메인 전문가의 멘탈 모델과 일치하지 않는다.
  • 추상화 수전이 너무 높아서 각 개별적인 형태의 세부 사항을 모델링하기 시작하면 어려운 상황에 빠질 것이다.
  • 그러면 각각의 클래스마다 특수한 경우를 정의할 것이고, 복잡한 클래스 계층 구조를 만들 것이다.
  • 우선적으로 중요하지 않은 문제를 해결하려다 코드의 양이 많이 증가할 것이다.
  • 잘못된 추상화 수준은 사용자 인터페이스까지 영향을 미칠 수 있다.
  • 상당한 시간과 비용을 낭비할 수 있다.
  • 프로젝트 초반에 미래의 모든 요구를 생각하고 반영할 수는 없으며 기존 모델은 새로운 요구사항을 예견하는데 실패할 수밖에 없다.

높은 수준의 추상화 구현 대신 팀이 정의한 도메인 전문가의 멘탈 모델에 따라 Ubiquitous Language로 모델링해야 한다. 비즈니스가 당장 요구하는 것만을 모델링하고 필요없는 것은 만들지 않는다.

 

올바른 크기의 애그리게잇

  1. 먼저 "작은 애그리게잇을 설계하라"라는 규칙에 집중한다. 애그리게잇 루트로 제공될 오직 1개의 엔터티만을 갖는 애그리게잇들을 생성한다. 각 엔터티를 단일 루트 엔터티와 가장 관련이 깊다고 생각되는 필드/속성/프로퍼티로 채우자. 가장 주의할 점은 애그리게잇을 식별하고 찾는 데 필요한 모든 필드/속성/프로퍼티를 정의하는 것 뿐만 아니라 애그리게잇을 초기에 만들 때 유효한 초기 상태를 구성하는데 필요한 모든 추가적인 필드/속성/프로퍼티를 정의하는 것이다.
  2. "애그리게잇 경계 내의 비즈니스 불변사항을 보호하라"에 관심을 가지자. 애그리게잇을 하나씩 살펴보고 이미 정의한 다른 애그리게잇들 중에 함께 갱신되어야 하는 것이 있는지 도메인 전문가와 함께 확인한다. 애그리게잇의 행위에 관련된 모든 갱신에 걸리는 시간을 파악할 수 있는 관련된 각 애그리게잇의 목록과 일관성 규칙을 만든다.
  3. 반응에 맞춘 갱신이 일어나는 시간은 얼마나 걸릴지 도메인 전문가에게 확인하자. (a) 즉시 또는 (b) N초/분/시간/일과 같은 두 가지 유형의 명세로 정의될 것이다. 올바른 비즈니스 임계치를 찾는 한 가지 가능한 방법은 받아들여질 수 없는 과장된 소요 시간(몇 달 등)을 먼저 제시하는 것이다. 그러면 비즈니스 전문가가 받아들일 수 있는 소요시간을 대답할 것이다.
  4. 각각의 애그리게잇들이 즉시 처리돼야 할 경우, 동일한 애그리게잇 경계 안에 그 2개의 엔터티를 구성하는 것을 긍정적으로 검토한다.
  5. 각각의 애그리게잇들이 주어진 시간에 따라 각각 반응하는 경우, 애그리게잇 설계의 네 번째 규칙인 "결과적 일관성을 사용해 다른 애그리게잇을 갱신하라"를 사용해서 갱신한다.

모든 애그리게잇이 함께 즉각적인 갱신에 들어가야 한다고 비즈니스 측에서 일방적으로 주장하지는 않는지 주의를 기울여야 한다. 특히 회의에 참석하는 사람들이 데이터베이스 설계와 데이터 모델링에 영향을 받을 때 이런 경향이 강하다. 하지만 실제로 비즈니스가 모든 상황에 즉각적인 일관성을 요구할 가능성은 매우 낮다.

이런 활동은 결과적 일관성이 기술 주도가 아닌, 비즈니스 주도라는 것을 보여준다. 비즈니스적으로 즉시 또는 적절한 트랜잭션으로 처리돼야 하는 것들은 동일한 애그리게잇으로 관리해야 하며, 결과적인 일관성이 필요한 경우에는 메시징과 같은 도메인 이벤트를 통해 관리해야 한다는 의미이다.

 

테스트 가능한 단위

단위 테스트를 통해 애그리게잇을 철저하게 캡슐화되도록 설계하자. 복잡한 애그리게잇은 테스트하기도 힘들다.
여기서 고려해야 하는 것은 각 애그리게잇이 기대대로 정확하게 수행되는지 테스트하는 것이다.

반응형

'고기 대신 SW 한점 > MSA' 카테고리의 다른 글

[Service Mesh] istio - injection 방법 알아보기  (0) 2022.11.17