web-dev-qa-db-ja.com

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

私が理解しているように、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)のようなメソッドを作成しますか?
11
codefish

エンティティまたはドメインオブジェクト/ 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)などは、具象クラスの背後に隠れている場合にのみ役立ちます。したがって、具体的なリポジトリでのみ使用されるprotected T GetByPredicate(Func<T, bool> predicate)を公開するRepositoryBase<T>という抽象基本クラス(たとえば、public class CustomerRepository : RepositoryBase<Customer>)がある場合は、それで問題ありません。

8
MetaFight

CQRSを使用してドメインを実装するかなりの数のコミュニティがあります。私の感想は、リポジトリのインターフェースがそれらによって使用されるベストプラクティスに類似している場合、あまり遠くまでは行かないということです。

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

1)コマンドハンドラーは通常、リポジトリを使用して、リポジトリを介してアグリゲートをロードします。コマンドは、集合体の単一の特定のインスタンスをターゲットにします。リポジトリはIDによってルートをロードします。私が見ることができるように、コマンドが集約のコレクションに対して実行されるケースはありません(代わりに、まずクエリを実行して集約のコレクションを取得し、次にコレクションを列挙して、それぞれにコマンドを発行します。

したがって、アグリゲートを変更するコンテキストでは、リポジトリがエンティティ(別名アグリゲートルート)を返すことを期待しています。

2)クエリハンドラーは集計にまったく触れません。代わりに、それらは集計のプロジェクション(ある時点での1つまたは複数の集計の状態を記述する値オブジェクト)で機能します。したがって、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​​つの具体的な実装でしょう。

6
VoiceOfUnreason

DDDに関する私の知識に基づいて、あなたはこれを持っているべきです、

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

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

場合によっては、この質問に対するさまざまな人々の意見が混在することもあります。しかし、私は個人的に、リポジトリがCustomerServiceのようなサービスによって使用される場合は、IQueryableを使用することができ、そうでなければIEnumerableを使用することができると信じています。

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

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

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

まだ気付いていない場合は、一般的なCRUDに Generic Repository Pattern を使用することもできます。

1
Muhammad Raja