20

私たちの現在の 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;
}

何かご意見は?

4

8 に答える 8

26

基本に戻りましょう。

サービス

サービスには、ドメイン サービスアプリケーション サービスインフラストラクチャ サービスの 3 つの種類があります。

  • ドメイン サービス: ドメイン オブジェクト内に本来収まらないビジネス ロジックをカプセル化します。あなたの場合、すべてのビジネスロジック。
  • Application Services : 外部のコンシューマーがシステムと対話するために使用します
  • インフラストラクチャ サービス: 技術的な問題 (MSMQ、電子メール プロバイダーなど) を抽象化するために使用されます。

リポジトリ

これは、データアクセスと一貫性チェックが行われる場所です。純粋な DDD では、Aggregate Rootsは (オブジェクトを永続化する前に) 一貫性をチェックする責任があります。あなたの場合、ドメイン サービスレイヤーからのチェックを使用します。


提案された解決策:既存のサービスを分割する

新しいドメイン サービスレイヤーを使用して、DTO のすべてのロジックをカプセル化し、一貫性チェックも (仕様を使用して、おそらく?) します。

Application Serviceを使用してFetchOpenOrdersWithLines、要求をリポジトリに転送する必要なフェッチ メソッド ( ) を公開します(Jeremy が提案したように、ジェネリックを使用します)。クエリ仕様を使用してクエリをラップすることも検討してください。

リポジトリから、オブジェクトを永続化する前に、ドメイン サービスレイヤーの仕様を使用してオブジェクトの整合性などを確認します。

Evans の著書にサポート情報があります。

  • 「サービスと分離ドメイン層」 (106 ページ)
  • 「仕様」 (224ページ)
  • 「クエリの仕様」 (229 ページ)
于 2009-12-22T09:03:54.960 に答える
11

Muに答えたくなりましたが、詳しく説明したいと思います。要約すると、ORM の選択によってドメイン モデルの定義方法が決まることはありません。

ドメイン モデルの目的は、ドメインをモデル化する豊富なオブジェクト指向 API になることです。真のドメイン駆動設計に従うには、ドメイン モデルをテクノロジに制約されずに定義する必要があります。

言い換えれば、ドメイン モデルが最初に来て、すべてのテクノロジー固有の実装は、その後、ドメイン モデルと問題のテクノロジーの間をマッピングするマッパーによって処理されます。多くの場合、これには両方の方法が含まれます。ORM の選択によって制約が導入される可能性のあるデータ アクセス層と、UI テクノロジが追加の要件を課す UI 層です。

実装がドメイン モデルから著しくかけ離れている場合は、Anti-Corruption Layerについて説明します。

あなたの場合、Anemic Domain Model と呼ばれるものは、実際にはデータ アクセス層です。最良の手段は、テクノロジに中立な方法でエンティティへのアクセスをモデル化するリポジトリを定義することです。

例として、注文エンティティを見てみましょう。テクノロジーに制約されない注文をモデル化すると、次のような結果になる可能性があります。

public class Order
{
    // constructors and properties

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

これは Plain Old CLR Object ( POCO ) であるため、テクノロジによる制約を受けないことに注意してください。問題は、これをどのようにデータ ストアに出し入れするかです。

これは、抽象 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 の Entity Framework など) では、特定の基本クラスからデータ クラスを派生させる必要があるため、POCO としてのドメイン オブジェクトにはまったく適合しません。そのため、マッピングが必要です。

認識すべき重要なことは、意味的にドメイン エンティティに似た厳密に型指定されたデータ クラスを使用している可能性があるということです。ただし、これは純粋な実装の詳細であるため、混乱しないでください。たとえばEntityObject から派生する Order クラスはドメイン クラスではありません。これは実装の詳細であるため、IOrderRepository を実装するときは、 Order Data Classを Order Doman Classにマップする必要があります。

これは面倒な作業かもしれませんが、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);
}
于 2009-12-25T13:04:37.963 に答える
5

これがまさにサービス層の目的です。ビジネスロジック層と呼ばれるアプリケーションも見てきました。

これらは、ほとんどの時間をテストに費やしたいルーチンです。それらが独自のレイヤーにある場合は、リポジトリ レイヤーをモック アウトするのは簡単です。

リポジトリ レイヤーは可能な限り汎用化する必要があるため、特定のクラスに固有のビジネス ロジックには適していません。

于 2009-12-19T17:31:56.557 に答える
4

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);
   }

}
于 2009-12-22T17:29:45.947 に答える
4

あなたの言うことから、サービス層とリポジトリ層について厳密に考えすぎている可能性があります。プレゼンテーション層がリポジトリ層に直接依存することを望まないようです。これを実現するために、サービス層のリポジトリ (パススルー メソッド) からメソッドを複製しています。

私はそれを疑問視します。それを緩和して、両方をプレゼンテーション レイヤー内で使用できるようにして、最初の作業をよりシンプルにすることができます。たぶん、そのようにリポジトリを非表示にすることで何を達成したかを自問してください。すでに永続性を抽象化し、それらを使用して IMPLEMENTATION をクエリしています。これは素晴らしいことであり、その目的のために設計されています。エンティティが永続化されているという事実を隠すサービス層を作成しようとしているようです。理由は?

注文合計などの計算に関しては、サービスレイヤーが自然な家になります。LineTotal(LineItem lineItem) メソッドと OrderTotal(Order order) メソッドを持つ SalesOrderCalculator クラスは問題ありません。また、必要に応じて OrderServices.CreateOrderCalculator() などの適切なファクトリを作成して、実装を切り替えることを検討することもできます (たとえば、注文割引に対する税には国固有のルールがあります)。これは、注文サービスへの単一のエントリ ポイントを形成し、IntelliSense を介して簡単に検索できるようにすることもできます。

これらすべてがうまくいかないように聞こえる場合は、抽象化が何を達成しているか、それらが互いにどのように関係しているか、および単一責任の原則について、より深く考える必要があるかもしれません。. リポジトリはインフラストラクチャの抽象化です (エンティティの保存方法と取得方法を隠します)。サービスは、ビジネス アクションまたはルールの実装を抽象化し、バージョン管理または分散のためのより良い構造を可能にします。それらは一般的に、あなたが説明する方法で階層化されていません。サービスに複雑なセキュリティ ルールがある場合は、リポジトリの方が適している場合があります。典型的な DDD スタイルのモデルでは、リポジトリ、エンティティ、値オブジェクト、およびサービスはすべて、同じレイヤー内で、同じモデルの一部として一緒に使用されます。したがって、上のレイヤー (通常はプレゼンテーション) は、これらの抽象化によって分離されます。モデル内では、あるサービスの実装が別のサービスの抽象化を使用する場合があります。さらなる改良により、誰がどのエンティティまたは値オブジェクトへの参照を保持するかについてのルールが追加され、より正式なライフサイクル コンテキストが適用されます。Eric Evans の本また​​はDomain Driven Design Quickly

于 2009-12-22T15:53:13.087 に答える
3

Dino Espositoの新しい本Microsoft®.NET:Architecting Applications for the Enterpriseは、これらのタイプの質問や問題に関する知識の優れたリポジトリであることがわかりました。

于 2009-12-24T04:43:48.590 に答える
1

サービス層。

于 2009-12-19T16:35:33.367 に答える
1

エンティティに少し動作を追加したいが、エンティティを変更できない場合は、拡張メソッドを試してみてください。ただし、あなたの例のような単純なシナリオでのみこれを行います。より複雑なもの、またはいくつかのエンティティおよび/またはサービス、レイヤー、または既に提案されているようにドメインサービスにあるものの間で調整するもの。

例(あなたの例から):

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」クラスに入れることができますが、それ以外の場合は、それらを定期的に扱い、エンティティのすべての拡張機能を独自のファイルの 1 つのクラスに保持することをお勧めします。 .

参考までに: これを行ったのは、L2S ソリューションがあり、パーシャルをいじりたくないときだけです。また、ソリューションが小さかったため、多くの拡張機能もありませんでした。本格的なドメイン サービス レイヤーを使用するというアイデアが気に入っています。

于 2009-12-28T21:10:19.347 に答える