リポジトリパターンを正しく使用するにはどうすればよいですか?


86

リポジトリをどのようにグループ化する必要があるのでしょうか。asp.net mvcや私の本で見た例のように、基本的にデータベーステーブルごとに1つのリポジトリを使用します。しかし、それは多くのリポジトリのように思われるため、後でモックなどのために多くのリポジトリを呼び出す必要があります。

だから私はそれらをグループ化する必要があると思います。ただし、それらをグループ化する方法がわかりません。

今、私はすべての登録を処理するための登録リポジトリを作成しました。ただし、更新する必要があるテーブルが4つあり、これを行うために3つのリポジトリがあります。

たとえば、テーブルの1つはライセンステーブルです。彼らが登録するとき、私は彼らの鍵を見て、データベースに存在するかどうかを確認します。登録以外の場所で、このライセンスキーまたはそのテーブルの何かを確認する必要がある場合はどうなりますか?

1つの場所はログインである可能性があります(キーの有効期限が切れていないかどうかを確認してください)。

それで、私はこの状況で何をしますか?コードを書き直します(DRYを破ります)?これらの2つのリポジトリを一緒にマージして、他の時点でメソッドが不要になることを期待してください(userNameが使用されているかどうかをチェックするメソッドがあるかもしれませんが、別の場所で必要になるかもしれません)。

また、それらをマージする場合、サイトの2つの異なる部分のすべてのロジックが長くなり、ValidateLogin()、ValdiateRegistrationForm()のような名前が必要になるため、同じリポジトリに2つのサービスレイヤーを配置する必要があります。 、ValdiateLoginRetrievePassword()など。

または、とにかくリポジトリを呼び出して、奇妙な名前を付けますか?

アプリケーションの多くの場所で使用できるように、十分に一般的な名前のリポジトリを作成するのは難しいようですが、それでも意味があります。リポジトリ内の別のリポジトリを呼び出すことは良い習慣ではないと思いますか?


11
+1。素晴らしい質問です。
griegs 2009

しばらくの間私を悩ませてきたおかげで。私が本で見たものは単純すぎると思うので、それらはこれらの状況で何をすべきかをあなたに示しません。
chobo2 2009

私は基本的にあなたと同じことをしています。複数のリポジトリで使用されているlinq2sqlクラスがあり、DRYを壊すテーブル構造を変更する必要がある場合はそうです。理想的とは言えません。私は今それをもう少し良く計画しているので、linq2sqlクラスを複数回使用する必要はありません。これは関心の分離として適切だと思いますが、これが私にとって本当の問題になる日が来ると予想しています。
griegs 2009

私はここに同様の質問をし(ただし、同一ではない):stackoverflow.com/questions/910156/...
Grokys

ええ、でもすべての状況でそれを計画するのは難しいようです。私が言ったように、私はおそらくログインと登録を認証にマージし、2つの別々のレイヤーを持つことができます。それはおそらく私の問題を解決するでしょう。しかし、私のサイトのプロフィールページで、なんらかの理由でキーを表示したい場合はどうなりますか(多分、htemに変更させます)。さて、DRYを壊して同じことを書くにはどうすればよいですか?または、これら3つのテーブルすべてに適切な名前を付けることができるリポジトリを作成してみてください。
chobo2 2009

回答:


41

リポジトリパターンをいじってみたときに間違ったことの1つは、あなたと同じように、テーブルはリポジトリ1:1に関連していると思いました。ドメイン駆動設計からいくつかのルールを適用すると、リポジトリのグループ化の問題がなくなることがよくあります。

リポジトリは、テーブルではなく、集約ルートごとに配置する必要があります。つまり、エンティティが単独で存在するべきではない場合(つまりRegistrant、特に参加してRegistrationいるエンティティがある場合)、それは単なるエンティティであり、リポジトリは必要ありません。集約ルートのリポジトリを介して更新/作成/取得する必要があります。所属しています。

もちろん、多くの場合、リポジトリの数を減らすこの手法(実際には、ドメインモデルを構造化する手法です)は適用できません。これは、すべてのエンティティが集約ルートであると想定されているためです(ドメインに大きく依存します。私は盲目的な推測のみを提供できます)。あなたの例Licenseでは、Registrationエンティティのコンテキストなしでそれらをチェックできる必要があるため、集約ルートのようです。

しかし、それは私たちをカスケードリポジトリに制限するものではありません(RegistrationリポジトリはLicense必要に応じてリポジトリを参照することができます)。これはLicenseRegistrationオブジェクトから直接リポジトリを参照する(IoCを介して)ことを制限するものではありません。

テクノロジーによってもたらされる複雑さや何かの誤解によって設計を推進しないようにしてください。ServiceX2つのリポジトリを構築したくないという理由だけでリポジトリをグループ化するのは良い考えではありません。

固有名詞を付ける方がはるかに良いでしょう-RegistrationServiceすなわち

しかし、サービスは一般的に避けるべきです-それらはしばしば貧血ドメインモデルにつながる原因です。

編集:
IoCの使用を開始してください。依存性を注入する手間を本当に軽減します。
書く代わりに:

var registrationService = new RegistrationService(new RegistrationRepository(),  
      new LicenseRepository(), new GodOnlyKnowsWhatElseThatServiceNeeds());

あなたは書くことができるでしょう:

var registrationService = IoC.Resolve<IRegistrationService>();

Psいわゆる共通サービスロケーターを使用する方が良いでしょうが、それは単なる例です。


17
ハハハ...サービスロケーターの使い方をアドバイスしています。私が過去にどれほど愚かであったかを見るのはいつも喜びです。
Arnis Lapsa 2010年

1
@Arnisあなたの意見が変わったように聞こえます-私はあなたが今この質問にどのように異なって答えるか興味がありますか?
ngm 2011

10
@ngmは、私がこれに答えてから大きく変わりました。集約ルートがトランザクション境界を描画する(全体として保存される)必要があることに同意しますが、リポジトリパターンを使用して永続性を抽象化することについては楽観的ではありません。最近-熱心な/遅延読み込み管理のようなものがあまりにも厄介なので、私はORMを直接使用しています。永続性の抽象化に焦点を合わせるのではなく、リッチドメインモデルの開発に焦点を合わせる方がはるかに有益です。
Arnis Lapsa 2011

2
@Developerいいえ、正確ではありません。彼らはまだ永続性を知らないはずです。外部から集約ルートを取得し、その上でジョブを実行するメソッドを呼び出します。私のドメインモデルには参照がなく、標準の.netフレームワークのものだけです。それを達成するには、十分にスマートな豊富なドメインモデルとツールが必要です(NHibernateがそのトリックを実行します)。
Arnis Lapsa

1
それどころか。多くの場合、複数の集計をフェッチすることは、PKまたはインデックスを使用できることを意味します。EFが生成する関係を利用するよりもはるかに高速です。ルートアグリゲートが小さいほど、パフォーマンスが向上します。
jgauffin 2013年

5

これに対処するために私が始めたことの1つは、N個のリポジトリをラップするサービスを実際に開発することです。うまくいけば、DIまたはIoCフレームワークがそれを簡単にするのに役立つでしょう。

public class ServiceImpl {
    public ServiceImpl(IRepo1 repo1, IRepo2 repo2...) { }
}

それは理にかなっていますか?また、このマナーでのサービスについて言えば、実際にはDDDの原則に準拠している場合と準拠していない場合があることを理解しています。


1
いいえ、あまり意味がありません。現時点では、DIまたはIoCフレームワークを使用していません。これは、プレートに十分な量があるためです。
chobo2 2009

1
new()だけでリポジトリをインスタンス化できる場合は、これを試すことができます... public ServiceImpl():this(new Repo1、new Repo2 ...){}サービスの追加コンストラクターとして。
neouser99 2009

依存性注入?私はすでにそれを行っていますが、あなたのコードがドンであり、それが何を解決するのかまだわかりません。
chobo2 2009

具体的には、リポジトリのマージを行っています。どのコードが意味をなさないのですか?DIを使用している場合、回答のコードは、DIフレームワークがそれらのIRepoサービスを注入するという意味で機能します。コメントコードでは、基本的にDIを実行するための小さな回避策です(基本的に、パラメーターなしのコンストラクターは「注入」しますServiceImplへのこれらの依存関係)。
neouser99 2009

私はすでにDIを使用しているので、ユニットテストをより適切に行うことができます。私の問題は、サービスレイヤーを作成し、名前の詳細にリポジトリを作成する場合です。次に、他の場所でそれらを使用する必要がある場合は、他のクラスのRegRepoを呼び出すRegistrationServiceレイヤーのように奇妙な呼び出しに見えます。だから私はあなたの例からあなたが完全に何をしているのか見ていません。たとえば、同じサービスレイヤーにリポジトリが多すぎると、ビジネスロジックと検証ロジックが大きく異なります。サービスレイヤーでは、通常、検証ロジックを配置します。だから、多くのIの必要性より...
chobo2

2

私がしているのは、次のように定義された抽象基本クラスがあることです。

public abstract class ReadOnlyRepository<T,V>
{
     V Find(T lookupKey);
}

public abstract class InsertRepository<T>
{
     void Add(T entityToSave);
}

public abstract class UpdateRepository<T,V>
{
     V Update(T entityToUpdate);
}

public abstract class DeleteRepository<T>
{
     void Delete(T entityToDelete);
}

次に、抽象基本クラスからリポジトリを派生させ、たとえば一般的な引数が異なる限り、単一のリポジトリを拡張できます。

public class RegistrationRepository: ReadOnlyRepository<int, IRegistrationItem>,
                                     ReadOnlyRepository<string, IRegistrationItem> 

等....

一部のリポジトリには制限があり、これにより最大限の柔軟性が得られるため、個別のリポジトリが必要です。お役に立てれば。


では、これをすべて処理するための汎用リポジトリを作成しようとしていますか?
chobo2 2009

つまり、実際にはUpdateメソッドと言えます。このVアップデートが好きで、TエンティティをUpdateに渡すが、実際にアップデートするコードがない、またはあるのと同じように。
chobo2 2009

はい..汎用リポジトリから派生するクラスを作成するため、updateメソッドにコードが含まれます。実装コードは、Linq2SQLまたはADO.NET、あるいはデータアクセス実装テクノロジとして選択したものであれば何でもかまいません
Michael Mann

2

私はこれをリポジトリクラスとして持っており、そうです、テーブル/エリアリポジトリで拡張しますが、それでも時々DRYを壊さなければなりません。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MvcRepository
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected System.Data.Linq.DataContext _dataContextFactory;

        public IQueryable<T> All()
        {
            return GetTable.AsQueryable();
        }

        public IQueryable<T> FindAll(Func<T, bool> exp)
        {
            return GetTable.Where<T>(exp).AsQueryable();
        }

        public T Single(Func<T, bool> exp)
        {
            return GetTable.Single(exp);
        }

        public virtual void MarkForDeletion(T entity)
        {
            _dataContextFactory.GetTable<T>().DeleteOnSubmit(entity);
        }

        public virtual T CreateInstance()
        {
            T entity = Activator.CreateInstance<T>();
            GetTable.InsertOnSubmit(entity);
            return entity;
        }

        public void SaveAll()
        {
            _dataContextFactory.SubmitChanges();
        }

        public Repository(System.Data.Linq.DataContext dataContextFactory)
        {
            _dataContextFactory = dataContextFactory;
        }

        public System.Data.Linq.Table<T> GetTable
        {
            get { return _dataContextFactory.GetTable<T>(); }
        }

    }
}

編集

public class AdminRepository<T> : Repository<T> where T: class
{
    static AdminDataContext dc = new AdminDataContext(System.Configuration.ConfigurationManager.ConnectionStrings["MY_ConnectionString"].ConnectionString);

    public AdminRepository()
        : base( dc )
    {
    }

Linq2SQL.dbmlクラスを使用して作成されたデータコンテキストもあります。

これで、AllやFindなどの標準呼び出しを実装する標準リポジトリができ、AdminRepositoryに特定の呼び出しがあります。

とは思いませんが、DRYの質問には答えません。


何のための「リポジトリ」?そしてCreateInstanceのように?あなたが持っているすべてが何をしているのかわからない。
chobo2 2009

それは何としても一般的です。基本的に、(エリア)用の特定のリポジトリが必要です。AdminRepositoryについては、上記の編集を確認してください。
griegs 2009

1

これは、 FluentNHibernateを使用した一般的なリポジトリ実装のです。マッパーを作成したクラスを永続化することができます。マッパークラスに基づいてデータベースを生成することもできます。


1

リポジトリパターンは悪いデザインパターンです。私は多くの古い.Netプロジェクトで作業していますが、このパターンは通常、「分散トランザクション」、「部分ロールバック」、および「接続プールの枯渇」エラーを引き起こしますが、これは回避できます。問題は、パターンが接続とトランザクションを内部で処理しようとしますが、それらはコントローラー層で処理する必要があることです。また、EntityFrameworkはすでに多くのロジックを抽象化しています。共有コードを再利用するには、代わりにサービスパターンを使用することをお勧めします。


0

私はあなたが見てすることをお勧めシャープアーキテクチャ。彼らは、エンティティごとに1つのリポジトリを使用することを提案しています。私は現在私のプロジェクトでそれを使用していて、結果に満足しています。


ここで+1を付けますが、彼はDIまたはIoCコンテナーは完全なオプションではないことを示しています(Sharp Archの利点はこれらだけではありません)。彼が回避している既存のコードのいくつかから多くがあると思います。
neouser99 2009

エンティティと見なされるものは何ですか?それはデータベース全体ですか?それともデータベーステーブルですか?DIまたはIoCコンテナーは、私が同時に学習している他の10のことの上にそれを学びたくないので、現時点ではオプションではありません。これらは、私のサイトの次のリビジョンまたは次のプロジェクトを調査するものです。これがこれになるかどうかはわかりませんが、サイトをざっと見てみると、nhirbrateを使用してほしいと思われ、現時点ではlinq tosqlを使用しています。
chobo2 2009
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.