리치 도메인 모델 — 행동이 정확히 어떻게 적용됩니까?


84

Rich vs. Anemic 도메인 모델에 대한 토론에서 인터넷은 철학적 조언으로 가득하지만 권위있는 예는 부족합니다. 이 질문의 목적은 적절한 도메인 기반 디자인 모델의 결정적인 지침과 구체적인 예를 찾는 것입니다. (이상적으로 C #에서)

실제 예에서이 DDD 구현은 잘못된 것 같습니다.

아래의 WorkItem 도메인 모델은 Entity Framework에서 코드 우선 데이터베이스에 사용하는 속성 모음 일뿐입니다. 파울러 당, 그것은 빈혈 입니다.

WorkItemService 계층은 도메인 서비스에 대한 일반적인 오해입니다. WorkItem에 대한 모든 동작 / 비즈니스 로직을 포함합니다. Yemelyanov와 다른 사람들에게 그것은 절차 적 입니다. (6 페이지)

아래 내용이 잘못된 경우 어떻게해야합니까?
동작, 즉 AddStatusUpdate 또는 Checkout 은 WorkItem 클래스에 속해야합니까?
WorkItem 모델에는 어떤 종속성이 있어야합니까?

여기에 이미지 설명을 입력하십시오

public class WorkItemService : IWorkItemService {
    private IUnitOfWorkFactory _unitOfWorkFactory;

    //using Unity for dependency injection
    public WorkItemService(IUnitOfWorkFactory unitOfWorkFactory) {
        _unitOfWorkFactory = unitOfWorkFactory;
    }

    public void AddStatusUpdate(int workItemId, int statusId) {

        using (var unitOfWork = _unitOfWorkFactory.GetUnitOfWork<IWorkItemUnitOfWork>()) {
            var workItemRepo = unitOfWork.WorkItemRepository;
            var workItemStatusRepo = unitOfWork.WorkItemStatusRepository;

            var workItem = workItemRepo.Read(wi => wi.Id == workItemId).FirstOrDefault();
            if (workItem == null)
                throw new ArgumentException(string.Format(@"The provided WorkItem Id '{0}' is not recognized", workItemId), "workItemId");

            var status = workItemStatusRepo.Read(s => s.Id == statusId).FirstOrDefault();
            if (status == null)
                throw new ArgumentException(string.Format(@"The provided Status Id '{0}' is not recognized", statusId), "statusId");

            workItem.StatusHistory.Add(status);

            workItemRepo.Update(workItem);
            unitOfWork.Save();
        }
    }
}

(이 예제는 더 읽기 쉽도록 단순화되었습니다. 코드는 혼란 스러울 수 있기 때문에 여전히 복잡합니다. 그러나 도메인 동작은 다음과 같습니다. 새로운 상태를 아카이브 기록에 추가하여 상태 업데이트. 궁극적으로 다른 답변에 동의합니다. CRUD로 처리 할 수 ​​있습니다.)

최신 정보

@AlexeyZimarev가 Jimmy Bogard의 C # 주제에 대한 완벽한 비디오 인 최고의 답변을 주었지만 링크를 넘어 충분한 정보를 제공하지 못했기 때문에 아래의 주석으로 이동 한 것 같습니다. 아래 답변에서 비디오를 요약하는 메모에 대한 초안이 있습니다. 수정 사항에 대한 답변에 자유롭게 의견을 보내주십시오. 비디오는 한 시간이지만 시청할 가치가 있습니다.

업데이트-2 년 후

DDD의 초기 성숙도의 징후라고 생각합니다. 2 년 동안 공부 한 후에도 "올바른 방법"을 알고 있다고 약속 할 수는 없습니다. 유비쿼터스 언어, 종합적인 뿌리 및 행동 중심 디자인에 대한 접근 방식은 DDD가 업계에 기여하는 중요한 요소입니다. 지속 무지와 이벤트 소싱은 혼란을 야기하며, 이와 같은 철학은 더 넓은 채택에서 벗어나는 것으로 생각합니다. 그러나 내가 배운 내용 으로이 코드를 다시 수행해야한다면 다음과 같이 보일 것입니다.

여기에 이미지 설명을 입력하십시오

유효한 도메인 모델에 대한 모범 사례 코드를 제공하는이 (매우 활발한) 게시물에 대한 답변을 여전히 환영합니다.


6
당신이 그들에게 말할 때 모든 철학적 이론은 땅에 바로 떨어 "I don't want to duplicate all my entities into DTOs simply because I don't need it and it violates DRY, and I also don't want my client application to take a dependency on EntityFramework.dll"집니다. 엔티티 프레임 워크 전문 용어 "엔티티" "도메인 모델"에서와 같이 "엔티티"와 동일하지 않습니다
페데리코 Berasategui

Automapper와 같은 자동화 도구를 사용하여 도메인 엔터티를 DTO로 복제해도 괜찮습니다. 나는 그것이 하루가 끝날 때 어떻게 보일지 잘 모르겠습니다.
RJB

16
Vimeo 에서 Jimmy Bogard의 NDC 2012 세션 "악한 도메인 모델 제작"을 시청하는 것이 좋습니다 . 그는 엔티티에서 행동을 취함으로써 리치 도메인이 무엇인지, 그리고 실제로 구현하는 방법을 설명합니다. C #에서는 예제가 매우 실용적입니다.
Alexey Zimarev

고마워요, 나는 비디오의 중간 쯤에 있습니다. 그리고 이것은 지금까지 완벽합니다. 이것이 잘못 되었다면 어딘가에 "올바른"대답이 있어야한다는 것을 알았습니다 ....
RJB

2
나도 자바에 대한 사랑을 요구한다 : /
uylmz

답변:


59

가장 도움이되는 답변은 Alexey Zimarev가 제공했으며 중재자가 원래 질문 아래의 주석으로 옮기기 전에 최소 7 개의 투표를했습니다 ....

그의 대답 :

Vimeo에서 Jimmy Bogard의 NDC 2012 세션 "악한 도메인 모델 제작"을 시청하는 것이 좋습니다. 그는 엔티티에서 행동을 취함으로써 리치 도메인이 무엇인지, 그리고 실제로 구현하는 방법을 설명합니다. C #에서는 예제가 매우 실용적입니다.

http://vimeo.com/43598193

팀의 이익을 위해 비디오를 요약하고이 게시물에서 좀 더 즉각적인 세부 정보를 제공하기 위해 메모를했습니다. (동영상은 1 시간 길이이지만 시간이 있으면 1 분마다 가치가 있습니다. Jimmy Bogard는 그의 설명에 대해 많은 크레딧을받을 가치가 있습니다.)

  • "대부분의 응용 분야에서 우리는 시작할 때 복잡해질 것이라는 것을 알지 못합니다. 그들은 그렇게됩니다."
    • 코드와 요구 사항이 추가됨에 따라 복잡성이 자연스럽게 증가합니다. CRUD와 같이 응용 프로그램은 매우 간단하게 시작할 수 있지만 동작 / 규칙이 시작될 수 있습니다.
    • "좋은 점은 복잡하게 시작할 필요가 없다는 것입니다. 빈곤 도메인 모델, 즉 속성 백으로 시작할 수 있으며 표준 리팩토링 기술만으로도 진정한 도메인 모델로 이동할 수 있습니다."
  • 도메인 모델 = 비즈니스 객체. 도메인 행동 = 비즈니스 규칙.
  • 동작은 종종 응용 프로그램에 숨겨져 있습니다. PageLoad, Button1_Click 또는 종종 'FooManager'또는 'FooService'와 같은 도우미 클래스에있을 수 있습니다.
  • 도메인 개체와 분리 된 비즈니스 규칙은 이러한 규칙을 "기억해야합니다".
    • 위의 개인 예에서 하나의 비즈니스 규칙은 WorkItem.StatusHistory.Add ()입니다. 상태를 변경하는 것이 아니라 감사를 위해 보관하는 것입니다.
  • 도메인 비헤이비어는 "단지 테스트를 작성하는 것보다 응용 프로그램의 버그를 훨씬 쉽게 제거합니다." 테스트를 위해서는 해당 테스트를 작성해야합니다. 도메인 비헤이비어는 테스트 할 올바른 경로 를 제공합니다 .
  • 도메인 서비스는 "다른 도메인 모델 엔터티 간의 활동을 조정하는 도우미 클래스입니다."
    • 도메인 서비스! = 도메인 동작. 엔터티에는 동작이 있으며 도메인 서비스는 엔터티 간 중개자 일뿐입니다.
  • 도메인 개체는 필요한 인프라 (예 : IOfferCalculatorService)를 소유하지 않아야합니다. 인프라 서비스는이를 사용하는 도메인 모델로 전달되어야합니다.
  • 도메인 모델은 그들이 할 수있는 일을 알려 주어야하며 그러한 일만 할 수 있어야합니다.
  • 도메인 모델의 속성은 프라이빗 세터로 보호해야하므로 모델 만 자체 동작을 통해 자체 속성을 설정할 수 있습니다 . 그렇지 않으면 "무차별 적"입니다.
  • ORM의 속성 백인 빈혈 도메인 모델 개체는 "얇은 한척-데이터베이스에 대한 강력한 형식의 버전"일뿐입니다.
    • "하지만 데이터베이스 행을 객체로 가져 오는 것이 쉽지만 그것이 바로 우리가 가진 것입니다."
    • '가장 지속되는 객체 모델은 바로 그 것입니다. 빈혈 도메인 모델과 실제로 동작이없는 응용 프로그램을 구별하는 것은 개체에 비즈니스 규칙이 있지만 도메인 모델에서 해당 규칙을 찾을 수없는 경우입니다. '
  • "많은 응용 프로그램의 경우 실제 비즈니스 응용 프로그램 논리 계층을 구축 할 필요가 없습니다. 데이터베이스와 통신 할 수있는 데이터 일 뿐이며 데이터를 쉽게 표현할 수있는 방법 일뿐입니다."
    • 다시 말해서, 당신이하고있는 모든 것이 특별한 비즈니스 객체 나 행동 규칙이없는 CRUD라면, DDD가 필요하지 않습니다.

포함되어야한다고 생각되거나이 메모 중 하나라도 표시가 없다고 생각되면 언제든지 의견을 말하십시오. 가능한 한 직접적으로 또는 문구를 인용하려고했습니다.


특히 도구에서 리팩토링이 어떻게 작동하는지보기위한 훌륭한 비디오. 일관성을 유지하기 위해 도메인 객체를 올바르게 캡슐화하는 것에 관한 것입니다. 그는 오퍼, 멤버 등에 대한 비즈니스 규칙을 알려주는 훌륭한 일을합니다. 그는 계약 기반 도메인 모델링 인 두 번 변하지 않는 단어를 언급합니다. .net 코드가 공식적인 비즈니스 규칙이 무엇인지 더 잘 전달하기를 바랍니다. 변경 사항이 있으므로이를 유지해야합니다.
Fuhrmanator

6

예가 잘못되어 질문에 대답 할 수 없습니다. 특히, 행동이 없기 때문입니다. 도메인의 영역에 있지 않아야합니다. AddStatusUpdate방법 의 예는 도메인 논리가 아니라 해당 도메인을 사용하는 논리입니다. 이러한 종류의 논리는 외부 요청을 처리하는 일종의 서비스 내부에있는 것이 합리적입니다.

예를 들어, 특정 작업 항목이 특정 상태 만 가질 수 있거나 N 개의 상태 만 가질 수 있어야하는 요구 사항이있는 경우, 이는 도메인 논리이며 방법 중 하나 WorkItem또는 StatusHistory방법의 일부 여야합니다 .

혼란의 이유는 필요하지 않은 코드에 지침을 적용하려고하기 때문입니다. 도메인 모델은 복잡한 도메인 로직이 많은 경우에만 관련이 있습니다. 예 : 엔티티 자체에서 작동하며 요구 사항에서 비롯된 논리입니다. 코드가 외부 데이터에서 엔티티를 조작하는 것에 관한 것이라면 아마도 도메인 논리가 아닙니다. 그러나 if작업하는 데이터와 엔티티에 따라 많은 것을 얻는 순간 도메인 논리입니다.

실제 도메인 모델링의 문제점 중 하나는 복잡한 요구 사항을 관리한다는 점입니다. 따라서 간단한 코드로는 그 장점과 장점을 보여줄 수 없습니다. 진정한 이점을 보려면 수많은 요구 사항이있는 수십 개의 엔터티가 필요합니다. 다시 말하지만, 도메인 모델이 실제로 빛을 발하기에는 귀하의 예가 너무 단순합니다.

마지막으로, 언급 할 OT는 실제 OOP 디자인을 가진 진정한 도메인 모델이 Entity Framework를 사용하여 유지하기가 실제로 어렵다는 것입니다. ORM은 실제 OOP 구조를 관계형 구조에 매핑하도록 설계되었지만 여전히 많은 문제가 있으며 관계형 모델은 종종 OOP 모델로 누출 될 수 있습니다. EF보다 훨씬 강력한 nHibernate를 사용하더라도 문제가 될 수 있습니다.


좋은 지적입니다. 그러면 Data 또는 Infrastrcture의 다른 프로젝트에서 AddStatusUpdate 메소드가 어디에 속합니까? 이론적으로 WorkItem에 속할 수있는 동작의 예는 무엇입니까? 모든 유사 코드 또는 모형은 크게 감사하겠습니다. 내 예제는 실제로 더 읽기 쉽도록 단순화되었습니다. 다른 엔터티가 있으며 예를 들어 AddStatusUpdate에는 추가 동작이 있습니다. 실제로 상태 범주 ​​이름이 필요하며 해당 범주가 존재하지 않으면 범주가 만들어집니다.
RJB

@RJB 앞서 말했듯이 AddStatusUpdate는 도메인을 사용하는 코드입니다. 따라서 도메인 클래스를 사용하는 일종의 웹 서비스 또는 응용 프로그램입니다. 그리고 내가 말했듯이 OOP 도메인 모델의 실제 이점을 보여주기 위해 충분히 복잡한 전체 프로젝트를 만들어야하기 때문에 어떤 종류의 모형 또는 의사 코드도 기대할 수 없습니다.
Euphoric

5

WorkItem과 관련된 비즈니스 로직을 "뚱뚱한 서비스"로 캡슐화한다는 가정은 필자가 주장 할 수있는 고유 한 안티 패턴 일 필요는 없습니다.

빈혈 도메인 모델에 대한 귀하의 생각과 상관없이, LOB (Business of Business) .NET 애플리케이션의 전형적인 표준 패턴 및 사례는 다양한 구성 요소로 구성된 트랜잭션 계층 접근법을 권장합니다. 또한 비즈니스 로직을 도메인 모델과 분리하여 다른 .NET 구성 요소뿐만 아니라 다른 기술 스택 또는 물리적 계층의 구성 요소에서 공통 도메인 모델의 통신을 용이하게합니다.

간단한 데이터 형식이 포함 된 DLL이있는 Silverlight 클라이언트 응용 프로그램과 통신하는 .NET 기반 SOAP 웹 서비스가 이에 해당합니다. 이 도메인 엔터티 프로젝트는 .NET 어셈블리 또는 Silverlight 어셈블리에 빌드 될 수 있습니다.이 DLL이있는 관심있는 Silverlight 구성 요소는 서비스에서만 사용할 수있는 구성 요소에 종속 된 개체 동작에 노출되지 않습니다.

이 토론에 대한 귀하의 입장에 관계없이, 이것은 Microsoft에 의해 채택되고 수용되는 패턴이며, 제 전문가의 견해로는 잘못된 접근법이 아니지만 자체 행동을 정의하는 객체 모델도 반 패턴이 아닙니다. 이 설계를 진행하는 경우 도메인 모델을 확인해야하는 다른 구성 요소와 통합해야 할 경우 발생할 수있는 제한 사항과 문제점을 이해하고 이해하는 것이 가장 좋습니다. 이 경우 Translator가 객체 지향 스타일 도메인 모델을 특정 동작 방법을 노출시키지 않는 간단한 데이터 객체로 변환하도록 할 수 있습니다.


1
1) 비즈니스 로직을 도메인 모델과 어떻게 분리 할 수 ​​있습니까? 이 비즈니스 로직이 존재하는 영역입니다. 해당 도메인의 엔티티가 해당 비즈니스 로직과 연관된 동작을 실행 중입니다. 실제 세계에는 서비스가 없으며 도메인 전문가의 책임자도 없습니다. 2)의 요구가 있기 때문에, 당신은 자신의 도메인 모델을 구축 할 필요가 통합하고자하는 모든 구성 요소 것이다 다르고이 됩니다 도메인 모델에 대한 다른 견해를 가지고있다. 공유 할 수있는 하나의 도메인 모델을 만들 수 있다는 것은 오랜 기간 동안의 약점입니다.
Stefan Billiet

1
@StefanBilliet 이것이 범용 도메인 모델의 오류에 대한 좋은 점이지만 이전에 수행 한 것처럼 더 간단한 구성 요소 및 구성 요소 상호 작용에서도 가능합니다. 내 의견은 도메인 모델 간의 변환 논리가 많은 지루하고 상용구 코드를 만들 수 있으며 안전하게 피할 수 있다면 좋은 디자인 선택이 될 수 있다는 것입니다.
maple_shaft

1
솔직히 말해서 유일하게 좋은 디자인 선택은 비즈니스 전문가가 추론 할 수있는 모델이라고 생각합니다. 비즈니스가 해당 도메인 내에서 특정 문제를 해결하는 데 사용할 수 있도록 도메인 모델을 구축하고 있습니다. 도메인 엔터티에서 서비스로 동작을 분리하면 도메인 전문가가 말한 내용을 현재 대화와 거의 비슷하지 않은 서비스 코드에 지속적으로 매핑해야하기 때문에 모든 관련자가 어려움을 겪습니다. 내 경험상 상용구를 입력하는 것보다 시간이 많이 걸립니다. 보일러 코드 강좌를 둘러싼 방법이 없다고 말하는 것은 아닙니다.
Stefan Billiet

@StefanBilliet 완벽한 세상에서 나는 비즈니스 전문가가 개발자와 함께 할 시간이있는 곳에 동의합니다. 소프트웨어 산업의 현실은 비즈니스 전문가가이 수준에 참여하는 데 시간이나 관심이 없거나 더 나쁘지만 개발자는 모호한 안내만으로이를 파악할 수 있다는 것입니다.
maple_shaft

사실이지만 그 현실을 받아 들일 이유는 아닙니다. 그러한 추구를 계속하려면 개발자의 시간과 명성을 낭비하고 고객의 돈을 낭비하는 것입니다. 내가 설명한 프로세스는 시간이 지남에 따라 구축되어야하는 관계입니다. 많은 노력이 필요하지만 훨씬 더 나은 결과를 얻을 수 있습니다. "유비쿼터스 언어"가 종종 DDD의 가장 중요한 측면으로 간주되는 이유가 있습니다.
Stefan Billiet

5

나는이 질문이 아주 오래되었다는 것을 알고이 대답은 후손을위한 것입니다. 이론에 근거한 것이 아니라 구체적인 예를 들어 대답하고 싶습니다.

다음 WorkItem과 같이 클래스에서 "작업 항목 상태 변경"을 캡슐화하십시오 .

public SomeStatusUpdateType Status { get; private set; }

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Maybe we designed this badly at first ;-)
    Status = status;       
}

이제 여러분의 WorkItem수업은 합법적 인 상태를 유지할 책임이 있습니다. 그러나 구현은 매우 약합니다. 제품 소유자가에 대한 모든 상태 업데이트 기록을 원합니다 WorkItem.

우리는 이것을 다음과 같이 변경합니다 :

private ICollection<SomeStatusUpdateType> StatusUpdates { get; private set; }
public SomeStatusUpdateType Status => StatusUpdates.OrderByDescending(s => s.CreatedOn).FirstOrDefault();

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Better...
    StatusUpdates.Add(status);       
}

구현이 크게 변경되었지만 ChangeStatus메소드 의 호출자 는 기본 구현 세부 사항을 인식하지 못하며 변경할 이유가 없습니다.

리치 도메인 모델 엔터티 IMHO의 예입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.