web-dev-qa-db-ja.com

ルートを集約するためのリポジトリの削減

現在、データベース内のほぼすべてのテーブルのリポジトリがあり、ルートのみを集約するように減らすことで、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つを変更したと仮定すると、電話を更新するにはどうすればよいですか?私の限られた理解では、ルートの下のオブジェクトはルートを介して更新する必要があるということです。これはEntity Frameworkで完全にうまく機能しますが、Entity Frameworkがグラフ内の変更されたオブジェクトのタブを保持しているにもかかわらず、コードを読んで実際に何が更新されているかわからないため、これは非常にわかりにくいようです。

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

最後に、CountryCodesColorsCodesSomethingElseCodesなど、実際には何にも関連付けられていないルックアップテーブルがいくつかあると仮定します。私はそれらを使用して、ドロップダウンやその他の理由で値を入力することができます。これらのスタンドアロンリポジトリはありますか?それらをCodesRepositoryのようなある種の論理グループ/リポジトリに結合できますか?または、ベストプラクティスに反することです。

82
e36M3

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

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を検索すると、多くのニースの実装に罰金が科されます。それらのいずれかを使用するか、独自に記述します。

11
amit_g

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

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

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

9
Darren Lewis

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

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

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

これらはあなたが直面している問題だと思う:

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

最初の問題を解決する方法はわかりませんが、2番目の問題を解決することで、最初の問題を十分に解決できることに気づきました。私が行動中心にどういう意味かを理解するには、 この論文 を試してみてください。

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

5
Arnis Lapsa

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

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

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

    }
3
Chalky

Phoneエンティティが集約ルートユーザーと一緒にのみ意味をなす場合、新しいPhoneレコードを追加するための操作は、特定のメソッド(DDD動作)を通じてユーザードメインオブジェクトの責任であり、それによっていくつかの理由で完全に理にかなっています。緊急の理由は、Phoneエンティティがその存在に依存しているため、Userオブジェクトが存在することを確認し、トランザクションロックを保持しながら、他のプロセスがルートアグリゲートを削除していないことを確認するための検証チェックをさらに行うことです。操作の検証が完了しました。他の種類のルート集計で他のケースでは、値を集計または計算し、後で他の操作によるより効率的な処理のためにルート集計の列プロパティに保持したい場合があります。ユーザードメインオブジェクトには電話を追加するメソッドがあることをお勧めしますが、データベースまたはEFの存在について知っておくべきという意味ではありません。EMおよびHibernateの優れた機能の1つは、エンティティに加えられた変更を追跡できることです。クラスは透過的に、つまり、ナビゲーションコレクションプロパティによって新しい関連エンティティを追加することも意味します。

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

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

0
Kaveh Hadjari