ORMロジックをカプセル化するためのリポジトリパターンの代替手段


24

ORMを切り替える必要がありましたが、クエリロジックが至る所でリークしていたため、それは比較的困難な作業でした。新しいアプリケーションを開発する必要があった場合、個人的な好みは、すべてのクエリロジックを(ORMを使用して)カプセル化して、変更に対する将来性を保証することです。リポジトリパターンはコーディングと保守が非常に面倒なので、問題を解決する他のパターンがあるかどうか疑問に思っていましたか?

実際に必要になる前に余分な複雑さを追加しないこと、アジャイルであることなどについての投稿を予見できますが、私は既存のパターンが同様の問題をより簡単な方法で解決することにのみ興味があります。

私が最初に考えたのは、必要に応じて拡張メソッドを介して特定のタイプリポジトリクラスにメソッドを追加する汎用タイプリポジトリを使用することでしたが、静的メソッドのユニットテストは非常に苦痛です。IE:

public static class PersonExtensions
{
    public static IEnumerable<Person> GetRetiredPeople(this IRepository<Person> personRep)
    {
        // logic
    }
}

5
リポジトリパターンに関して、コーディングと保守が面倒だと感じるのは正確に何ですか?
マシューフリン

1
他にも選択肢があります。その1つは、クエリオブジェクトパターンを使用することです。これは、使用していませんが、良いアプローチのように思えます。リポジトリIMHOは、正しく使用されていても良いパターンではありますが、すべてではなくすべてを終了します
-dreza

回答:


14

まず第一に:汎用リポジトリは、完全な実装ではなく、基本クラスであると見なされるべきです。一般的なCRUDメソッドを適切に取得するのに役立ちます。ただし、クエリメソッドを実装する必要があります。

public class UserRepository : EntityFrameworkRepository<User>, IUserRepository
{
    public UserRepository(DbContext context){}

    public ICollection<Person> FindRetired()
    {
        return context.Persons.Where(p => p.Status == "Retired").ToList();
    }
}

第二:

戻らないでくださいIQueryable。それは漏れやすい抽象化です。そのインターフェイスを自分で実装するか、基礎となるdbプロバイダーに関する知識なしで使用してみてください。たとえば、すべてのdbプロバイダーには、エンティティを積極的にロードするための独自のAPIがあります。それが漏れやすい抽象化の理由です。

代替案

別の方法として、クエリを使用できます(たとえば、コマンド/クエリ分離パターンで定義されている)。CQSはウィキペディアで説明されています。

基本的に、呼び出すクエリクラスを作成します。

public class GetMyMessages
{
    public GetMyMessages(IDbConnection connection)
    {
    }


    public Message[] Execute(DateTime minDate)
    {
        //query
    }
}

クエリの素晴らしい点は、コードを乱雑にすることなく、異なるクエリに対して異なるデータアクセス方法を使用できることです。たとえば、あるサービスではWebサービスを使用し、別のサービスではnhibernateを使用し、別のサービスではADO.NETを使用できます。

.NETの実装に興味がある場合は、次の記事を読んでください:http : //blog.gauffin.org/2012/10/griffin-decoupled-the-queries/


代替パターンは、リポジトリパターンのメソッドがクラス全体になっていることを考慮して、より大きなプロジェクトでさらに混乱を招くことを提案しませんか?
ダンテ

3
少人数のクラスを持つという定義が混乱している場合は、そうです。その場合、SOLID原則を適用しようとすると、常により多くの(より小さな)クラスが生成されるため、従わないでください。
jgauffin

2
小さいクラスの方が優れていますが、既存のクエリクラスのナビゲーションは、(ドメイン)リポジトリのインテリセンスを参照して、必要な機能がそこにあるかどうかを確認するよりも面倒ではありません。
ダンテ

4
プロジェクト構造がより重要になります。すべてのクエリを名前空間 YourProject.YourDomainModelName.Queriesに配置すると、ナビゲートしやすくなります。
-jgauffin

CQSでうまくやることが難しいと思うことの1つは、一般的なクエリです。たとえば、ユーザーが指定されると、1つのクエリがすべての関連する学生(成績の違い、自分の子供など)を取得します。今度は、別のクエリでユーザーの子を取得してから操作を行う必要があります。そのため、クエリ2はクエリ1の一般的なロジックを活用できますが、簡単な方法はありません。これを容易にするCQS実装を見つけることができませんでした。この点でリポジトリは非常に役立ちます。どちらのパターンのファンでもないが、私の意見を共有するだけです。
Mrchief

8

まず、ORMはデータベースを十分に抽象化したものだと思います。一部のORMは、すべての一般的なリレーショナルデータベースへのバインディングを提供します(NHibernateにはMSSQL、Oracle、MySQL、Postgressなどへのバインディングがあります)。また、このORMを「抽象化」する必要があるという主張は無意味です。

それでもこの抽象化を構築したい場合は、リポジトリパターンに反対します。純粋なSQLの時代には素晴らしかったが、現代のORMに直面すると非常に面倒だ。主に、CRUD操作やクエリなどのORM機能のほとんどを再実装することになります。

そのような抽象化を構築する場合、これらのルール/パターンを使用します

  • CQRS
  • 既存のORM機能を可能な限り使用する
  • 複雑なロジックとクエリのみをカプセル化する
  • アーキテクチャ内にORMの「配管」を隠してみてください

具体的な実装では、ORMのCRUD操作をラップせずに直接使用します。また、コードで直接簡単なクエリを実行します。ただし、複雑なクエリは独自のオブジェクトにカプセル化されます。ORMを「非表示」にするには、データコンテキストを透過的にサービス/ UIオブジェクトに注入し、クエリオブジェクトに対して同じことを行います。

最後に言いたいのは、多くの人がORMを使用する方法と、それを最大限に活用する方法を知らずにORMを使用しているということです。リポジトリを推奨する人は通常この種類です。

推奨読書として、私はAyendeのブログ、特にこの記事を言うでしょう。


+1利益「私が言いたい最後のことは、多くの人がそれを使用する方法を知り、どのように最善を作るためにすることなく、ORMを使用することである『』それからリポジトリを推奨する人々は、この種の通常です。」
Misters

1

漏れやすい実装の問題は、多くの異なるフィルター条件が必要なことです。

リポジトリメソッドFindByExampleを実装し、次のように使用すると、このAPIを絞り込むことができます

// find all retired persons
Person filter = new Person {Status=PersonStatus.Retired};
IEnumerable<Person> found = personRepository.FindByExample(filter);


// find all persons who have a dog named "sam"
Person filter = new Person();
filter.AddPet(new Dog{Name="sam"});
IEnumerable<Person> found = personRepository.FindByExample(filter);

0

拡張機能をIRepositoryではなくIQueryableで動作させることを検討してください。

public static class PersonExtensions
{
    public static IQueryable<Person> AreRetired(this IQueryable<Person> people)
    {
        return people.Where(p => p.Status == "Retired");
    }
}

単体テストするには:

List<Person> people = new List<Person>();
people.Add(new Person() { Name = "Bob", Status = "Retired" });
people.Add(new Person() { Name = "Sam", Status = "Working" });

var retiredPeople = people.AsQueryable().AreRetired();
// assert that Bob is in the list
// assert that Sam is not in the list

テストに必要なancing笑のモックはありません。これらの拡張メソッドを組み合わせて、必要に応じてより複雑なクエリを作成することもできます。


5
-1 a)IQueryable悪い母親、またはむしろGODインターフェース。それを実装することは非常に多く、完全なLINQ to Sqlプロバイダーは(もしあれば)非常に少数です。b)拡張方法は、拡張(基本的なOOP原則の1つ)を不可能にします。
jgauffin

0

ここでは、NHibernateのかなりきれいなクエリオブジェクトパターンを記述しました:https : //github.com/shaynevanasperen/NHibernate.Sessions.Operations

次のようなインターフェイスを使用して機能します。

public interface IDatabases
{
    ISessionManager SessionManager { get; }

    T Query<T>(IDatabaseQuery<T> query);
    T Query<T>(ICachedDatabaseQuery<T> query);

    void Command(IDatabaseCommand command);
    T Command<T>(IDatabaseCommand<T> command);
}

次のようなPOCOエンティティクラスが与えられた場合:

class Database1Poco
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
}

次のようなクエリオブジェクトを作成できます。

class Database1PocoByProperty1 : DatabaseQuery<Database1Poco>
{
    public override Database1Poco Execute(ISessionManager sessionManager)
    {
        return sessionManager.Session.Query<Database1Poco>().SingleOrDefault(x => x.Property1 == Property1);
    }

    public int Property1 { get; set; }
}

そして、次のように使用します:

var database1Poco = _databases.Query(new Database1PocoByProperty1 { Property1 = 1 });

1
このリンクは質問に回答するかもしれませんが、回答の重要な部分をここに含め、参照用のリンクを提供する方が良いでしょう。リンクされたページが変更されると、リンクのみの回答が無効になる可能性があります。
ダンピチェルマン

申し訳ありませんが、急いでいた。回答が更新され、いくつかのサンプルコードが表示されます。
シェーン

1
より多くのコンテンツで投稿を改善するための+1。
松o
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.