Programming/MSA

Domain Driven Design/Eric Evance - 5. A Model Expressed in Software

armyost 2022. 7. 21. 17:38
728x90


결합
가능한 한 관계를 제한하는 것이 중요합니다. 양방향 결합은 두 객체가 함께있어야만 이해될 수 있음을 의미합니다. 응용 프로그램 요구 사항이 양방향 순회를 요구하지 않는 경우 순회 방향을 추가하면 상호 의존성이 줄어들고 설계가 단순화됩니다. 도메인을 이해하면 자연스럽게 방향성을 알수 있습니다.

미국에는 다른 많은 국가와 마찬가지로 많은 대통령이 있습니다. 이것은 양방향, 일대다 관계입니다. 그러나 우리는 "조지 워싱턴"이라는 이름으로 시작하여 "그는 어느 나라의 대통령이었습니까?"라고 묻는 경우는 거의 없습니다. 실용적으로 우리는 "국가"에서 "대통령"으로 이동하는 단방향 연결 관계를 줄일 수 있습니다. 이 개선은 실제로 도메인에 대한 통찰력을 반영할 뿐만 아니라 보다 실용적인 디자인을 만듭니다. 그것은 결합의 방향을 이해하는게 다른것보다 훨씬 더 의미 있고 중요하다는 것입니다. 이것은 "President"라는 덜 근본적인 개념을 통해 "Person" 클래스를 독립적으로 만듭니다. 

종종 더 깊은 이해는 "자격을 갖춘" 관계로 이어집니다. 대통령에 대해 더 깊이 들여다보면 한 국가에는 한 번에 한 명의 대통령만 있다는 것을 알 수 있습니다. 이 제약사항은 다중성을 일대일로 줄이고 중요한 규칙을 모델에 명시적으로 포함합니다. "1790년에 미국 대통령은 누구였습니까?" 제한된 연관은 더 많은 지식과 더 실용적인 디자인을 전달합니다. 다대다 연관의 순회 방향을 제한하면 구현을 일대다로 효과적으로 줄여 훨씬 쉽게 설계할 수 있습니다. 물론 궁극적인 단순화는 당면한 작업이나 모델 객체의 근본적인 의미에 필수적이지 않은 경우 연관을 완전히 제거하는 것입니다.

연관성을 더 다루기 쉽게 만드는 방법에는 최소 세 가지가 있습니다. 
1. 순회 방향성을 투영시킴 
2. 제약사항을 추가하여 다중성을 효과적으로 감소 
3. 필수적이지 않은 연관 제거


예시



public class BrokerageAccount {
    String accountNumber;
    Customer customer;
    Set investments;
  // Constructors, etc. omitted

public Customer getCustomer() {
    return customer;
}

public Set getInvestments() {
    return investments;
  }
}

중개 계정과 투자 간의 연결을 제한하고 다중성을 줄임으로써 모델을 개선해 보겠습니다.



public class BrokerageAccount {
  String accountNumber;
  Customer customer;
  Map investments;
 // Omitting constructors, etc.

public Customer getCustomer() {
    return customer;
}

public Investment getInvestment(String stockSymbol) {
    return (Investment)investments.get(stockSymbol);
  }
}

Entity
객체 모델링은 객체의 속성에 초점을 맞추는 경향이 있지만 ENTITY의 기본 개념은 라이프 사이클을 통과하고 여러 형태를 통과하는 추상 연속성입니다. 일부 개체는 기본적으로 속성으로 정의되지 않습니다. 그것들은 시간과 구현체를 가로질러 실행되는 정체성의 Thread를 뜻합니다. 속성이 다르더라도 이러한 개체는 다른 개체와 일치해야 하는 경우가 있습니다. 객체는 속성이 같더라도 다른 객체와 구별되어야 합니다. 잘못된 Identity는 데이터 충돌로 이어질 수 있습니다.

개체가 속성이 아닌 Identity로 구별되는 경우 이를 모델의 정의에 기본으로 설정합니다. 클래스 정의를 단순하게 유지하고 라이프 사이클 연속성과 Identity에 중점을 둡니다. 형식이나 히스토리에 관계없이 각 개체를 구별하는 수단을 정의합니다. 속성별로 개체를 일치시켜야 하는 요구 사항에 주의하십시오. 각 개체에 대해 고유한 결과를 생성토록 보장하는 작업을 정의해야합니다. 이 식별 수단은 외부에서 올 수도 있고 시스템에 의해 그리고 시스템을 위해 생성된 임의의 식별자일 수 있지만 모델의 식별 구분과 일치해야 합니다. 모델은 동일하다는 것이 무엇을 의미하는지 정의해야합니다. 

정체성의 정의는 모델에서 나옵니다. ID를 정의하려면 도메인에 대한 이해가 필요합니다

ENTITIES의 ID를 추적하는 것은 필수적이지만 ID를 다른 개체에 연결하면 시스템 성능이 저하되고 분석 작업이 추가되며 모든 개체를 동일하게 보이게 하여 모델을 혼란스럽게 만들 수 있습니다. 소프트웨어 디자인은 복잡성과의 끊임없는 싸움입니다. 필요한 경우에만 특수 처리가 적용되도록 구분해야 합니다. 

모델 요소의 속성에만 관심이 있는 경우 VALUE OBJECT로 분류하십시오. 전달코자하는 속성의 의미를 표현하고 기능과 연결시킵니다. VALUE OBJECT는 변경할 수 없는 것으로 생각하세요. 아이덴티티를 부여하지 말고 ENTITIES를 유지하기 위한 복잡한 디자인을 피하십시오.



Designing VALUE OBJECTS
VALUE OBJECT가 설계에서 변경할 수 없는 것으로 지정되면 개발자는 응용 프로그램이 개체의 특정 인스턴스에 의존하지 않는다는 지식을 바탕으로 순전히 기술적인 기반으로 복사 및 공유와 같은 문제에 대해 자유롭게 결정할 수 있습니다.


When to Allow Mutability
불변성은 구현을 크게 단순화하여 공유 및 참조 전달을 안전하게 만듭니다. Value의 의미와도 일맥상통한다. 속성 값이 변경되면 기존 값을 수정하는 대신 다른 VALUE OBJECT를 사용합니다. 그럼에도 불구하고 VALUE OBJECT를 변경할 수 있도록 허용하는 것이 성능 고려 사항인 경우가 있습니다. 이러한 요소는 변경 가능한 구현에 유리합니다.

VALUE OBJECTS 간의 양방향 연결을 완전히 제거하십시오. 결국 이러한 연결이 모델에서 필요해 보인다면 먼저 객체를 VALUE OBJECT로 선언하기로 한 결정을 재고하십시오. 아직 명시적으로 인식되지 않은 정체성이 있을 수 있습니다. 



Service
본질적으로 사물이 아닌 활동이나 행동이지만, 우리의 모델링 패러다임은 객체이기 때문에 어쨌든 객체에 맞추려고 합니다. 이제 더 흔한 실수는 행동을 적절한 객체에 맞추는 것을 너무 쉽게 포기하고 점차적으로 절차적 프로그래밍으로 미끄러지는 것입니다. 그러나 객체의 정의에 맞지 않는 객체에 작업을 강제로 적용하면 객체는 개념적 명확성을 잃고 이해하거나 리팩토링하기 어려워집니다. 

도메인의 일부 개념은 개체로 모델링하는 것이 자연스럽지 않습니다. 필수 도메인 기능을 ENTITY 또는 VALUE의 책임으로 강제하는 것은 모델 기반 객체의 정의를 왜곡하거나 무의미한 인공 객체를 추가합니다.

SERVICE는 ENTITIES 및 VALUE OBJECTS와 같이 상태를 캡슐화하지 않고 모델에서 단독으로 독립하는 인터페이스로 제공되는 작업입니다

SERVICE에는 여전히 정의된 책임이 있어야 하며 해당 책임과 이를 수행하는 인터페이스는 도메인 모델의 일부로 정의되어야 합니다. 작업 이름은 UBIQUITOUS LANGUAGE에서 가져오거나 해당 언어로 도입되어야 합니다. 매개변수와 결과는 도메인 개체여야 합니다.

좋은 서비스에는 세 가지 특징이 있습니다. 
1. 작업이 ENTITY 또는 VALUE OBJECT의 자연스러운 부분이 아닌 도메인 개념과 관련됩니다. 
2. 인터페이스는 도메인 모델의 다른 요소로 정의됩니다. 
3. 작업은 상태 비저장입니다.

여기서 비저장 상태는 모든 클라이언트가 인스턴스의 개별 history에 관계 없이 특정 SERVICE의 모든 인스턴스를 사용할 수 있음을 의미합니다. SERVICE의 실행은 전역적으로 액세스 가능한 정보를 사용하며 해당 전역 정보를 변경할 수도 있습니다(즉, 부작용이 있을 수 있음). 그러나 SERVICE는 대부분의 도메인 개체와 같이 자체 동작에 영향을 주는 자체 상태를 보유하지 않습니다. 

도메인에 대한 중요한 프로세스 또는 변환이 ENTITY 또는 VALUE OBJECT의 책임이 아니라면,  SERVICE로 선언된 독립형 인터페이스로 모델에 Operation을 추가해보세요. 모델의 언어로 인터페이스를 정의하고 작업 이름이 UBIQUITOUS LANGUAGE의 일부인지 확인하십시오. SERVICE를 상태 비저장으로 만들어야 합니다.

많은 Domain Service 또는 Application Service는 ENTITIES 및 VALUES 개체군 상위에 구축되어 실제로 무언가를 완료할 수 있는 도메인의 잠재력을 구성하는 스크립트처럼 작동합니다. ENTITIES 및 VALUE OBJECTS는 종종 너무 세분화되어 도메인 계층의 기능에 편리하게 액세스할 수 없습니다.

Application Service와 Domain Service를 구분하는것은 어렵다. 예를들어 은행계좌 정보를 Excel로 내보내기 하는것은 Application Service이다. 왜냐하면 Banking 도메인에는 "File format"이라는 의미가 없고 비즈니스 룰도 없다. 반면에 자금을 한계좌에서 다른 계좌로 이동하는것은 Domain Service이다. 왜냐하면 이것은 상당히 중요한 비즈니스 룰을 포함하고 있기 때문이다. 그리고 "Fund Transfer"는 Banking 도메인에서 매우 의미있는 존재이기 때문이다. 


중간 수준으로 분할된 Stateless Service는 간단한 인터페이스 뒤에 중요한 기능을 캡슐화하기 때문에 대규모 시스템에서 더 쉽게 재사용할 수 있습니다. 또한 세분화된 개체는 분산 시스템에서 비효율적인 메시징으로 이어질 수 있습니다.


Modules (a.k.a Packages)
도메인 Layer에서 Module이란 모델의 의미있는 부분을 밖으로 보여주면서 도메인의 스토리를 큰 관점에서 이야기 해준다. 모든사람이 모듈을 사용하지만 극 소수만이 모듈을 모델의 모든 자격을 갖춘 부분으로 다루고 있다. 

동일한 개체가 아니더라도 단일 개념 개체를 구현하는 모든 코드를 동일한 모듈에 유지합니다. 

패키징을 사용하여 도메인 계층을 다른 코드와 분리합니다. 그렇지 않으면 도메인 개발자가 도메인 모델 및 디자인 선택을 지원하는 방식으로 도메인 개체를 패키징할 수 있는 자유를 최대한 많이 남겨두십시오.