[oop] DDD-엔티티가 리포지토리에 직접 액세스 할 수없는 규칙

도메인 기반 디자인에서있을 것 같습니다 제비계약 이 엔티티해야하지 액세스 저장소 직접.

이것은 Eric Evans Domain Driven Design 서적 에서 나왔습니까 아니면 다른 곳에서 나왔습니까?

그 뒤에 추론에 대한 좋은 설명이 어디에 있습니까?

편집 : 명확히하기 위해 : 비즈니스 논리와 별도의 계층으로 데이터 액세스를 분리하는 고전적인 OO 관행에 대해 이야기하고 있지 않습니다 .DDD에서 엔티티가 데이터와 대화하지 않아야하는 특정 배열에 대해 이야기하고 있습니다. 액세스 계층 전혀 없음 (즉, 리포지토리 객체에 대한 참조를 보유하지 않아야 함)

업데이트 : 나는 그의 대답이 가장 가깝게 보였기 때문에 BacceSR에 현상금을 주었지만 여전히 이것에 대해 어두운 상태에 있습니다. 중요한 원칙이라면 온라인 어딘가에 좋은 기사가 있어야합니까?

업데이트 : 2013 년 3 월, 질문에 대한 지지자들은 이것에 많은 관심이 있음을 암시하며 많은 답변이 있었음에도 불구하고 사람들이 이것에 대한 아이디어를 가지고 있다면 더 많은 여지가 있다고 생각합니다.



답변

여기에 약간의 혼란이 있습니다. 리포지토리는 집계 루트에 액세스합니다. 집계 루트는 엔티티입니다. 그 이유는 우려와 분리의 분리 때문입니다. 소규모 프로젝트에서는 이치에 맞지 않지만 대규모 팀의 경우 “제품 리포지토리를 통해 제품에 액세스합니다. 제품은 ProductCatalog 개체를 포함하여 엔터티 모음의 총체입니다. ProductCatalog를 업데이트하려면 ProductRepository를 거쳐야합니다. “

이러한 방식으로 비즈니스 로직과 사물이 업데이트되는 위치를 매우 명확하게 분리 할 수 ​​있습니다. 혼자서 아이를 낳지 않고 복잡한 모든 일을 제품 카탈로그에 작성하는이 전체 프로그램을 작성하고, 업스트림 프로젝트에 통합 할 때이를보고 깨닫고 있습니다. 다 버려야합니다. 또한 사람들이 팀에 합류하고 새로운 기능을 추가 할 때 갈 곳과 프로그램 구성 방법을 알고 있습니다.

하지만 기다려! 리포지토리는 리포지토리 패턴에서와 같이 지속성 계층을 나타냅니다. 더 나은 세상에서 Eric Evans의 Repository와 Repository Pattern은 이름이 약간 겹치므로 별개의 이름을 갖습니다. 저장소 패턴을 얻으려면 서비스 버스 또는 이벤트 모델 시스템을 사용하여 데이터에 액세스하는 다른 방법과 대조됩니다. 일반적으로이 레벨에 도달하면 Eric Evans의 저장소 정의가 나란히 진행되고 경계 컨텍스트에 대해 이야기하기 시작합니다. 각각의 경계 컨텍스트는 본질적으로 자체 응용 프로그램입니다. 제품 카탈로그로 물건을 가져 오기위한 정교한 승인 시스템이있을 수 있습니다. 오리지널 디자인에서 제품은 중심 제품이지만이 제한된 맥락에서 제품 카탈로그가 있습니다. 여전히 서비스 버스를 통해 제품 정보에 액세스하고 제품을 업데이트 할 수 있습니다.

원래 질문으로 돌아 가기 엔터티 내에서 리포지토리에 액세스하는 경우 엔터티가 실제로 비즈니스 엔터티가 아니라 서비스 계층에 존재해야하는 항목임을 의미합니다. 엔터티는 비즈니스 개체이므로 가능한 한 DSL (도메인 특정 언어)과 비슷해야합니다. 이 계층에는 비즈니스 정보 만 있습니다. 성능 문제를 해결하는 경우 비즈니스 정보 만 있어야하므로 다른 곳을 찾아야합니다. 갑자기 응용 프로그램 문제가 발생하면 응용 프로그램을 확장 및 유지 관리하기가 매우 어려워집니다. 실제로 DDD의 핵심은 유지 관리 가능한 소프트웨어 만들기입니다.

의견에 대한 답변 1 : 옳고 좋은 질문입니다. 따라서 모든 유효성 검사가 도메인 계층에서 발생하는 것은 아닙니다 . Sharp는 원하는 것을 수행하는 “DomainSignature”속성을 가지고 있습니다. 지속성을 인식하고 있지만 속성은 도메인 계층을 깨끗하게 유지합니다. 예를 들어 동일한 이름을 가진 중복 엔티티가 없는지 확인합니다.

그러나 더 복잡한 유효성 검사 규칙에 대해 이야기합시다. Amazon.com이라고 가정 해 봅시다. 신용 카드 유효 기간이 만료 된 상품을 주문한 적이 있습니까? 카드를 업데이트하지 않고 무언가를 구입 한 곳이 있습니다. 주문을 수락하고 UI는 모든 것이 복숭아라는 것을 알려줍니다. 약 15 분 후에 주문에 문제가 있음을 알리는 이메일을 받게되는데 신용 카드가 유효하지 않습니다. 여기서 일어나는 일은 이상적으로 도메인 계층에 정규식 유효성 검사가 있다는 것입니다. 올바른 신용 카드 번호입니까? 그렇다면 주문을 유지하십시오. 그러나 응용 프로그램 작업 계층에는 신용 카드로 결제가 가능한지 외부 서비스를 쿼리하는 추가 유효성 검사가 있습니다. 그렇지 않은 경우 실제로 배송하지 말고 주문을 일시 중지하고 고객을 기다리십시오.

서비스 계층에서 리포지토리에 액세스 할 수 있는 유효성 검사 개체를 만드는 것을 두려워하지 마십시오 . 도메인 계층에서 제외하십시오.


답변

처음에, 나는 나의 실체 중 일부가 저장소 (즉, ORM이없는 게으른 로딩)에 접근 할 수 있도록 설득했다. 나중에 나는해서는 안되며 다른 방법을 찾을 수 있다는 결론에 도달했습니다.

  1. 요청에 대한 의도와 도메인에서 원하는 것을 알아야하므로 집계 동작을 구성하거나 호출하기 전에 리포지토리 호출을 만들 수 있습니다. 또한 일관성없는 인 메모리 상태 문제와 지연로드가 필요하지 않도록합니다 (이 기사 참조 ). 냄새는 데이터 액세스에 대해 걱정하지 않고 더 이상 엔티티의 메모리 인스턴스를 만들 수 없다는 것입니다.
  2. CQS (Command Query Separation)는 엔티티의 항목에 대한 저장소를 호출하려는 필요성을 줄이는 데 도움이됩니다.
  3. 사양 을 사용하여 도메인 로직 요구를 캡슐화하고 통신하고이를 저장소에 전달할 수 있습니다 (서비스는 이러한 것들을 조정할 수 있습니다). 사양은 해당 불변을 유지하는 책임이있는 실체로부터 나올 수 있습니다. 저장소는 사양의 일부를 자체 쿼리 구현으로 해석하고 사양의 규칙을 쿼리 결과에 적용합니다. 이것은 도메인 계층에서 도메인 로직을 유지하는 것을 목표로합니다. 또한 유비쿼터스 언어와 의사 소통을 더 잘 제공합니다. “지연된 주문 스펙”과 “ts_at가 sysdate 30 분 전인 tbl_order의 필터 주문”이라고 말하는 것을 상상해보십시오 (이 답변 참조 ).
  4. 단일 책임 원칙을 위반하기 때문에 엔터티의 동작에 대한 추론이 더 어려워집니다. 스토리지 / 지속성 문제를 해결해야 할 경우 갈 곳과 갈 곳을 알고 있습니다.
  5. (저장소 및 도메인 서비스를 통해) 엔티티에 글로벌 상태에 대한 양방향 액세스를 제공 할 위험이 없습니다. 또한 거래 경계를 어 기고 싶지 않습니다.

도메인 기반 디자인 구현이라는 빨간 책의 Vernon Vaughn은 내가 알고있는 두 곳에서이 문제를 언급합니다. 서비스의 7 장에서는 도메인 서비스와 사양을 사용하여 집계가 저장소를 사용하고 다른 집계가 사용자 인증 여부를 결정해야하는 필요성을 해결했습니다. 그는 다음과 같이 인용했다.

경험상 우리는 가능한 경우 집계 내부에서 저장소 (12)를 사용하지 않도록 노력해야합니다.

본 버논 (2013-02-06). 도메인 기반 디자인 구현 (Kindle Location 6089). 피어슨 교육. 킨들 에디션.

그리고 10 장 집합에서 “모델 탐색”이라는 제목섹션 (다른 집합 루트를 참조하기 위해 전역 고유 ID 사용을 권장 한 직후)은 다음과 같습니다.

ID로 참조한다고해서 모델을 탐색 할 수있는 것은 아닙니다. 일부는 검색을 위해 집계 내부에서 리포지토리 (12)를 사용합니다. 이 기술을 연결이 끊어진 도메인 모델이라고하며 실제로 지연로드 형식입니다. 그러나 다른 권장 방법이 있습니다. 리포지토리 또는 도메인 서비스 (7)를 사용하여 집계 동작을 호출하기 전에 종속 개체를 찾아보십시오. 클라이언트 응용 프로그램 서비스가이를 제어 한 다음 집계로 전달할 수 있습니다.

그는 코드에서 이것의 예를 보여줍니다.

public class ProductBacklogItemService ... {

   ...
   @Transactional
   public void assignTeamMemberToTask(
        String aTenantId,
        String aBacklogItemId,
        String aTaskId,
        String aTeamMemberId) {

        BacklogItem backlogItem = backlogItemRepository.backlogItemOfId(
                                        new TenantId( aTenantId),
                                        new BacklogItemId( aBacklogItemId));

        Team ofTeam = teamRepository.teamOfId(
                                  backlogItem.tenantId(),
                                  backlogItem.teamId());

        backlogItem.assignTeamMemberToTask(
                  new TeamMemberId( aTeamMemberId),
                  ofTeam,
                  new TaskId( aTaskId));
   }
   ...
}

또한 도메인 서비스를 집계 명령 방법에서 double-dispatch 와 함께 사용하는 방법에 대한 또 다른 솔루션을 언급 합니다. (저는 그의 책을 읽는 것이 얼마나 유익한 지 충분히 추천 할 수는 없습니다. 인터넷을 통해 끝없이 뒤섞이는 것에 지친 후, 충분한 돈을 벌고 책을 읽으십시오.)

그런 다음 항상 은혜로운 Marco Pivetta @Ocramius토론 하여 도메인에서 사양을 가져 와서 사용하는 데 약간의 코드를 보여주었습니다.

1) 이것은 권장되지 않습니다 :

$user->mountFriends(); // <-- has a repository call inside that loads friends?

2) 도메인 서비스에서 이것은 좋습니다 :

public function mountYourFriends(MountFriendsCommand $mount) { /* see http://store.steampowered.com/app/296470/ */
    $user = $this->users->get($mount->userId());
    $friends = $this->users->findBySpecification($user->getFriendsSpecification());
    array_map([$user, 'mount'], $friends);
}


답변

아주 좋은 질문입니다. 이에 대한 토론을 기대하겠습니다. 그러나 나는 그것이 여러 DDD 서적 과 Jimmy nilssons와 Eric Evans에 언급되어 있다고 생각합니다 . reposistory 패턴을 사용하는 방법을 예제를 통해 볼 수 있다고 생각합니다.

그러나 토론 할 수 있습니다. 나는 매우 유효한 생각이 다른 엔티티를 유지하는 방법에 대해 엔티티가 알아야하는 이유라고 생각합니다. DDD에서 중요한 것은 각 엔터티가 자체 “지식 영역”을 관리 할 책임이 있으며 다른 엔터티를 읽거나 쓰는 방법에 대해 아는 것이 없다는 것입니다. 엔티티 B를 읽기 위해 엔티티 A에 저장소 인터페이스를 추가 할 수도 있습니다. 그러나 위험은 B를 유지하는 방법에 대한 지식을 노출시키는 것입니다. 엔티티 A도 B를 db로 지속하기 전에 B에 대한 유효성 검증을 수행합니까?

보다시피 엔터티 A는 엔터티 B의 수명주기에 더 많이 관여 할 수 있으며 모델에 더 복잡해질 수 있습니다.

단위 테스팅이 더 복잡 할 것입니다 (예없이).

그러나 엔터티를 통해 리포지토리를 사용하려는 경향이있는 시나리오가 항상있을 것이라고 확신합니다. 유효한 판단을하려면 각 시나리오를 살펴 봐야합니다. 장점과 단점. 그러나 필자의 의견으로는 저장소 엔터티 솔루션은 많은 단점으로 시작합니다. 그것은 단점을 균형 잡는 전문가와 매우 특별한 시나리오 여야합니다 ….


답변

왜 데이터 액세스를 분리해야합니까?

이 책에서 Model Driven Design 장의 처음 두 페이지는 도메인 모델 구현에서 기술적 구현 세부 사항을 추상화하려는 이유에 대한 정당성을 제공한다고 생각합니다.

  • 도메인 모델과 코드를 밀접하게 연결하려고합니다.
  • 기술적 문제를 분리하면 모델이 구현에 실용적임을 증명할 수 있습니다
  • 유비쿼터스 언어가 시스템 설계에 스며 들기를 원합니다.

이것은 시스템의 실제 구현과 분리되는 별도의 “분석 모델”을 피하기위한 것입니다.

이 책에서 내가 이해 한 바에 따르면이 “분석 모델”은 소프트웨어 구현을 고려하지 않고도 설계 될 수 있다고합니다. 개발자가 비즈니스 측면에서 이해 한 모델을 구현하려고 시도하면 필요로 인해 자체 추상화를 형성하여 의사 소통과 이해에 벽을 가져옵니다.

다른 한편으로, 도메인 모델에 너무 많은 기술적 인 관심사를 도입 한 개발자는이 차이도 발생할 수 있습니다.

따라서 지속성과 같은 우려 사항을 분리하면 분석 모델이 발산되는 이러한 설계로부터 보호하는 데 도움이 될 수 있습니다. 모델에 퍼시스턴스 (persistence)와 같은 것들을 도입 할 필요가 있다고 느낀다면 그것은 붉은 깃발입니다. 모델이 구현에 실용적이지 않을 수 있습니다.

인용 :

“단일 모델은 설계가 이제 신중하게 고려 된 모델의 직접적인 결과물이기 때문에 오류 발생 가능성을 줄입니다. 설계 및 코드 자체는 모델의 통신 성을 갖습니다.”

내가 이것을 해석하는 방식으로, 데이터베이스 액세스와 같은 것들을 다루는 더 많은 코드 줄로 끝내면 그 의사 소통을 잃게됩니다.

데이터베이스에 액세스해야 할 필요성이 고유성을 확인하는 것과 같은 것이라면 다음을 살펴보십시오.

Udi Dahan : DDD를 적용 할 때 팀이 저지르는 가장 큰 실수

http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/

“모든 규칙이 동일하게 생성되지 않았습니다”에서

도메인 모델 패턴 사용

http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119

동일한 주제를 다루는 “도메인 모델을 사용하지 않는 시나리오”에서

데이터 액세스를 분리하는 방법

인터페이스를 통한 데이터 로딩

“데이터 액세스 계층”은 필요한 데이터를 검색하기 위해 호출하는 인터페이스를 통해 추상화되었습니다.

var orderLines = OrderRepository.GetOrderLines(orderId);

foreach (var line in orderLines)
{
     total += line.Price;
}

장점 :이 인터페이스는 “데이터 액세스”배관 코드를 분리하여 테스트를 작성할 수 있습니다. 데이터 액세스는 사례별로 처리 할 수있어 일반적인 전략보다 더 나은 성능을 제공합니다.

단점 : 호출 코드는로드 된 것과로드되지 않은 것을 가정해야합니다.

GetOrderLines가 성능상의 이유로 null ProductInfo 속성을 가진 OrderLine 객체를 반환한다고 가정 해보십시오. 개발자는 인터페이스 뒤의 코드에 대해 잘 알고 있어야합니다.

실제 시스템 에서이 방법을 시도했습니다. 결국 성능 문제를 해결하기 위해로드되는 내용의 범위를 변경하게됩니다. 인터페이스 뒤에 숨어 데이터로드 코드를 살펴보고로드되거나로드되지 않는 것을 확인합니다.

이제 관심사를 분리하면 개발자는 코드의 한 측면에 최대한 집중할 수 있습니다. 인터페이스 기술은이 데이터가로드되는 방법을 제거하지만로드되는 시점과로드 시점에 얼마나 많은 데이터가로드되는지를 제거합니다.

결론 : 상당히 낮은 분리!

게으른 로딩

요청시 데이터가로드됩니다. 데이터를로드하기위한 호출은 객체 그래프 자체에 숨겨져 있으며, 속성에 액세스하면 결과를 반환하기 전에 SQL 쿼리가 실행될 수 있습니다.

foreach (var line in order.OrderLines)
{
    total += line.Price;
}

장점 : 데이터 액세스의 ‘WHEN, WHERE 및 HOW’는 도메인 논리에 중점을 둔 개발자에게 숨겨져 있습니다. 데이터로드를 처리하는 집계에는 코드가 없습니다. 로드되는 데이터의 양은 코드에 필요한 정확한 양일 수 있습니다.

단점 : 성능 문제가 발생했을 때 일반적인 “한 크기에 맞는”솔루션이 있으면 해결하기가 어렵습니다. 지연 로딩은 전체적으로 성능을 저하시킬 수 있으며 지연 로딩을 구현하는 것은 까다로울 수 있습니다.

역할 인터페이스 / 열쇠 가져 오기

각 사용 사례는 집계 클래스에 의해 구현 된 역할 인터페이스 를 통해 명시 적으로 작성 되므로 사용 사례별로 데이터로드 전략을 처리 할 수 ​​있습니다.

페칭 전략은 다음과 같습니다.

public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
    Order Load(string aggregateId)
    {
        var order = new Order();

        order.Data = GetOrderLinesWithPrice(aggregateId);

        return order;
    }

}

그런 다음 집계는 다음과 같습니다.

public class Order : IBillOrder
{
    void BillOrder(BillOrderCommand command)
    {
        foreach (var line in this.Data.OrderLines)
        {
            total += line.Price;
        }

        etc...
    }
}

BillOrderFetchingStrategy는 집계를 빌드하는 데 사용되며 집계는 작업을 수행합니다.

장점 : 사용 사례별로 사용자 지정 코드를 허용하여 최적의 성능을 제공합니다. 인터페이스 분리 원리 와 일치합니다 . 복잡한 코드 요구 사항이 없습니다. 집계 단위 테스트는로드 전략을 모방하지 않아도됩니다. 일반 로딩 전략은 대부분의 경우에 사용될 수 있으며 (예 : “모두로드”전략) 필요한 경우 특수 로딩 전략을 구현할 수 있습니다.

단점 : 개발자는 도메인 코드를 변경 한 후에도 페칭 전략을 조정 / 검토해야합니다.

페칭 전략 접근 방식을 사용하면 비즈니스 규칙 변경을 위해 사용자 정의 페칭 코드를 변경하는 것을 여전히 알 수 있습니다. 문제를 완벽하게 분리하지는 않지만 유지 관리가 쉬우 며 첫 번째 옵션보다 낫습니다. 페치 전략은 HOW, WHEN 및 WHERE 데이터가로드되는 것을 캡슐화합니다. 하나의 크기가 모든 게으른 로딩 방식에 맞는 것처럼 유연성을 잃지 않으면 서 더 나은 우려 분리 기능을 제공합니다.


답변

이 블로그에서 엔티티 내부의 저장소를 캡슐화하는 것에 대해 꽤 좋은 주장이 있음을 발견했습니다.

http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities


답변

정말 훌륭한 질문입니다. 나는 같은 발견 경로에 있으며 인터넷을 통한 대부분의 답변은 솔루션을 가져 오는 것만 큼 많은 문제를 일으키는 것으로 보입니다.

그래서 (지금부터 1 년 동안 동의하지 않는 것을 쓸 위험이 있음) 여기까지의 발견이 있습니다.

우선, 우리는 풍부한 도메인 모델을 좋아합니다. 이 모델 은 높은 검색 가능성 (집계로 수행 할 수있는 작업)과 가독성 (표현식 메소드 호출)을 제공합니다.

// Entity
public class Invoice
{
    ...
    public void SetStatus(StatusCode statusCode, DateTime dateTime) { ... }
    public void CreateCreditNote(decimal amount) { ... }
    ...
}

우리는 다음과 같은 이유로 엔티티의 생성자에 서비스를 주입하지 않고이를 달성하려고합니다.

  • (새로운 서비스를 사용하는) 새로운 행동을 도입하면 생성자가 변경 될 수 있습니다. 즉, 변경은 엔티티를 인스턴스화하는 모든 행에 영향을 미칩니다 !
  • 이러한 서비스는 모델의 일부가 아니지만 생성자 주입은 해당 서비스를 제안합니다.
  • 서비스 (인터페이스조차도)는 종종 도메인의 일부가 아닌 구현 세부 사항입니다. 도메인 모델은 바깥 쪽을 향한 종속성을 갖습니다 .
  • 이러한 의존성이 없으면 엔티티가 존재할 수없는 이유 가 혼란 스러울 수 있습니다 . (신용 메모 서비스라고합니까? 나는 신용 메모로는 아무 것도하지 않을 것입니다 …)
  • 인스턴스화를 어렵게하여 테스트 하기가 어렵습니다 .
  • 이 엔티티를 포함하는 다른 엔티티는 동일한 종속성을 갖기 때문에 문제가 쉽게 퍼집니다. 이는 매우 부 자연스러운 종속성 처럼 보일 수 있습니다 .

그러면 어떻게 할 수 있습니까? 지금까지의 결론은 메소드 종속성이중 디스패치 가 적절한 솔루션을 제공한다는 것입니다.

public class Invoice
{
    ...

    // Simple method injection
    public void SetStatus(IInvoiceLogger logger, StatusCode statusCode, DateTime dateTime)
    { ... }

    // Double dispatch
    public void CreateCreditNote(ICreditNoteService creditNoteService, decimal amount)
    {
        creditNoteService.CreateCreditNote(this, amount);
    }

    ...
}

CreateCreditNote()이제 신용 메모 작성을 담당하는 서비스가 필요합니다. 엔티티 에서 검색 가능성유지 하면서 이중 디스패치를 사용 하여 작업 을 담당 서비스로 완전히 오프로드합니다 .Invoice

SetStatus()이제 로거에 대한 간단한 종속성 이 있으며 이는 분명히 작업의 일부 수행 합니다 .

후자의 경우 클라이언트 코드에서 작업을 더 쉽게하기 위해 대신로 로그인 할 수 있습니다 IInvoiceService. 결국, 인보이스 로깅은 인보이스에 본질적인 것처럼 보입니다. 이러한 단일 기능 IInvoiceService은 다양한 작업을위한 모든 종류의 미니 서비스가 필요하지 않습니다. 단점은이 서비스가 정확하게하는 것이 무엇인지 모호하게한다는 것입니다 수행 . 이중 디스패치처럼 보일 수도 있지만 대부분의 작업은 실제로 SetStatus()자체적으로 수행 됩니다.

의도를 밝히기 위해 매개 변수 이름을 ‘logger’로 지정할 수 있습니다. 그래도 약간 약한 것 같습니다.

대신 IInvoiceLogger(코드 샘플에서 이미했던 것처럼) 을 요청 IInvoiceService하고 해당 인터페이스를 구현하기로 결정했습니다. 클라이언트 코드는 그 와 같이 매우 특정한 송장 고유의 ‘미니 서비스’를 요청 IInvoiceService하는 모든 Invoice메소드에 단일 코드를 사용할 수 있으며, 메소드 서명은 여전히 ​​원하는 것을 명확하게 보여줍니다.

리포지토리를 정확하게 처리하지 않은 것으로 나타났습니다. 로거는 저장소이거나 저장소를 사용하지만보다 명확한 예를 제공하겠습니다. 저장소가 한두 가지 방법으로 필요한 경우 동일한 접근 방식을 사용할 수 있습니다.

public class Invoice
{
    public IEnumerable<CreditNote> GetCreditNotes(ICreditNoteRepository repository)
    { ... }
}

실제로, 이것은 항상 문제가되는 게으른 부하에 대한 대안을 제공 합니다 .

업데이트 : 역사적인 목적으로 아래 텍스트를 남겼지 만 게으른 하중을 100 % 제거하는 것이 좋습니다.

사실, 재산 기반의 게으른로드, 난 않는 현재 생성자 주입을 사용하지만 지속성-무식한 방법이다.

public class Invoice
{
    // Lazy could use an interface (for contravariance if nothing else), but I digress
    public Lazy<IEnumerable<CreditNote>> CreditNotes { get; }

    // Give me something that will provide my credit notes
    public Invoice(Func<Invoice, IEnumerable<CreditNote>> lazyCreditNotes)
    {
        this.CreditNotes = new Lazy<IEnumerable<CreditNotes>>() => lazyCreditNotes(this));
    }
}

한편으로 Invoice데이터베이스에서 를로드하는 저장소 는 해당 신용 메모를로드하고 해당 함수를에 삽입하는 함수에 자유롭게 액세스 할 수 있습니다 Invoice.

반면에 실제 코드를 작성하는 코드 Invoice는 빈 목록을 리턴하는 함수 만 전달합니다.

new Invoice(inv => new List<CreditNote>() as IEnumerable<CreditNote>)

(관습 ILazy<out T>은 우리에게 추한 캐스트를 제거 할 수는 IEnumerable있지만 토론을 복잡하게 만듭니다.)

// Or just an empty IEnumerable
new Invoice(inv => IEnumerable.Empty<CreditNote>())

귀하의 의견, 선호 사항 및 개선 사항을 기꺼이 듣고 싶습니다!


답변

나에게 이것은 DDD에만 국한되지 않고 일반적인 OOD 관련 관행 인 것처럼 보입니다.

내가 생각할 수있는 이유는 다음과 같습니다.

  • 우려 분리 (사용 시나리오에 따라 동일한 엔티티가 유지되는 여러 전략이있을 수 있으므로 엔티티는 유지되는 방식과 분리되어야 함)
  • 논리적으로 저장소는 운영 수준보다 낮은 수준에서 엔티티를 볼 수 있습니다. 하위 레벨 구성 요소는 상위 레벨 구성 요소에 대한 지식이 없어야합니다. 따라서 항목에는 저장소에 대한 지식이 없어야합니다.