DDD Aggregates는 웹 애플리케이션에서 실제로 좋은 아이디어입니까?


40

Domain Driven Design에 뛰어 들었습니다. 그리고 제가 접하게 될 일부 개념은 표면상에서 많은 의미를 갖지만, 더 많이 생각할 때 그것이 정말로 좋은 아이디어인지 궁금합니다.

예를 들어 집계의 개념은 의미가 있습니다. 전체 도메인 모델을 다룰 필요가 없도록 작은 소유 도메인을 만듭니다.

그러나 웹 응용 프로그램의 맥락에서 이것에 대해 생각할 때 작은 데이터 하위 집합을 가져 오기 위해 데이터베이스에 자주 충돌합니다. 예를 들어, 페이지에는 주문 수만 나열 할 수 있으며, 주문을 열기 위해 주문을 클릭하고 주문 ID를 볼 수있는 링크가 있습니다.

내가 잘 이해 집계를 해요, 난 일반적으로 구성원을 포함하는 것 인 OrderAggregate 돌아 저장소 패턴을 사용 GetAll, GetByID, Delete,와 Save. 좋아, 잘 들린다. 그러나...

GetAll을 호출하여 모든 주문을 나열하면이 패턴에 전체 집계 정보 목록, 전체 주문, 주문 라인 등을 반환해야하는 것처럼 보입니다. 해당 정보의 작은 하위 집합 만 필요한 경우 (헤더 정보 만).

뭔가 빠졌습니까? 아니면 여기서 사용할 최적화 수준이 있습니까? 나는 당신이 필요로하지 않을 때 전체 정보를 반환하는 것을 옹호한다고 상상할 수 없습니다.

확실히, 저장소와 같은 메소드를 생성 할 수는 GetOrderHeaders있지만 처음에는 저장소와 같은 패턴을 사용하는 목적을 무효화하는 것처럼 보입니다.

누구든지 나를 위해 이것을 명확히 할 수 있습니까?

편집하다:

더 많은 연구를 한 후, 여기서의 단절은 순수한 리포지토리 패턴이 대부분의 사람들이 리포지토리를 생각하는 것과 다르다는 것입니다.

Fowler는 저장소를 컬렉션 의미론을 사용하는 데이터 저장소로 정의하며 일반적으로 인 메모리로 유지됩니다. 이것은 전체 객체 그래프를 만드는 것을 의미합니다.

Evans는 리포지토리에서 Aggregate Roots를 포함하도록 변경하므로 리포지토리는 Aggregate의 개체 만 지원하도록 절단됩니다.

대부분의 사람들은 리포지토리를 영광스러운 데이터 액세스 개체로 생각하고 원하는 데이터를 얻을 수있는 메서드를 만들 수 있습니다. Fowler 's Patterns of Enterprise Application Architecture에 설명 된 의도는 아닌 것 같습니다.

또 다른 사람들은 저장소를 테스트 및 조롱을보다 쉽게하거나 지속성을 시스템의 나머지 부분에서 분리하기 위해 주로 사용되는 단순한 추상화로 생각합니다.

답은 이것이 처음 생각했던 것보다 훨씬 더 복잡한 개념이라는 것입니다.


4
"정답은 이것이 처음 생각했던 것보다 훨씬 더 복잡한 개념이라는 것입니다." 이것은 매우 사실입니다.
quentin-starin

상황에 따라 요청 될 때만 데이터를 선택적으로 검색하고 캐시하는 집계 루트 오브젝트에 대한 프록시를 작성할 수 있습니다.
Steven A. Lowe

내 제안은 루트 집계 협회에서 지연로드를 구현하는 것입니다. 따라서 너무 많은 객체를로드하지 않고 루트 목록을 검색 할 수 있습니다.
Juliano

4
거의 6 년 후에도 여전히 좋은 질문입니다. 빨간 책의 장을 읽은 후에는 이렇게 말합니다. 집합체를 너무 크게 만들지 마십시오. 도메인에서 최상위 개념을 선택하고 그것을 루트로 선언하는 유혹을 느끼고 DDD를 제외하고는 더 작은 집계를 옹호합니다. 위에서 설명한 것과 같은 비 효율성을 완화합니다.
Cpt. Senkfuss

골재는 가능한 작고 도메인에 자연스럽고 효과적이어야합니다 (도전!). 또한, repos가 매우 구체적인 방법 을 갖는 것이 완벽하고 바람직 합니다.
Timo

답변:


30

쿼리에 도메인 모델과 집계를 사용하지 마십시오.

실제로, 당신이 요구하는 것은 그것을 피하기 위해 일련의 원칙과 패턴이 확립되었다는 일반적인 질문입니다. 이를 CQRS 라고 합니다 .


2
@Mystere Man : 아니요, 필요한 최소한의 데이터를 제공하기위한 것 입니다 . 그것은 분리 된 읽기 모델의 큰 목적 중 하나입니다. 또한 일부 동시성 문제를 사전에 해결하는 데 도움이됩니다. CQRS는 DDD에 적용될 때 몇 가지 이점이 있습니다.
quentin-starin

2
@Mystere : 내가 링크 한 기사를 읽는다면 놓치게 될 것입니다. "쿼리 (보고)"섹션을 참조하십시오. "응용 프로그램이 데이터를 요청하는 경우 ... 쿼리 계층에 대한 단일 호출로 수행해야하며, 대신 필요한 모든 데이터를 포함하는 단일 DTO를 얻게됩니다. 그 이유는 데이터가 일반적으로 도메인 동작이 실행되는 것보다 여러 번 쿼리되기 때문에이를 최적화함으로써 시스템의인지 성능을 향상시킬 수 있기 때문입니다. "
quentin-starin

4
이것이 리포지토리를 사용 하여 CQRS 시스템에서 데이터 를 읽지 않는 이유 입니다. 우리는 쿼리를 캡슐화하는 간단한 클래스를 작성했으며 (ADO.Net 또는 Linq2Sql 또는 SubSonic이 편리한 기술을 사용하는 경우가 많음) 피해야 할 작업에 필요한 데이터 만 반환합니다. DDD 리포지토리의 모든 일반 레이어를 통해 데이터를 끌어옵니다. 도메인에 명령을 보내려는 경우 리포지토리 만 사용하여 집계를 검색합니다.
quentin-starin

9
"필요하지 않을 때 전체 정보를 반환하는 것을 옹호하는 사람은 없을 것입니다." 나는 당신 이이 진술에 정확히 맞다고 말하려고합니다. 필요하지 않은 정보의 전체 집합을 검색하지 마십시오. 이것이 DDD에 적용되는 CQRS의 핵심입니다. 쿼리하기 위해 집계가 필요하지 않습니다. 다른 메커니즘을 통해 데이터를 얻은 다음 일관되게 수행하십시오.
quentin-starin

2
@qes 사실, 가장 좋은 해결책은 쿼리에 DDD를 사용하지 않는 것입니다 (읽기) :) 그러나 여전히 명령 부분에서 데이터를 저장하거나 업데이트하기 위해 DDD를 사용합니다. 그래서 당신에게 질문이 있습니다 .DB에서 데이터를 업데이트해야 할 때 항상 엔티티가있는 리포지토리를 사용합니까? 열에서 하나의 작은 값만 변경해야한다고 가정하고 (어떤 종류의 스위치) 여전히 앱 계층에 전체 엔티티를로드하고 하나의 값 (속성)을 변경 한 다음 전체 엔티티를 다시 DB에 저장합니까? 약간의 과잉도?
Andrew

8

도메인 기반 디자인에서 리포지토리 패턴을 가장 잘 사용하는 방법으로 어려움을 겪고 있으며 여전히 어려움을 겪고 있습니다. 지금 처음으로 사용한 후 다음과 같은 사례를 생각해 냈습니다.

  1. 저장소는 단순해야합니다. 도메인 개체를 저장하고 검색하는 것만 담당합니다. 다른 모든 논리는 팩토리 및 도메인 서비스와 같은 다른 개체에 있어야합니다.

  2. 저장소는 마치 집합 루트의 메모리 컬렉션에있는 것처럼 컬렉션처럼 동작합니다.

  3. 리포지토리는 일반 DAO가 아니며 각 리포지토리에는 고유하고 좁은 인터페이스가 있습니다. 저장소에는 종종 도메인 측면에서 콜렉션을 검색 할 수있는 특정 파인더 메소드가 있습니다 (예 : 사용자 X에 대한 모든 미결 주문 제공). 저장소 자체는 일반 DAO의 도움으로 구현 될 수 있습니다.

  4. 이상적으로 파인더 메소드는 집계 루트 만 리턴합니다. 비효율적 인 경우 필요한 것을 정확하게 포함하는 것보다 읽기 전용 값 객체를 반환 할 수도 있습니다 (이러한 값 객체가 도메인 측면에서 표현 될 수 있다면 플러스 임). 최후의 수단으로 리포지토리를 사용하여 집합 루트의 하위 집합 또는 하위 집합 컬렉션을 반환 할 수도 있습니다.

  5. 이와 같은 선택은 사용 된 기술에 따라 달라집니다. 사용 된 기술로 도메인 모델을 가장 효율적으로 표현하는 방법을 찾아야합니다.


확실히 복잡한 주제입니다. 이론이 두 개의 분리되고 분리 된 이론을 단일 실무로 결합 할 때 특히 이론을 실천하기가 어렵습니다.
Sebastian Patten

6

GetOrderHeaders 메서드가 리포지토리의 목적을 전혀 상실한다고 생각하지 않습니다.

DDD는 무엇보다도 집계 루트를 통해 필요한 것을 얻도록 보장하는 데 관심이 있지만 (예를 들어 OrderDetailsRepository가 없을 수도 있음) 언급하는 방식으로 제한하지는 않습니다.

OrderHeader가 도메인 개념 인 경우이를 정의하고이를 검색하기위한 적절한 저장소 메소드가 있어야합니다. 당신이 할 때 올바른 집계 루트를 겪고 있는지 확인하십시오.


어쩌면 나는 여기서 개념을 혼란스럽게하지만 저장소 패턴에 대한 이해는 지속성을위한 표준 인터페이스를 사용하여 도메인에서 지속성을 분리하는 것입니다. 특정 기능에 대한 사용자 정의 방법을 추가 해야하는 경우 다시 연결하는 것처럼 보입니다.
Erik Funkenbusch

1
지속성 메커니즘 은 도메인과 분리되어 있지만 지속되는 것은 아닙니다. "여기에 주문 헤더를 나열해야합니다"와 같은 말을하는 경우 도메인에서 OrderHeader를 모델링하고 리포지토리에서 검색 할 수있는 방법을 제공해야합니다.
에릭 킹

또한 "지속성을위한 표준 인터페이스"에 매달리지 마십시오. 가능한 모든 앱에 적합한 일반 리포지토리 패턴과 같은 것은 없습니다. 각 앱에는 표준 "GetById", "Save"등을 제외한 많은 리포지토리 메소드가 있습니다. 이러한 메소드는 엔드 포인트가 아닌 시작점입니다.
에릭 킹

4

DDD 사용은 "순수한"DDD로 간주되지 않을 수 있지만 DB 데이터 저장소에 대해 DDD를 사용하여 다음과 같은 실제 전략을 채택했습니다.

  • 집계 루트에는 연관된 저장소가 있습니다.
  • 연관된 저장소는 해당 집계 루트에서만 사용됩니다 (공개적으로 사용할 수 없음)
  • 리포지토리에는 쿼리 호출 (예 : GetAllActiveOrders, GetOrderItemsForOrder)이 포함될 수 있습니다.
  • 서비스는 저장소의 공개 서브 세트 및 기타 비 조달 된 오퍼레이션 (예 : 한 은행 계좌에서 다른 은행 계좌로 자금 이체, LoadById, 검색 / 찾기, CreateEntity 등)을 노출합니다.
  • 루트-> 서비스-> 리포지토리 스택을 사용합니다. DDD 서비스는 엔티티가 자체적으로 응답 할 수없는 것 (예 : LoadById, TransferMoneyFromAccountToAccount)에만 사용되는 것으로 가정하지만 실제로는 다른 CRUD 관련 서비스 (저장, 삭제, 쿼리)를 고수하는 경향이 있습니다. root는 스스로 "응답 / 수행"할 수 있어야합니다. 엔터티에 다른 집계 루트 서비스에 대한 액세스 권한을 부여하는 데 아무런 문제가 없습니다! 그러나 서비스 (GetOrderItemsForOrder)에는 포함하지 않지만 집계 루트가 서비스를 사용할 수 있도록이를 저장소에 포함해야합니다. 서비스는 리포지토리와 같이 열린 쿼리를 노출해서는 안됩니다.
  • 나는 보통 인터페이스를 통해 도메인 모델에서 리포지토리를 추상적으로 정의하고 별도의 구체적인 구현을 제공합니다. 사용하기 위해 구체적인 저장소에 주입하는 도메인 모델에서 서비스를 완전히 정의합니다.

** 전체 집계를 다시 가져올 필요는 없습니다. 그러나 더 많은 것을 원한다면 다른 서비스 나 저장소가 아닌 루트를 요구해야합니다. 이것은 게으른 로딩이며 수동으로 게으른 로딩이 좋지 않은 경우 (적절한 저장소 / 서비스를 루트에 주입) 또는이를 지원하는 ORM을 사용하여 수동으로 수행 할 수 있습니다.

귀하의 예에서, 세부 정보를 별도의 호출에로드하려는 경우 주문 헤더 만 가져 오는 리포지토리 호출을 제공 할 것입니다. "OrderHeader"를 가짐으로써 실제로 도메인에 추가 개념을 도입하고 있습니다.


3

도메인 모델에는 비즈니스 로직이 가장 순수한 형태로 포함되어 있습니다. 비즈니스 운영을 지원하는 모든 관계 및 운영 개념도에서 누락 된 것은 서비스 계층 이 도메인 모델을 둘러싼 응용 프로그램 서비스 계층에 대한 아이디어이며 도메인 모델을 필요에 따라 변경할 수있는 비즈니스 도메인 (원하는 경우 프로젝션)에 대한 간단한보기를 제공합니다. 서비스 계층을 사용하여 애플리케이션에 직접 영향을 미치지 않습니다.

더 나아가고 있습니다. 골재의 개념은 골재의 일관성을 유지하는 하나의 대상인 골재 루트가 있다는 것입니다. 귀하의 예에서, 주문은 주문 라인을 조작하는 책임이 있습니다.

예를 들어 서비스 계층은 GetOrdersForCustomer와 같은 작업을 노출하여 주문의 요약 목록을 보는 데 필요한 항목 만 반환합니다 (주문 헤더라고 함).

마지막으로 리포지토리 패턴은 단지 모음이 아니라 선언적 쿼리를 허용합니다. C #에서는 LINQ를 Query Object 로 사용하거나 대부분의 다른 O / RM이 Query Object 사양도 제공합니다.

리포지토리는 인 메모리 도메인 개체 컬렉션처럼 작동하는 도메인과 데이터 매핑 계층 사이를 중재합니다. 클라이언트 객체는 선언적으로 쿼리 사양을 구성하고이를 만족시키기 위해 리포지토리에 제출합니다. ( Fowler 's Repository 페이지에서 )

리포지토리에 대해 쿼리를 만들 수 있다는 점에서 일반적인 쿼리를 처리하는 편리한 방법을 제공하는 것이 좋습니다. 즉, 주문의 헤더 만 원한다면 헤더 만 반환하는 쿼리를 작성하고 리포지토리의 편리한 메소드에서 노출시킬 수 있습니다.

이것이 일을 명확히하는 데 도움이되기를 바랍니다.


0

나는 이것이 오래된 질문이라는 것을 알고 있지만 다른 대답을 한 것으로 보입니다.

리포지토리를 만들면 일반적으로 캐시 된 쿼리가 래핑 됩니다.

Fowler는 저장소를 컬렉션 의미론을 사용하는 데이터 저장소로 정의하며 일반적으로 인 메모리로 유지됩니다. 이것은 전체 객체 그래프를 만드는 것을 의미합니다.

서버의 해당 저장소를 램으로 유지하십시오. 객체를 데이터베이스로 전달하는 것이 아닙니다!

주문 목록 페이지가있는 웹 애플리케이션에있는 경우 클릭하여 세부 정보를 볼 수있는 경우 주문 목록 페이지에 주문에 대한 세부 정보 (ID, 이름, 금액, 날짜)가 표시 될 수 있습니다. 사용자가보고 싶은 것을 결정하도록 도와줍니다.

이 시점에서 두 가지 옵션이 있습니다.

  1. 데이터베이스를 쿼리하고 목록을 작성하는 데 필요한 내용을 정확하게 가져온 다음 세부 정보 페이지에서 확인해야 할 개별 세부 정보를 가져 오도록 다시 쿼리 할 수 ​​있습니다.

  2. 모든 정보를 가져 와서 캐시하는 하나의 쿼리를 만들 수 있습니다. 다음 페이지에서는 데이터베이스 대신 서버 램에서 읽습니다. 사용자가 다시 방문하거나 다음 페이지를 선택하면 여전히 데이터베이스에 대한 트립이 없습니다.

실제로 구현 방법은 그저 구현 세부 사항입니다. 가장 큰 사용자가 10 개의 주문을 가지고 있다면 아마도 옵션 2를 사용하고 싶을 것입니다. 10,000 개의 주문을 말하고 있다면 옵션 1이 필요합니다. 위의 경우와 다른 많은 경우에 저장소가 해당 구현 세부 사항을 숨기고 싶습니다.

지난 달 주문 목록 페이지 에서 주문 ( 집계 데이터 )에 지출 한 금액을 사용자에게 알려주는 티켓을 얻는다면 SQL에서 계산하고 다른 왕복 여행을하는 논리를 작성하는 것이 좋습니다. DB 또는 이미 서버 램에있는 데이터를 사용하여 계산 하시겠습니까?

내 경험상 도메인 집계는 큰 이점을 제공합니다.

  • 실제로 작동하는 큰 부품 코드 재사용입니다.
  • SQL Server가이를 수행하기 위해 인프라 계층을 뚫지 않고 코어 계층에서 비즈니스 로직을 유지함으로써 코드를 단순화합니다.
  • 또한 쉽게 캐시 할 수 있으므로 필요한 쿼리 수를 줄여 응답 시간을 대폭 단축 할 수 있습니다.
  • 내가 쓰는 SQL은 종종 모든 것을 요구하고 서버 측을 계산하기 때문에 유지 관리가 더 쉽습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.