빈혈 도메인 모델을 사용해야한다면 비즈니스 논리와 계산 된 필드는 어디에 넣습니까?

StackOverflow https://stackoverflow.com/questions/1933351

문제

현재의 O/RM 도구는 실제로 풍부한 도메인 모델을 허용하지 않으므로 모든 곳에서 빈혈 (DTO) 엔티티를 활용해야합니다. 이것은 제대로 작동했지만 기본 객체 기반 비즈니스 로직과 계산 된 필드를 어디에 두는 지 계속 고생하고 있습니다.

전류 레이어 :

  • 프레젠테이션
  • 서비스
  • 저장소
  • 데이터/엔티티

우리의 저장소 계층에는 대부분의 기본 페치/유효성 검사/저장 로직이 있지만 서비스 계층은보다 복잡한 유효성 검사 및 저장을 많이합니다 (저장 작업도 로깅, 권한 확인 등). 문제는 다음과 같이 코드를 넣을 위치입니다.

Decimal CalculateTotal(LineItemEntity li)
{
  return li.Quantity * li.Price;
}

또는

Decimal CalculateOrderTotal(OrderEntity order)
{
  Decimal orderTotal = 0;
  foreach (LineItemEntity li in order.LineItems)
  {
    orderTotal += CalculateTotal(li);
  }
  return orderTotal;
}

이견있는 사람?

도움이 되었습니까?

해결책

기본으로 돌아가자 :

서비스

서비스는 3 가지 맛으로 제공됩니다. 도메인 서비스, 응용 프로그램 서비스, 그리고 인프라 서비스

  • 도메인 서비스 : 도메인 객체에 자연스럽게 맞지 않는 비즈니스 로직을 캡슐화합니다. 당신의 경우, 모두 비즈니스 논리의.
  • 응용 프로그램 서비스 : 외부 소비자가 시스템과 대화하기 위해 사용
  • 인프라 서비스 : 기술 문제를 추상화하는 데 사용됩니다 (예 : MSMQ, 이메일 제공 업체 등)

저장소

이곳은 귀하의 데이터 액세스와입니다 일관성 검사 가다. 순수한 DDD에서, 당신의 집계 뿌리 일관성을 점검 할 책임이 있습니다 (객체를 지속하기 전에). 귀하의 경우, 귀하는 귀하의 수표를 사용합니다. 도메인 서비스 층.


제안 된 해법: 기존 서비스를 분리하십시오

새로운 것을 사용하십시오 도메인 서비스 DTO의 모든 논리를 캡슐화하는 계층과 일관성 검사도 사용합니다 (사용 명세서, 아마도?).

사용 응용 프로그램 서비스 필요한 페치 방법을 노출시키기 위해 (FetchOpenOrdersWithLines), 요청을 귀하에게 전달합니다 저장소 (제레미가 제안한대로 제네릭을 사용합니다). 사용을 고려할 수도 있습니다 쿼리 사양 쿼리를 마무리합니다.

당신의 저장소, 사용 명세서 당신의 도메인 서비스 객체를 지속하기 전에 객체 일관성 등을 확인하는 레이어.

Evans의 책에서 지원 정보를 찾을 수 있습니다.

  • "서비스 및 격리 된 도메인 계층" (PG 106)
  • "명세서" (PG 224)
  • "쿼리 사양" (PG 229)

다른 팁

나는 대답하고 싶어한다 MU, 그러나 나는 정교하고 싶다. 요약해서 말하자면: ORM의 선택이 도메인 모델을 정의하는 방법을 지시하지 마십시오.

도메인 모델의 목적은 도메인을 모델링하는 풍부한 객체 지향 API입니다. 사실을 따릅니다 도메인 구동 설계, 도메인 모델을 정의해야합니다 기술에 의해 제한되지 않았습니다.

다시 말해, 도메인 모델이 먼저 제공됩니다, 및 모든 기술 별 구현은 이후에 해결됩니다 매퍼 도메인 모델과 해당 기술 사이의지도. 여기에는 종종 두 가지 방법이 포함됩니다. ORM 선택이 제약 조건을 도입 할 수있는 데이터 액세스 계층 및 UI 기술이 추가 요구 사항을 부과하는 UI 계층에 포함됩니다.

구현이 도메인 모델과는 거리가 멀다면, 우리는 반부패 층.

귀하의 경우, 빈혈 도메인 모델이라고 부르는 것은 실제로 데이터 액세스 계층입니다. 당신의 최선의 의지는 정의하는 것입니다 저장소 이 모델은 기술 중립적 인 방식으로 엔터티에 대한 액세스입니다.

예를 들어, 주문 엔티티를 살펴 보겠습니다. 기술로 제한되지 않은 순서를 모델링하면 다음과 같은 것을 이끌어 낼 수 있습니다.

public class Order
{
    // constructors and properties

    public decimal CalculateTotal()
    {
        return (from li in this.LineItems
                select li.CalculateTotal()).Sum();
    }
}

이것은 평범한 오래된 CLR 물체 ( 포코 ) 따라서 기술에 의해 제한되지 않습니다. 이제 문제는 데이터 저장소에서 어떻게 이걸 얻고 나가는가입니다.

이것은 추상적 인 iorderrepository를 통해 수행되어야합니다.

public interface IOrderRepository
{
    Order SelectSingle(int id);

    void Insert(Order order);

    void Update(Order order);

    void Delete(int id);

    // more, specialized methods can go here if need be
}

이제 선택한 ORM을 사용하여 iorderrepository를 구현할 수 있습니다. 그러나 일부 ORM (예 : Microsoft의 엔티티 프레임 워크)은 특정 기본 클래스에서 데이터 클래스를 도출해야하므로 POCO와 같은 도메인 객체와 전혀 맞지 않습니다. 따라서 매핑이 필요합니다.

깨달아야 할 중요한 점은 데이터 클래스를 강력하게 입력했을 수 있다는 것입니다. 의미 적으로 도메인 엔티티와 비슷합니다. 그러나 이것은 순수한 구현 세부 사항이므로 혼란스러워하지 마십시오. 예를 들어에서 파생되는 주문 클래스 EntityObject 도메인 클래스가 아닙니다 - 구현 세부 사항이므로 iorderrepository를 구현할 때 주문을 매핑해야합니다. 데이터 클래스 주문에 Doman 클래스.

이것은 지루한 일일 수 있지만 사용할 수 있습니다. automapper 당신을 위해 그것을하기 위해.

Selectsingle 메소드의 구현이 다음과 같은 방법은 다음과 같습니다.

public Order SelectSinge(int id)
{
    var oe = (from o in this.objectContext.Orders
              where o.Id == id
              select o).First();
    return this.mapper.Map<OrderEntity, Order>(oe);
}

이것이 바로 서비스 계층의 것입니다. 비즈니스 리그 계층이라고하는 응용 프로그램도 보았습니다.

이것들은 대부분의 시간을 테스트하는 데 사용하려는 루틴이며, 자체 레이어에 있다면 저장소 레이어를 조롱하는 것이 간단해야합니다.

저장소 계층은 가능한 한 일반화되어야하므로 특정 클래스에 개인 인 비즈니스 논리에 적합한 장소가 아닙니다.

당신이 말한 바에 따르면, 당신은 당신의 서비스와 저장소 계층에 대해 너무 엄격하게 생각하는 것일 수 있습니다. 프리젠 테이션 레이어가 저장소 계층에 직접 의존성을 갖지 않기를 원하지 않으며이를 달성하기 위해 서비스 계층의 저장소 (귀하의 패스 스루 메소드)에서 방법을 복제하는 것 같습니다.

나는 그것을 질문 할 것이다. 당신은 그것을 긴장을 풀고 프레젠테이션 계층 내에서 둘 다 사용하고 인생을 더 간단하게 만들 수 있습니다. 그런 저장소를 숨겨서 자신이 성취 한 것을 스스로에게 물어보십시오. 당신은 이미 지속성을 추상화하고 구현을 쿼리하고 있습니다. 이것은 훌륭하고 그들이 설계된 것입니다. 마치 엔터티가 전혀 지속된다는 사실을 숨기는 서비스 계층을 만들려고하는 것처럼 보입니다. 왜 그런지 물어볼까요?

주문 총계를 계산하는 경우 서비스 계층은 자연적인 집입니다. linetotal (lineitem lineitem) 및 OrderTotal (주문) 메소드가있는 SalesOrderCalculator 클래스는 괜찮습니다. 또한 필요한 경우 구현을 전환하기 위해 Orderservices.createOrderCalculator ()를 예 : Orderservices.CreateOrderCalculator ()를 작성하는 것을 고려할 수도 있습니다 (주문 할인에 대한 세금에는 국가 별 규칙이 있습니다). 이것은 또한 주문을 주문하기위한 단일 진입 지점을 형성하고 Intellisense를 통해 쉽게 찾을 수 있습니다.

이 모든 것이 불가능하게 들릴 수 없다면 추상화가 무엇을 달성하고 있는지, 그들이 서로 관련되는 방법에 대해 더 깊이 생각해야 할 수도 있습니다. 단일 책임 원칙. 저장소는 인프라 추상화입니다 (엔티티가 저장 및 검색되는 방법을 숨기는). 서비스는 비즈니스 행동 또는 규칙의 구현을 추상화하고 버전화 또는 차이를위한 더 나은 구조를 허용합니다. 그것들은 일반적으로 당신이 묘사하는 방식으로 겹치지 않습니다. 서비스에 복잡한 보안 규칙이있는 경우 저장소가 더 나은 집이 될 수 있습니다. 일반적인 DDD 스타일 모델에서 리포지토리, 엔터티, 가치 객체 및 서비스는 모두 동일한 레이어와 동일한 모델의 일부로 서로 함께 사용됩니다. 따라서 위의 층 (일반적으로 프리젠 테이션)은 이러한 추상화에 의해 격리됩니다. 모델 내에서 한 서비스의 구현은 다른 서비스의 추상화를 사용할 수 있습니다. 추가 개선은보다 공식적인 수명주기 컨텍스트를 시행하는 엔티티 또는 가치 객체에 대한 참조를 보유한 사람에게 규칙을 추가합니다. 이에 대한 자세한 내용은 공부하는 것이 좋습니다. 에릭 에반스 책 또는 도메인 구동 디자인을 빠르게 구동합니다.

ORM 기술이 DTO 객체 만 잘 처리하는 경우 풍부한 엔티티 객체를 버려야한다는 의미는 아닙니다. 여전히 엔티티 객체로 DTO 객체를 랩핑 할 수 있습니다.

public class MonkeyData
{
   public string Name { get; set; }
   public List<Food> FavoriteFood { get; set; }
}

public interface IMonkeyRepository
{
   Monkey GetMonkey(string name) // fetches DTO, uses entity constructor
   void SaveMonkey(Monkey monkey) // uses entity GetData(), stores DTO
}


public class Monkey
{
   private readonly MonkeyData monkeyData;

   public Monkey(MonkeyData monkeyData)
   {
      this.monkeyData = monkeyData;
   }

   public Name { get { return this.monkeyData.Name; } }

   public bool IsYummy(Food food)
   {
      return this.monkeyData.FavoriteFood.Contains(food);
   }

   public MonkeyData GetData()
   {
      // CLONE the DTO here to avoid giving write access to the
      // entity innards without business rule enforcement
      return CloneData(this.monkeyData);
   }

}

Dino Esposito의 새 책을 찾았습니다 Microsoft® .NET : 기업의 아키텍처 응용 프로그램 이러한 유형의 질문과 문제에 대한 훌륭한 지식 저장소가되기 위해.

서비스 계층.

엔티티에 약간의 행동을 추가하려면 엔터티를 수정할 수 없다면 확장 방법을 시도하십시오. 나는 당신의 예에서와 같이 간단한 시나리오에 대해서만 이것을 할 것입니다. 더 복잡하거나 해당 여러 엔티티 및/또는 서비스, 계층 또는 이미 제안 된대로 도메인 서비스에 있어야하는지 조정합니다.

예를 들어 (예에서) :

public static class LineItemEntityExtensions
{
  public static decimal CalculateTotal(this LineItemEntity li)
  {
    return li.Quantity * li.Price;
  }
}

public static class OrderEntityExtensions
{
  public static decimal CalculateOrderTotal(this OrderEntity order)
  {
    decimal orderTotal = 0;
    foreach (LineItemEntity li in order.LineItems)
      orderTotal += li.CalculateTotal();
    return orderTotal;
  }
}

public class SomewhereElse
{
  public void DoSomething(OrderEntity order)
  {
    decimal total = order.CalculateOrderTotal();
    ...
  }
}

원하는 추가 사항이 거의 없으면 모든 것을 "DomainExtensions"클래스에 넣을 수 있지만, 그렇지 않으면 정기적으로 존중하여 처리하고 모든 엔터티의 확장을 한 클래스의 모든 클래스로 유지하는 것이 좋습니다. .

참고 : 내가 한 유일한 시간은 L2S 솔루션을 가지고 있었고 부분을 엉망으로 만들고 싶지 않은 시간입니다. 솔루션이 작기 때문에 연장이 많지 않았습니다. 나는 완전히 날아간 도메인 서비스 계층을 더 잘 사용한다는 아이디어를 좋아합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top