0

ドメインエンティティには永続性に関連するコードを含めるべきではないため、永続性を無視する PIである必要があります

ドメインモデル DMが関心を持っているデータは、ドメインエンティティナビゲーションプロパティを介して、または上位層(つまり、UI層またはサービス層)によってDMに配信できます。

ただし、特定のドメインエンティティが必要なデータを動的に決定する必要があるシナリオでは、そのエンティティがリポジトリなどのコンポーネントを介してそのデータを要求することは完全に許容できると思いました。

このリポジトリが永続層から完全に分離されている場合、エンティティはPIに違反していません。これは、データを取得する方法がまだわからないため、リポジトリからデータを要求することによってのみデータを取得することを認識しているためです。

class Customer
{
       public string InterestedWhatOtherCustomerOrdered( ... )
       {
                ...
                var orders = repository.Find...;
                ...
        }
       ...
}

そのため、ドメインコードが、上位層またはナビゲーションプロパティからデータを受信するだけでなく、リポジトリから必要なデータを要求できるようにすることは、なぜ悪い習慣と見なされるのでしょうか。

つまり、Fowlerデータマッパーに関するPEAAの章)によると、ドメインコードに必要なメソッドをデータマッパーからインターフェイスクラスに抽出して、ドメインコードで使用できるようにすることは問題ありません。

セバスチャングッドへの返信:

1)

アイデアは、ドメインモデルがそのデータがどこから来たのかについての詳細に関係するべきではないということです。

ただし、ドメインエンティティがPIルールに準拠している場合、データが実際にどこから来たのかについての詳細を知らないと主張することができます。

2)それでもそのデータをロードする方法を決定する必要がありますが、「アプリケーションサービス」に(通常は)それについて心配させます。

a)実世界のエンティティが特定のデータを検索する機能を持っていると仮定すると、データを要求するドメインエンティティに問題があると見なしますか(申し訳ありませんが、そのような一般的な質問に答えるのは難しいことを認識しています)?

b)最も重要なことは、アプリケーションサービスレイヤーが、ドメインエンティティが処理に必要とする可能性のあるさまざまな種類のデータすべてをどのように予測できるかを理解するのに苦労しています。

つまり、アプリケーション層サービスがデータの読み込みを単独で担当しないということは、ドメインエンティティの内部ロジックを変更するたびに(このエンティティ が異なるタイプのデータを必要とするように)、アプリケーションサービスも変更する必要があることを意味しますしたがって、古いデータではなく新しいタイプのデータをエンティティに提供するようになりますか?!

Eulerfxへの返信:

1)

a)The application service can provide not only data, but a mechanism for retrieving data as well, in cases where it is better to place logic for determining the exact instance of data needed in the domain

したがって、ドメインで必要なデータの正確なインスタンスを決定するためのロジックを配置する方がよい場合は、サービスS内のリポジトリへのアクセスをカプセル化してから、ドメインエンティティのメソッドへの引数としてSを渡す必要がありますか?したがって、この例では、内部サービスへのアクセスをカプセル化してから、引数として:に渡す必要があります。 OrderRepositoryordersSelectorServiceordersSelectorServiceCustomer.InterestedWhatOtherCustomerOrdered

class Customer
{
       public string InterestedWhatOtherCustomerOrdered(OrdersSelectorService ordersSelectorService)
       {
                ...
                var orders = ordersSelectorService.Select...;
                ...
        }
        ...
}



class CustomerService
{
  OrdersSelectorService ordersSelectorService;
  CustomerRepository customerRepository;

  public void ()
  {
        var customer = this.customerRepository.Get...;
                ...

        customer.InterestedWhatOtherCustomerOrdered(ordersSelectorService);
                ...

  }
}

OrderRepositoryb)それが実際にあなたが提案していることである場合、単に引数として渡すことよりも他の利点がありますか(すでに述べたもの以外に)Customer.InterestedWhatOtherCustomerOrdered

class Customer
{
       public string InterestedWhatOtherCustomerOrdered(CustomerRepository orderRepository)
       {
                ...
                var orders = orderRepository.Select...;
                ...
       }
       ...
}

2)次の質問は、あなたの投稿全体を正しく理解したことを確認するためのものです。

So if a specific behavior requires access to some service, have the application service provide an abstraction of that service as an argument to the corresponding behavior method. This way, the dependency upon the service is explicitly stated in the method signature.

a)「特定の動作」とは、ドメインエンティティ(つまりCustomer)を指しているのですか?!

b)「引数としてそのサービスの抽象化を提供するアプリサービス」が何を意味するのか正確にはわかりません。おそらく、サービス S自体(ie OrderRepository)をメソッド(ie Customer.InterestedWhatOtherCustomerOrdered)の引数として提供する代わりに、クラス C(ie )でSOrdersSelectorServiceをカプセル化してから、 Cをメソッドの引数として渡す必要がありますか?

c)C ( Sをカプセル化するクラス< -b)の質問を参照))は常にアプリケーションサービスであり、Sは常にCによってカプセル化される必要があると仮定します( Sがすでにアプリケーションサービスである場合を除く)?はいの場合、なぜですか?

d)

このように、サービスへの依存関係はメソッドシグネチャに明示的に示されます。

メソッドシグネチャに明示的に記述されているサービスに依存することで、どのようなメリットが得られますか?メソッドのコードを検査しなくても、メソッドが何をしているのかをすぐに知ることができるということだけですか?

3)少し外れたトピックですが、メソッドM( )の引数として動作BをクラスCに注入すると、依存性注入とは呼ばれませんが、代わりにコンストラクターまたはセッターを介してBがCに注入された場合に表示されます( )、それからそれを依存性注入と呼びます。何故ですか?C.M(B b);B b=new B();C c=new C(b);

Eulerfxへの2回目の返信:

1)

1ab)...別のオプションは、OrdersSelectorServiceの代わりにラムダを使用することです。

に渡す代わりに、Customer.InterestedWhatOtherCustomerOrdered内でLinq-to-Entities(ラムダに大きく依存している)OrdersSelectorService使用する必要Customer.InterestedWhatOtherCustomerOrderedがあることを意味していると思います。しかし、私が知る限り、これは永続性無視ルールに違反します(私の前のスレッドを参照してください)

2)

2c)いいえ、Cは必要なメソッドを含むインターフェースである必要があります。サービスSは、そのインターフェイスを実装することも、その場で実装を提供することもできます。

ああ、 Cはアプリケーションサービスであるべきだとあなたが提案していると誤解しました。とにかく、Cはどこに住むべきですか?アプリケーションサービスアセンブリ内またはドメインモデルアセンブリ内にパックする必要がありますか?

3)

2d)...クラス自体のコンストラクターとは対照的に、メソッドシグネチャで依存関係を宣言する利点は...もう1つの利点は、ドメインクラスがIoCコンテナーからの依存関係グラフの一部である必要がないことです-make物事はもっと簡単です。

IoCについてはまだよくわからないので、ドメインクラスがIoCの依存関係グラフの一部になるのはどの程度正確かを尋ねる必要があります。つまり、このドメインクラスはIoCの構成レイヤー内で指定する必要があります(このレイヤーは、依存関係のインターフェイスと実際の依存関係の実装との間のマッピングを指定するためにのみ使用されると思いました。したがって、依存クラスは均等ではないと想定しました。このレイヤー内で言及)または...?

4)トラブルを引き起こしたり、あなたの一人が間違っていることを暗示したりするつもりはありません(あなたの両方があなたのデザインを好む理由をすでに推論しています)が、私はあなたの投稿を完全に理解したことを確認したいと思います。実際、 nwang0が提案しているのとは正反対のことを推奨しています(つまり、両方が同じことを推奨している場合、私の理解力には修復が必要です:o)?!

ありがとうございました

4

3 に答える 3

2

アイデアは、ドメインモデルがそのデータがどこから来たのかについての詳細に関係するべきではないということです。それでもそのデータをロードする方法を決定する必要がありますが、「アプリケーションサービス」に(通常は)それについて心配させます。このようにして、ドメインオブジェクトがドメインロジックについて心配している間に、データの永続性、キャッシュ、セキュリティなどの無数の複雑さを管理できます。

または、別の説得力のある議論は、それが単一責任の原則の違反であるということです。これで、ドメインオブジェクトは、独自のロジックを理解し、データを要求する方法を理解する責任があります。

于 2012-10-04T20:32:28.997 に答える
1

ドメインオブジェクトが必要なデータを要求することは悪い習慣ではありませんが、通常、リポジトリの依存関係をエンティティに直接注入することは悪い習慣と見なされます。この理由の1つは、ドメインオブジェクトが依存関係グラフの一部になり、不必要に複雑になることです。さらに、リポジトリには通常、トランザクションや作業単位などの周囲の依存関係が含まれています。これにより複雑さが増し、ドメインロジックについての推論がより困難になります。

代わりに、Sebastian Goodによって指定されているように、アプリケーションサービスにエンティティが必要とするデータを提供させるのが最善です。アプリケーションサービスは、リポジトリやその他のゲートウェイを注入するのに最適な場所です。アプリケーションサービスは、データだけでなく、ドメインで必要なデータの正確なインスタンスを決定するためのロジックを配置する方がよい場合に、データを取得するためのメカニズムも提供できます。例として、この質問を見てください。したがって、特定の動作で何らかのサービスへのアクセスが必要な場合は、対応する動作メソッドの引数として、アプリケーションサービスにそのサービスの抽象化を提供してもらいます。このように、サービスへの依存関係はメソッドシグネチャに明示的に示されます。

アップデート

1ab)はい、その通りです。別のオプションは、の代わりにラムダを使用することですOrdersSelectorService。ラムダがご使用の言語で使用できない場合は、インターフェースである必要があります。パスに対する利点は、不必要な結合を減らすことを目的としたインターフェイス分離の原則OrderRepositoryに基づいています。Customerの動作に、OrderRepositoryのすべてのメソッドが必要になる可能性は低く、代わりに特定の関数が必要になるため、明示的にしてください。

2a)はい、私が参照している動作はCustomerエンティティの動作であり、これはクラスのメソッドの1つにすぎません。

2b)1abに記載されている理由によりはい。

2c)いいえ、Cは必要なメソッドを含むインターフェースである必要があります。サービスSは、そのインターフェイスを実装することも、その場で実装を提供することもできます。

2d)はい。これは、サービスの場所よりも依存性の注入を支持する議論の一部です。クラス自体のコンストラクターとは対照的に、メソッドシグネチャで依存関係を宣言することの利点は、そのサービスが通常1つのメソッドにのみ必要であり、それをクラスのメンバーにするのは無駄だからです。もう1つの利点は、ドメインクラスがIoCコンテナの依存関係グラフの一部である必要がないことです。これにより作業が簡単になります。

3)両方の依存性注入(DI)と呼びます。DIは、クラスコンストラクターまたはメソッドがサービスロケーターを介して必要なサービスを取得する責任があるサービスの場所を対比することを目的としています。

更新2

1)これがC#コードサンプルです:

// this is is the repository, but it doesn't have to be an interface, just some class encapsulating data access
interface IOrderRepository
{
  Order Get(string id);
  void Add(Order order);
  IEnumerable<Order> GetOrdersBySomeCriteria(SomeCriteria criteria);
}

class Customer
{
   // the selector parameter is a lambda.
   public string InterestedWhatOtherCustomerOrdered(Func<SomeCriteria, IEnumerable<Order>> selector)
   {
      // do stuff with selector lambda
   }
}

// this is the app service
class CustomerApplicationService
{
  readonly IOrderRepository orderRepository;

  public void DoSomething()
  {
     var customer = this.customerRepository.Get ...;

     // the app service passes lambda which in turn points to repository.
     var result = customer.InterestedWhatOtherCustomerOrdered(criteria => this.orderRepository.GetOrdersBySomeCriteria(criteria));

  }
}

これは永続性の無知に違反せず、非常に切り離されています。メソッドのラムダパラメーターは、InterestedWhatOtherCustomerOrderedメソッドに必要なものを正確に指定します。それ以上のものはありません。そして、その機能がどのように提供されるかは関係ありません。

2)lamdaの場合、Cはラムダによって全体が指定されているため、実際にはどこにも存在しません。ただし、たとえばIOrderSelector、インターフェースを使用する場合は、Customerアグリゲートが存在する場所でそのインターフェースを宣言する必要があります。によって直接実装するOrderRepositoryことも、アダプタクラスを使用することもできます。

3)IoCについて言及する理由は、別のアプローチが、Customerクラスのコンストラクターで順序セレクターへの依存関係を宣言することであるためです。次に、クラスの新しいインスタンスが作成されるたびに、その依存関係(順序セレクター)を挿入する必要があります。Customerそのための1つの方法は、クラスがインスタンス化される場所でIoCコンテナーを使用することです。これが問題となる理由は、Customerクラスをインスタンス化する場所でIoCコンテナにアクセスできるようにする必要があるためです。顧客の作成は注文セレクターとは関係がなく、1つの動作のみがそれを必要とするため、これは責任の不整合でもあります。

4)それは私が思う哲学の違いです。上記の理由やその他の理由で、ドメインオブジェクトがリポジトリを参照するのは好きではありません。全体として、SOやブログなどを閲覧する場合、通常は眉をひそめます。リポジトリインターフェイスがドメインで宣言されていることは事実ですが、ドメインエンティティから直接参照する必要があるという意味ではありません。

于 2012-10-06T00:44:23.633 に答える
1

ドメインオブジェクトがリポジトリオブジェクトに依存することは何も悪いことではありません。実際、リポジトリオブジェクトはドメインモデルに属しており、リポジトリインターフェイスは残りのドメインオブジェクトと一緒にパッケージ化する必要があります。

ただし、リポジトリインターフェースを抽象的に保ち、それらが実装される特定の方法に結合されないようにすることが重要です。つまり、OrderRepositoryには、セマンティクスのようなコレクションがあり、仕様を使用する必要があります。この投稿には、リポジトリの構築/使用の良い例がいくつかあります。http://thinkinginobjects.com/2012/08/26/dont-use-dao-use-repository/

一方、上位層がアプリケーションサービス層を意味すると仮定すると、上位層から値を受け取ることはあまり良い解決策ではないと思います。

あなたの例では、次のようになります。

var orders = repository.Find...;

実際には、関連する注文を検索するために、いくつかの情報をリポジトリに渡す必要があります。私はここで例を作ります:

var orders = repository.FindByDate(productIdThisCustomerLike);

productIdThisCustomerLikeはCustomerのプライベートフィールドであると想定しています。

CustomerオブジェクトでRepository.Findを作成し、ローカル情報を渡すのは自然なことです。代わりに、repository.Findをアプリケーションサービスレイヤーで呼び出すことを選択した場合、顧客から製品ID情報を抽出する必要があります。それはカプセル化を壊し、それ故に邪悪な解決策になるでしょう。

あなたのコメントへの回答:

  1. リポジトリをサービスでラップする必要はありません。ドメインオブジェクトがサービスオブジェクトに依存していることは悪い習慣だと思います。サービスレイヤーはドメインモデルレイヤーに依存しており、その逆ではないからです。返された注文のリストで後処理(フィルタリング、グループ化、マージなど)が必要な場合は、CustomerとOrderRepositoryの間に別のドメインオブジェクトを導入し、サービスではなくドメインオブジェクトのように名前を付けます。

  2. ユースケースによって異なります。Customer.InterestedWhatOtherCustomerOrderedがサービスレイヤーから直接呼び出された場合、サービスレイヤーからリポジトリ参照を渡すことができます。ただし、別のドメインオブジェクト(ShoppingCartなど)によって呼び出された場合、同じメソッドにより、ShoppingCartはCustomerを提供するためだけにOrderRepositoryを認識します。一般的に、私はドメインオブジェクトに必要なリポジトリへの参照を保持させることを非常に好みます。

于 2012-10-07T22:24:31.547 に答える