web-dev-qa-db-ja.com

リポジトリパターンが最新のORM(EF、nHibernate)に対して過剰である場合、より優れた抽象化とは何ですか?

私は最近、Entity Frameworkのような強力なORMでリポジトリパターンを使用することに対する多くの議論を読みました。

単体テストのような状況でパターンを使用することに対するもう1つの論点は、より一般的な実装ではIQueryableを利用するため、リポジトリパターンは漏洩しやすい抽象化であるということです。

リポジトリパターンの使用に反対する議論は私には理にかなっていますが、推奨される抽象化の代替方法は、しばしばより混乱しやすく、問題と同じくらいやり過ぎに見えます。

Jimmy Bogardsのソリューションは、抽象化を吹き飛ばすだけでなく、彼自身のアーキテクチャも導入することの組み合わせのようです。 https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

リポジトリが不必要にある別の例....しかし、私のアーキテクチャを使用してください! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

別の... http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework

それ自体が設計されていない「過度に複雑な」リポジトリパターンアプローチの明確な代替または代替案を見つけられませんでした。

12

リポジトリと汎用リポジトリを融合していると思います。

基本的なリポジトリは、データストアをインターフェースし、データを返すメソッドを提供します

IRepository {
   List<Data> GetDataById(string id);
}

IQueryableやその他のランダムクエリを渡す方法を介してコードにデータレイヤーをリークせず、明確に定義されたテスト可能で注入可能なメソッドの表面を提供します。

Generic Repositoryを使用すると、ORMのようにクエリを渡すことができます

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
    //or
    IQueryable<T> Get<T>();
}

基本的に単なる別の汎用リポジトリーであるORMの上で汎用リポジトリーを使用する意味があまりないことに同意します。

答えは、基本的なリポジトリパターンを使用してORMを非表示にすることです

12
Ewan

あなたが誤って言及する引数のほとんどは、それが持っていないリポジトリパターン機能に起因します。

概念的には、DDDで最初に定義されたリポジトリは、検索または追加できるオブジェクトのコレクションにすぎません。その背後にある永続化メカニズムは抽象化されているため、コンシューマとしては、それがメモリ内コレクションであるという錯覚を覚えます。

リークの多い抽象化(たとえば、IQueryablesを公開する)を持つリポジトリ実装は、貧弱なリポジトリ実装です。

単なるコレクション操作(作業ユニット機能など)を公開するリポジトリ実装は、貧弱なリポジトリ実装です。

データアクセスのリポジトリに代わるものはありますか?はい。ただし、質問で取り上げた問題とは関係ありません。

6
guillaume31

Repository-interfaceの目的がnittest(=単独でのテスト)のためにデータベースを模擬することである場合、最適な抽象化は模倣しやすいものです。

IQueryableの結果に基づくリポジトリインターフェースをモックするのは困難です。

ユニットテストの観点から

IRepository {
   List<Data> GetDataById(string id);
}

簡単にあざけることができます

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
}

モックがクエリパラメータのコンテンツを無視する場合にのみ、簡単にモックできます。

IGenericRepository<T> {
    IQueryable<T> Get<T>(some_parameters);
}

簡単にあざけることはできません

1
k3b

私にとって、リポジトリは、ORMまたは他のDB永続化レイヤーと組み合わせて、次の欠点があります。

  1. 作業単位のカバー。 UoWはプログラマーがコーディングする必要があり、バックグラウンドで一種の魔法として実装されることはめったにありません。ユーザーは、UoWの境界やコミットポイントを定義せずに、クエリや変更を行うだけです。時々、UoWは、各リポジトリアクセス方法でそれらをマイクロUoW(例:NHibernateセッション)に減らすことによって放棄されます。
  2. 永続性の無視を覆い隠す、または最悪の場合は破棄する:「Load()」、「Get()」、「Save()」、または「Update()」などのメソッドは、個別のオブジェクトを送信するかのように、即時の単一オブジェクト操作を提案しますSQL/DML、またはファイルを操作しているかのよう。実際、たとえば、これらの誤解を招くような名前を持つNHibernateメソッドは、通常、個別のアクセスを行いませんが、遅延ロードまたは挿入/更新バッチ(永続性無視)をエンキューします。プログラマーは、DBの操作がすぐに行われず、永続性の無知が強制的に解消されない理由を不思議に思うことがある。
  3. 無秩序な成長。単純なリポジトリーは、特定のニーズに合うようにますます多くのメソッドを蓄積する可能性があります。

といった:

public interface ICarsRepository  /* initial */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); // bad, should be for multiple IDs.
    void SaveCar(ICar carToSave); // bad, no individual saves, use UoW commit!
}

public interface ICarsRepository  /* a few years later */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); 
    IList<ICar> GetBlueCars();
    IList<ICar> GetRedYellowGreenCars();
    IList<ICar> GetCarsByColor(Color colorOfCars); // a bit better
    IList<ICar> GetCarsByColor(IEnumerable<Color> colorsOfCars); // better!
    IList<ICar> GetCarsWithPowerBetween(int hpFrom, int hpTo);
    IList<ICar> GetCarsWithPowerKwBetween(int kwFrom, int kwTo);
    IList<ICar> GetCarsBuiltBetween(int yearFrom, int yearTo);
    IList<ICar> GetCarsBuiltBetween(DateTime from, DateTime to); // some also need month and day
    IList<ICar> GetHybridCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetElectricCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetCarsFromManufacturer(IManufacturer carManufacturer); 
    bool HasCarMeanwhileBeenChangedBySomebodyElseInDb(ICar car); // persistence ignorance broken
    void SaveCar(ICar carToSave);
}

4.神の危険オブジェクト:モデルまたはデータアクセス層のすべてをカバーする1つの神クラスを作成したくなるかもしれません。リポジトリクラスには、Carメソッドだけでなく、すべてのエンティティのメソッドが含まれます。

私の意見では、多くの単一目的メソッドの巨大な混乱を避けるために、少なくともいくつかのクエリの機会を提供する方が良いです。 LINQ、独自のクエリ言語、またはORMから直接取得したもの(OK、一種の結合の問題など)であっても関係ありません。

1
Erik Hart

クエリにラムダ関数を使用する場合、リポジトリパターンは過剰だとは思いません。特にORMを抽象化する必要がある場合(私の意見では常にそうするべきです)、リポジトリ自体の実装の詳細は気にしません。

例えば:

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        UserRepository ur = new UserRepository();
        var userWithA = ur.GetBy(u => u.Name.StartsWith("A"));

        Console.WriteLine(userWithA.Name);


        ur.GetAllBy(u => u.Name.StartsWith("M"))
          .ForEach(u => Console.WriteLine(u.Name));


        ur.GetAllBy(u => u.Age > 13)
          .ForEach(u => Console.WriteLine(u.Name));
    }
}

public class UserRepository 
{
    List<User> users = new List<User> { 
        new User{Name="Joe", Age=10},
            new User{Name="Allen", Age=12},
            new User{Name="Martin", Age=14},
            new User{Name="Mary", Age=15},
            new User{Name="Ashton", Age=29}
    };

    public User GetBy(Predicate<User> userPredicate)
    {
        return users.Find(userPredicate);
    }

    public List<User> GetAllBy(Predicate<User> userPredicate)
    {
        return users.FindAll(userPredicate);
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
0
dhalsim