リポジトリを削減してルートを集約する


83

現在、データベース内のほぼすべてのテーブルのリポジトリがあり、それらを集約ルートのみに縮小することで、DDDとさらに整合させたいと考えています。

次のテーブルがあると仮定しましょう。 UserPhone。各ユーザーは1つ以上の電話を持っている可能性があります。集約ルートの概念がなければ、私は次のようなことをするかもしれません:

//assuming I have the userId in session for example and I want to update a phone number
List<Phone> phones = PhoneRepository.GetPhoneNumberByUserId(userId);
phones[0].Number = “911”;
PhoneRepository.Update(phones[0]);

集合根の概念は、実際よりも紙の上で理解する方が簡単です。ユーザーに属していない電話番号を取得することは決してないので、PhoneRepositoryを廃止し、電話関連のメソッドをUserRepositoryに組み込むことは理にかなっていますか?答えが「はい」であると仮定して、前のコードサンプルを書き直します。

UserRepositoryに電話番号を返すメソッドを設定することはできますか?または、常にユーザーへの参照を返し、ユーザーを介して関係をトラバースして電話番号に到達する必要があります。

List<Phone> phones = UserRepository.GetPhoneNumbers(userId);
// Or
User user = UserRepository.GetUserWithPhoneNumbers(userId); //this method will join to Phone

どちらの方法で電話を入手したとしても、そのうちの1つを変更したとすると、どうすれば更新できますか?私の限られた理解は、ルートの下のオブジェクトはルートを介して更新する必要があるということです。これにより、以下の選択肢1に進むことができます。これはEntityFrameworkで完全に機能しますが、コードを読んでいると、Entity Frameworkがグラフ内の変更されたオブジェクトを監視しているにもかかわらず、実際に何を更新しているのかわからないため、これは非常にわかりにくいようです。

UserRepository.Update(user);
// Or
UserRepository.UpdatePhone(phone);

最後に、私のような本当に何にも縛られない、いくつかのルックアップテーブルを、持っていると仮定するとCountryCodesColorsCodesSomethingElseCodes。ドロップダウンにデータを入力するため、またはその他の理由でそれらを使用する場合があります。これらのスタンドアロンリポジトリはありますか?それらを組み合わせて、次のような論理的なグループ化/リポジトリにすることはできますCodesRepositoryか?または、それはベストプラクティスに反します。


2
確かに非常に良い質問です。私は自分自身と多くの苦労をしてきました。「正しい」解決策がないトレードオフポイントの1つのようです。私がこれを書いている時点で入手可能な答えは良く、ほとんどの問題をカバーしていますが、それらが「最終的な」解決策を提供しているとは感じません。:(
cwap 2011年

聞いたところによると、「正しい」解決策にどれだけ近づくことができるかに制限はありません。より良い方法を学ぶまで、最善を尽くさなければならないと思います:)
e36M3 2011年

+ 1-私もこれに苦労しています。以前は、テーブルごとに個別のリポジトリとサービスレイヤーがありました。意味のあるところでこれらを組み合わせ始めましたが、最終的には1,000行を超えるコードを含むリポジトリとサービスレイヤーになりました。私の最新のアプリケーションスライスでは、アイテムが依存している場合でも、密接に関連する概念のみを同じリポジトリ/サービスレイヤーに配置するように少しバックアップしました。例-ブログの場合、投稿リポジトリの集計にコメントを追加していましたが、コメントリポジトリ/サービスを分離するためにコメントを分離しました。
jpshook 2011年

回答:


12

リポジトリには任意のメソッドを含めることができます:)どちらの場合も、電話リストを入力してユーザーに返すのが理にかなっています。通常、ユーザーオブジェクトにはすべてのサブ情報(たとえば、すべての住所、電話番号)が完全に入力されるわけではなく、ユーザーオブジェクトにさまざまな種類の情報を入力するためのさまざまな方法がある場合があります。これは、遅延読み込みと呼ばれます。

User GetUserDetailsWithPhones()
{
    // Populate User along with Phones
}

更新の場合、この場合、電話番号自体ではなく、ユーザーが更新されます。ストレージモデルは、電話を別のテーブルに保存する場合があります。そのようにすると、電話だけが更新されていると思うかもしれませんが、DDDの観点から考えるとそうではありません。読みやすさに関する限り、

UserRepository.Update(user)

単独では何が更新されているかを伝えません。その上のコードは何が更新されているかを明確にします。また、更新されている内容を示す可能性のあるフロントエンドメソッド呼び出しの一部である可能性があります。

ルックアップテーブルの場合、そして実際にはそうでない場合でも、GenericRepositoryを用意してそれを使用すると便利です。カスタムリポジトリは、GenericRepositoryから継承できます。

public class UserRepository : GenericRepository<User>
{
    IEnumerable<User> GetUserByCustomCriteria()
    {
    }

    User GetUserDetailsWithPhones()
    {
        // Populate User along with Phones
    }

    User GetUserDetailsWithAllSubInfo()
    {
        // Populate User along with all sub information e.g. phones, addresses etc.
    }
}

Generic Repository Entity Frameworkを検索すると、多くの優れた実装がうまくいきます。それらの1つを使用するか、独自に作成してください。


@amit_g、情報をありがとう。私はすでに、他のすべてが継承する汎用/ベースリポジトリを利用しています。「ルックアップ」テーブルを1つのリポジトリに論理的にグループ化するという私の考えは、単に時間を節約し、リポジトリの数を減らすことでした。したがって、ColorCodeRepositoryとAnotherCodeRepositoryを作成する代わりに、CodesRepository.GetColorCodes()とCodesRepository.GetAnotherCodes()を作成するだけです。しかし、無関係なエンティティを1つのリポジトリに論理的にグループ化することが悪い習慣であるかどうかはわかりません。
e36M3 2011年

また、DDDルールにより、ルートに対応するリポジトリ内のメソッドが、グラフ内の基になるエンティティではなくルートを返す必要があることを確認しています。したがって、私の例では、グラフの残りの部分(またはアドレスや電話などのグラフの一部)がどのように見えるかに関係なく、UserRepositoryのどのメソッドもユーザータイプのみを返すことができますか?
e36M3 2011年

CodesRepositoryは問題ありませんが、それに属するものを一貫して維持することは困難です。GenericRepository <ColorCodes> GetAll()を使用するだけで、同じことが実現できます。GenericRepositoryには非常に一般的なメソッド(GetAll、GetByIDなど)しかないため、ルックアップテーブルでは問題なく機能します。
amit_g 2011年

1
@ e36M3、はい。たとえば、geekswithblogs.net
seanfao

2
残念ながら、この答えは間違っています。リポジトリはメモリ内オブジェクトのコレクションとして扱われる必要があり、遅延読み込みは回避する必要があります。ここではそのことについて素晴らしい記事ですbesnikgeek.blogspot.com/2010/07/...は
ラファウŁużyński

9

Aggregate Rootリポジトリの例は完全に問題ありません。つまり、別のエンティティに依存せずに合理的に存在できないエンティティには、独自のリポジトリ(この場合はPhone)を含めるべきではありません。この考慮事項がないと、dbテーブルへの1-1マッピングでリポジトリが急増していることにすぐに気付くことができます。

リポジトリ自体ではなく、データの変更に作業単位パターンを使用することを検討する必要があります。これは、データベースへの変更の永続化に関して、意図について混乱を引き起こしていると思うためです。EFソリューションでは、作業単位は基本的にEFコンテキストのインターフェイスラッパーです。

ルックアップデータのリポジトリに関しては、ドメインエンティティ(国、色など)に特に属していないデータを担当するReferenceDataRepositoryを作成するだけです。


1
ありがとうございました。Unit of Workがリポジトリをどのように置き換えるのかわかりませんか?すべてのビジネストランザクションの終了時(HTTPリクエストの終了時)にEntity Frameworkコンテキストへの単一のSaveChanges()呼び出しがあるという意味で、私はすでにUOWを採用しています。ただし、データアクセスのために(EFコンテキストを格納する)リポジトリを引き続き使用します。UserRepository.Delete(user)やUserRepository.Add(user)など。
e36M3 2011年

5

電話がユーザーなしで意味をなさない場合、それはエンティティ(IDが気になる場合)または値オブジェクトであり、常にユーザーを介して変更し、一緒に取得/更新する必要があります。

集約ルートをコンテキスト定義者と考えてください。それらはローカルコンテキストを描画しますが、グローバルコンテキスト(アプリケーション)自体にあります。

ドメイン駆動設計に従う場合、リポジトリは集約ルートごとに1:1であると想定されます。
言い訳しない。

私はこれらがあなたが直面している問題であるに違いない:

  • 技術的な問題-オブジェクト関係インピーダンスの不一致。オブジェクトグラフ全体を簡単に永続化するのに苦労していて、エンティティフレームワークの種類は役に立ちません。
  • ドメインモデルはデータ中心です(動作中心ではありません)。そのため-オブジェクト階層(前述のコンテキスト)に関する知識が失われ、魔法のようにすべてが集約ルートになります。

最初の問題を修正する方法がわかりませんが、2番目の問題を修正すると最初の問題が十分に修正されることに気付きました。行動中心の意味を理解するには、このペーパーを試してみてください。

PSリポジトリを集約ルートに減らすことは意味がありません。
Ppsは避け"CodeRepositories"ます。それはデータ中心->手続き型コードにつながります。
Ppps作業単位パターンを避けてください。集約ルートは、トランザクション境界を定義する必要があります。


1
:紙へのリンクがアクティブでなくなるため、代わりにこのいずれかを使用しないweb.archive.org/web/20141021055503/http://www.objectmentor.com/...
JwJosefy

3

これは古い質問ですが、簡単な解決策を投稿する価値があると考えました。

  1. EF Contextは、作業単位(変更を追跡)とリポジトリ(DBからのものへのメモリ内参照)の両方をすでに提供しています。それ以上の抽象化は必須ではありません。
  2. Phoneは集約ルートではないため、コンテキストクラスからDBSetを削除します。
  3. 代わりに、Userの「Phones」ナビゲーションプロパティを使用してください。

static void updateNumber(int userId、string oldNumber、string newNumber)

static void updateNumber(int userId, string oldNumber, string newNumber)
    {
        using (MyContext uow = new MyContext()) // Unit of Work
        {
            DbSet<User> repo = uow.Users; // Repository
            User user = repo.Find(userId); 
            Phone oldPhone = user.Phones.Where(x => x.Number.Trim() == oldNumber).SingleOrDefault();
            oldPhone.Number = newNumber;
            uow.SaveChanges();
        }

    }

抽象化は必須ではありませんが、推奨されます。Entity Frameworkは、依然として単なるプロバイダーであり、インフラストラクチャの一部です。プロバイダーが変更された場合に何が起こるかだけでなく、大規模なシステムでは、異なるドメインの概念を異なる永続化メディアに永続化する複数のタイプのプロバイダーが存在する可能性があります。これは、早い段階で非常に簡単に作成できる一種の抽象化ですが、十分な時間と複雑さを超えてリファクタリングするのは面倒です。
ジョセフ・フェリス

1
リポジトリインターフェイスに抽象化しようとすると、EFのORMの利点(遅延読み込み、クエリ可能など)を維持するのが非常に難しいことがわかりました。
白亜2016

確かに、それは興味深い議論です。遅延読み込みは実装に非常に固有であるため、その値はインフラストラクチャ(レイヤー境界変換を使用したドメインオブジェクトの入出力)に限定されていることがわかりました。私が見た実装の多くは、一般的な抽象化を試みると問題が発生します。一般的なメソッドにはドメイン値がほとんどないため、明示的な実装を使用する傾向があります。EFはクエリ可能オブジェクトを非常に使いやすくしますが、問題はリポジトリの役割になります。つまり、コントローラによって使用されるリポジトリは、抽象化の利点を逃します。
ジョセフ・フェリス

0

Phoneエンティティが集約ルートユーザーと一緒にのみ意味をなす場合、新しい電話レコードを追加する操作は、特定のメソッド(DDD動作)を介したユーザードメインオブジェクトの責任であり、それが可能であるということも意味があると思います。いくつかの理由で完全に理にかなっていますが、当面の理由は、Phoneエンティティがその存在に依存しているため、Userオブジェクトが存在することを確認し、他のプロセスが以前にルート集計を削除していないことを確認するためにさらに検証チェックを実行しながら、トランザクションロックを保持する必要があることです。操作の検証が完了しました。他の種類のルートアグリゲートを使用する場合は、値をアグリゲートまたは計算し、ルートアグリゲートの列プロパティに保持して、後で他の操作でより効率的に処理できるようにすることができます。

また、所有しているユーザーに関係なくすべての電話を取得するメソッドを使用する場合は、ユーザーリポジトリを介して、1つのメソッドですべてのユーザーをIQueryableとして返すことができます。その後、それらをマップしてすべてのユーザーの電話を取得し、改良を加えることができます。それでクエリします。したがって、この場合はPhoneRepositoryも必要ありません。それに加えて、メソッドの背後でクエリを抽象化したい場合は、リポジトリクラスからだけでなくどこでも使用できるIQueryableの拡張メソッドを備えたクラスを使用したいと思います。

電話リポジトリではなくドメインオブジェクトのみを使用して電話エンティティを削除できるようにするための1つの注意点は、UserIdが電話の主キーの一部であること、つまり電話レコードの主キーが複合キーであることを確認する必要があることです。 PhoneエンティティのUserIdとその他のプロパティ(自動生成されたIDをお勧めします)で構成されています。電話レコードはユーザーレコードによって「所有」されており、ユーザーナビゲーションコレクションから削除すると、データベースから完全に削除されるため、これは直感的に理解できます。

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