DDDでは、リポジトリはエンティティまたはドメインオブジェクトを公開する必要がありますか?


11

私が理解しているように、DDDでは、集約ルートでリポジトリパターンを使用することが適切です。私の質問は、データをエンティティまたはドメインオブジェクト/ DTOとして返す必要がありますか?

たぶんいくつかのコードが私の質問をさらに説明するでしょう:

エンティティ

public class Customer
{
  public Guid Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
}

このようなことをする必要がありますか?

public Customer GetCustomerByName(string name) { /*some code*/ }

またはこのようなもの?

public class CustomerDTO
{
  public Guid Id { get; set; }
  public FullName { get; set; }
}

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

追加の質問:

  1. リポジトリで、IQueryableまたはIEnumerableを返す必要がありますか?
  2. サービスまたはリポジトリでは、私のような何かを行う必要がありますか。.. GetCustomerByLastNameGetCustomerByFirstNameGetCustomerByEmail?または、次のようなメソッドを作成しGetCustomerBy(Func<string, bool> predicate)ますか?

GetCustomerByName('John Smith')データベースに20人のJohn Smithがいる場合、何が返されますか?2人の人が同じ名前を持っているとは思わないようです。
bdsl

回答:


8

エンティティまたはドメインオブジェクト/ DTOとしてデータを返す必要があります

まあそれは完全にあなたのユースケースに依存します。完全なエンティティの代わりにDTOを返すことを考えることができる唯一の理由は、エンティティが巨大であり、そのサブセットでのみ作業する必要がある場合です。

この場合、ドメインモデルを再検討し、大きなエンティティを関連する小さなエンティティに分割する必要があります。

  1. リポジトリで、IQueryableまたはIEnumerableを返す必要がありますか?

良い経験則は、常に可能な限り最も単純な(継承階層で最も高い)タイプを返すことです。そのIEnumerableため、リポジトリコンシューマにを使用させたい場合を除き、を返しますIQueryable

個人的に、私は返り値IQueryableは漏れやすい抽象化だと思いますが、そうではないと情熱的に主張する何人かの開発者に会いました。私の考えでは、すべてのクエリロジックはリポジトリに含まれ、隠されるべきです。呼び出しコードでクエリをカスタマイズできるようにした場合、リポジトリのポイントは何ですか?

  1. サービスまたはリポジトリで、GetCustomerByLastName、GetCustomerByFirstName、GetCustomerByEmailのようなことをする必要がありますか?または、GetCustomerBy(Func predicate)のようなメソッドを作成しますか?

ポイント1で述べたのと同じ理由で、間違いなくを使用しないでくださいGetCustomerBy(Func<string, bool> predicate)。最初は魅力的に思えるかもしれませんが、これがまさに人々が汎用リポジトリを嫌うことを学んだ理由です。漏れやすいです。

このようなものGetByPredicate(Func<T, bool> predicate)は、具体的なクラスの背後に隠れている場合にのみ役立ちます。そのため、具象リポジトリ(たとえば、)でのみ使用されるRepositoryBase<T>、exposed protected T GetByPredicate(Func<T, bool> predicate)which という抽象基本クラスがあれば、それで問題ありませんpublic class CustomerRepository : RepositoryBase<Customer>


ドメイン層にDTOクラスがあっても大丈夫ですか?
コードフィッシュ

それは私が言おうとしていたことではありません。私はDDDの第一人者ではないので、それが受け入れられるかどうかは言えません。好奇心から、なぜあなたは完全な実体を返さなかったのですか?大きすぎますか?
メタファイト

そうでもない。私はただ、何をするのが適切で許容できることなのかを知りたいだけです。完全なエンティティを返すのか、そのサブセットのみを返すのか。それはあなたの答えに基づいていると思います。
コードフィッシュ

6

ドメインを実装するためにCQRSを使用する大規模なコミュニティがあります。私の考えでは、リポジトリのインターフェイスがそれらのリポジトリで使用されているベストプラクティスに類似していれば、あなたはあまりにも迷わないでしょう。

私が見たものに基づいて...

1)通常、コマンドハンドラーはリポジトリを使用して、リポジトリを介して集約をロードします。コマンドは、集約の単一の特定のインスタンスを対象としています。リポジトリはIDでルートをロードします。コマンドが集計のコレクションに対して実行されるケースはありません(代わりに、最初に集計のコレクションを取得するクエリを実行してから、コレクションを列挙し、それぞれにコマンドを発行します)。

したがって、集約を変更するコンテキストでは、リポジトリがエンティティ(集約ルート)を返すことが期待されます。

2)クエリハンドラーは集計にまったく触れません。代わりに、それらは集約の投影で動作します-ある時点での集約/集約の状態を記述する値オブジェクト。したがって、AggregateDTOではなくProjectionDTOを考えてください。正しい考えがあります。

集計に対してクエリを実行したり、表示用に準備したりするコンテキストでは、エンティティではなく、DTOまたはDTOコレクションが返されることを期待しています。

あなたのgetCustomerByProperty呼び出しはすべて私にとってクエリのように見えるので、後者のカテゴリに分類されます。おそらく、単一のエントリポイントを使用してコレクションを生成したいと思うので、

getCustomersThatSatisfy(Specification spec)

合理的な選択です。クエリハンドラは、指定されたパラメータから適切な仕様を構築し、その仕様をリポジトリに渡します。欠点は、署名が本当にリポジトリがメモリ内のコレクションであることを示唆していることです。リポジトリがリレーショナルデータベースに対してSQLステートメントを実行するだけの抽象化である場合、述語が多くを購入するかどうかは明確ではありません。

ただし、役立つパターンがいくつかあります。たとえば、手作業で仕様を作成する代わりに、制約の説明をリポジトリに渡し、リポジトリの実装が何をすべきかを決定できるようにします。

警告:Javaのような入力が検出されました

interface CustomerRepository {
    interface ConstraintBuilder {
        void setLastName();
        void setFirstName();
    }

    interface ConstraintDescriptor {
        void copyTo(ConstraintBuilder builder);
    }

    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor);
}

SQLBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        WhereClauseBuilder builder = new WhereClauseBuilder();
        descriptor.copyTo(builder);
        Query q = createQuery(builder.build());
        //...
     }
}

CollectionBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        PredicateBuilder builder = new PredicateBuilder();
        descriptor.copyTo(builder);
        Predicate p = builder.build();
        // ...
}

class MatchLastName implements CustomerRepository.ConstraintDescriptor {
    private final lastName;
    // ...

    void copyTo(CustomerRepository.ConstraintBuilder builder) {
        builder.setLastName(this.lastName);
    }
}

結論として、集約を提供するかDTOを提供するかの選択は、消費者がそれを使用して何を期待するかによって異なります。私の推測では、各コンテキストのインターフェイスをサポートする具体的な実装の1つです。


それはすべて良いことですが、質問者はCQRSの使用について言及していません。あなたが正しいのですが、彼がそうすれば彼の問題は存在しないでしょう。
メタファイト

私はそれについて聞いたCQRSに関する知識を持っていません。私が理解しているように、AggregateDTOまたはProjectionDTOの場合に何を返すかを考えるとき、ProjectionDTOを返します。次にgetCustomersThatSatisfy(Specification spec)、検索オプションに必要なプロパティをリストします。正しくなっていますか?
コードフィッシュ

不明。AggregateDTOが存在するとは思わない。集約のポイントは、すべての変更がビジネス不変条件を満たしていることを確認することです。満たす必要があるドメインルールのカプセル化です。つまり、動作です。一方、プロジェクションは、ビジネスに受け入れられた状態のスナップショットの表現です。仕様を明確にする試みについては、編集を参照してください。
VoiceOfUnreason

AggregateDTOは存在すべきではないというVoiceOfUnreasonに同意します-ProjectionDTOはデータのみのビューモデルと考えています。呼び出し元が必要とするものにその形状を適合させ、必要に応じてさまざまなソースからデータを取り込みます。集約ルートは、保存する前にすべての参照ルールをテストできるように、すべての関連データの完全な表現である必要があります。DBテーブルの変更やデータ関係の変更など、より過酷な状況下でのみ形状が変化します。
ブラッドアービー

1

DDDの私の知識に基づいて、これが必要です。

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

リポジトリで、IQueryableまたはIEnumerableを返す必要がありますか?

それは、この質問に対するさまざまな人々の意見が混在していることに依存します。ただし、CustomerServiceなどのサービスでリポジトリを使用する場合は、IQueryableを使用できます。

リポジトリはIQueryableを返す必要がありますか?

サービスまたはリポジトリで、GetCustomerByLastName、GetCustomerByFirstName、GetCustomerByEmailのようなことをする必要がありますか?または、GetCustomerBy(Func predicate)のようなメソッドを作成しますか?

リポジトリには、前述のような汎用関数が必要ですGetCustomer(Func predicate)が、サービスレイヤーに3つの異なるメソッドを追加します。これは、サービスが異なるクライアントから呼び出され、異なるDTOが必要になる可能性があるためです。

まだ知らない場合は、共通のCRUDにGeneric Repository Patternを使用することもできます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.