ジェネリックの考え方に関するいくつかの非常に明白な問題IRepository<T>
:
すべてのエンティティが同じタイプのキーを使用することを前提としていますが、これはほとんどの重要なシステムでは当てはまりません。GUID を使用するエンティティもあれば、ある種の自然キーや複合キーを持つエンティティもあります。NHibernate はこれをかなりうまくサポートできますが、Linq to SQL はそれがかなり苦手です。自動キー マッピングを行うには、大量のハック コードを書かなければなりません。
これは、各リポジトリが 1 つのエンティティ タイプのみを処理でき、最も単純な操作のみをサポートすることを意味します。リポジトリがこのような単純な CRUD ラッパーに追いやられると、ほとんど役に立たなくなります。IQueryable<T>
クライアントにまたはを渡すだけでもかまいませんTable<T>
。
すべてのエンティティに対してまったく同じ操作を実行することを前提としています。実際には、これは真実とはかけ離れたものになるでしょう。確かに、 ID で取得したいかもしれませんが、特定の顧客の特定の日付範囲内Order
のオブジェクトのリストを取得したい可能性が高くなります。Order
完全にジェネリックという概念では、IRepository<T>
さまざまな種類のエンティティに対してさまざまな種類のクエリをほぼ確実に実行したいという事実が考慮されていません。
リポジトリ パターンの要点は、一般的なデータ アクセス パターンを抽象化することです。一部のプログラマーは、リポジトリの作成に飽きて、「よし、どんなエンティティ タイプでも扱える uber-repository を 1 つ作成してみよう!」と言っていると思います。これは素晴らしいことですが、あなたがやろうとしていることの 80% に対してリポジトリがほとんど役に立たないことを除けば. 基本クラス/インターフェースとしては問題ありませんが、それがあなたが行う作業の全範囲である場合は、単に怠けているだけです (そして、将来の頭痛の種を保証します)。
理想的には、次のような汎用リポジトリから始めることができます。
public interface IRepository<TKey, TEntity>
{
TEntity Get(TKey id);
void Save(TEntity entity);
}
これにはor関数がないことに気付くでしょう。これは、コードのどこからでもテーブル全体からデータを一度に取得できると考えるのはばかげているからです。これは、特定のリポジトリへのアクセスを開始する必要がある場合です。List
GetAll
public interface IOrderRepository : IRepository<int, Order>
{
IEnumerable<Order> GetOrdersByCustomer(Guid customerID);
IPager<Order> GetOrdersByDate(DateTime fromDate, DateTime toDate);
IPager<Order> GetOrdersByProduct(int productID);
}
など - あなたはアイデアを得る。このようにして、信じられないほど単純化されたIDによる取得セマンティクスが実際に必要になった場合に備えて、「汎用」リポジトリがありますが、一般的には、コントローラークラスに渡すことはありません。
コントローラーに関しては、これを正しく行う必要があります。そうしないと、すべてのリポジトリをまとめるために行ったすべての作業をほとんど無効にしてしまいます。
コントローラは外部からリポジトリを取得する必要があります。これらのリポジトリを作成した理由は、ある種の制御の反転を実行できるようにするためです。ここでの最終的な目標は、あるリポジトリを別のリポジトリに交換できるようにすることです。たとえば、単体テストを実行したり、将来のある時点で Linq から SQL、Entity Framework に切り替えることにした場合などです。
この原則の例は次のとおりです。
public class OrderController : Controller
{
public OrderController(IOrderRepository orderRepository)
{
if (orderRepository == null)
throw new ArgumentNullException("orderRepository");
this.OrderRepository = orderRepository;
}
public ActionResult List(DateTime fromDate, DateTime toDate) { ... }
// More actions
public IOrderRepository OrderRepository { get; set; }
}
言い換えれば、コントローラはリポジトリを作成する方法を知りませんし、そうすべきでもありません。そこでリポジトリの構築が行われている場合、それは本当に望ましくない結合を作成しています。ASP.NET MVC サンプル コントローラーに具体的なリポジトリを作成するパラメーターなしのコンストラクターがある理由は、依存関係の挿入フレームワーク全体をセットアップすることを強制することなく、サイトをコンパイルおよび実行できる必要があるためです。
しかし、実稼働サイトでは、コンストラクターまたはパブリック プロパティを介してリポジトリの依存関係を渡していない場合、コントローラーがデータベース レイヤーに緊密に結合されているため、リポジトリを持つ時間をまったく無駄にしていることになります。次のようなテスト コードを記述できる必要があります。
[TestMethod]
public void Can_add_order()
{
OrderController controller = new OrderController();
FakeOrderRepository fakeRepository = new FakeOrderRepository();
controller.OrderRepository = fakeRepository; //<-- Important!
controller.SubmitOrder(...);
Assert.That(fakeRepository.ContainsOrder(...));
}
OrderController
オフになって独自のリポジトリを作成している場合、これを行うことはできません。このテスト メソッドは、データ アクセスを行うことを想定しておらず、コントローラーがアクションに基づいて正しいリポジトリ メソッドを呼び出していることを確認するだけです。
これはまだ DI ではありません。気を付けてください。これはただの偽物/嘲笑です。DI が登場するのは、Linq to SQL では十分ではなく、本当に NHibernate で HQL が必要であると判断したときですが、すべてを移植するには 3 か月かかり、できるようにしたい場合です。一度に 1 つのリポジトリを実行します。したがって、たとえばNinjectのような DI フレームワークを使用する場合、これを変更するだけで済みます。
Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<LinqToSqlOrderRepository>();
Bind<IProductRepository>().To<LinqToSqlProductRepository>();
に:
Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<NHibernateOrderRepository>();
Bind<IProductRepository>().To<NHibernateProductRepository>();
これで、依存するものはすべてNHibernate バージョンを使用するようになりました。数百行になる可能性があるコードを1 行IOrderRepository
変更するだけで済みました。そして、Linq to SQL バージョンと NHibernate バージョンを並行して実行し、途中で何も壊すことなく機能を少しずつ移植しています。
したがって、私が行ったすべてのポイントを要約すると、次のようになります。
IRepository<T>
汎用インターフェイスに厳密に依存しないでください。リポジトリに必要な機能のほとんどは固有のものであり、一般的なものではありません。クラス/インターフェイス階層の上位レベルにを含めたい場合は問題ありませんが、コントローラーは特定のIRepository<T>
リポジトリに依存する必要があるため、汎用リポジトリが見つかったときに 5 つの異なる場所でコードを変更する必要はありません。重要なメソッドがありません。
コントローラは、独自のリポジトリを作成するのではなく、外部からリポジトリを受け入れる必要があります。これは、結合を排除し、テスト容易性を向上させるための重要なステップです。
通常、依存関係の挿入フレームワークを使用してコントローラーを接続する必要があり、それらの多くは ASP.NET MVC とシームレスに統合できます。それが多すぎて理解できない場合は、少なくとも何らかの静的サービス プロバイダーを使用して、すべてのリポジトリ作成ロジックを一元化できるようにする必要があります。(長期的には、DI フレームワークを学習して使用する方が簡単であることがわかるでしょう)。