リポジトリ/サービスレイヤー設計パターンを備えたアドバイス
-
01-10-2019 - |
質問
ここで本当にシンプルなリポジトリとサービスレイヤーパターンを作成しようとしています。 (.NET 4、C#、LINQ、この質問は部分的に言語に依存していません)。注:これは単なるR&Dです。
私の目標は、サービスレイヤーのメソッド定義の量を最小限に抑えることです。
これが私のリポジトリ契約です:
interface IFooRepository
{
IEnumerable<Foo> Find();
void Insert(Foo foo);
void Update(Foo foo);
void Delete(Foo foo);
}
そこには新しいものはありません。
さて、これが私のサービス契約で持っていようとしているものです:
interface IFooDataService
{
public IEnumerable<Foo> Find(FooSearchArgs searchArgs);
}
基本的に、特定の「Foo」には多くのプロパティ(ID、名前など)があり、検索できるようにしたいと思います。
だから、私は異なるプロパティごとに1倍の検索方法を持ちたくない、私はただそれを望んでいる - そうすれば、私は契約を変更する必要はない追加のプロパティを作成するとき。
「FoosearchArgs」は、すべての異なる「Foo」プロパティを備えた単純なPocoです。
だから、それが私がやろうとしていることです、ここに私の質問があります:
- これは貧弱なデザインですか?もしそうなら、代替手段は何ですか?
- このフィルタリングをサービスレイヤーに実装するにはどうすればよいですか? 「FoosearchArgs」のどのプロパティが設定されているかを確認してから、フィルタリングを続けなければなりませんか? (もしそうなら、Query。どこで、これ、Query.whereなど)誰かがこれを行うための巧妙なlinq ienumerable拡張方法のアイデアを持っていますか? (すなわち
repository.WhereMeetsSearchCriteria(fooSearchArgs)
)
助けに感謝します。
解決
非常によく似たものを使用しています。あなたが決定する必要があることの1つは、リポジトリの外部でiQueryableを公開するかどうかです。 FINDメソッドは、WHIN句から返されるiQueryableである可能性があるIENUMERABLEを返します。
iQueryableを返すことの利点は、リポジトリレイヤーの外側で基準をさらに改善できることです。
repository.Find(predicate).Where(x => x.SomeValue == 1);
式は、返されたデータを使用するようになったときにのみコンパイルされ、ここでは不利な点があります。実際に結果を使用するようになったときにのみデータベースにヒットするため、セッション(nhibernate)または接続が閉じられた後にデータベースを呼び出そうとする可能性があります。
私の個人的な好みは、検索方法を渡す仕様パターンを使用することです。
public interface ISpecification<TCandidate>
{
IQueryable<TCandidate> GetSatisfyingElements(IQueryable<TCandidate> source);
}
public class TestSpecification : ISpecification<TestEntity>
{
public IQueryable<TestEntity> GetSatisfyingElements(IQueryable<TestEntity> source)
{
return source.Where(x => x.SomeValue == 2);
}
}
public class ActiveRecordFooRepository: IFooRepository
{
...
public IEnumerable<TEntity> Find<TEntity>(ISpecification<TEntity> specification) where TEntity : class
{
...
return specification.GetSatisfyingElements(ActiveRecordLinq.AsQueryable<TEntity>()).ToArray();
...
}
public TEntity FindFirst<TEntity>(ISpecification<TEntity> specification) where TEntity : class
{
return specification.GetSatisfyingElements(ActiveRecordLinq.AsQueryable<TEntity>()).First();
}
}
クエリが実行された後、リポジトリコールは、仕様から返されたiQueryableが返され、クエリがそこに評価され、その後に評価されます。これはiQueryableを公開するよりも柔軟性が低いように見えるかもしれませんが、いくつかの利点があります。
- クエリはすぐに実行され、セッションが終了した後に行われているデータベースへの呼び出しを防ぎます。
- クエリが仕様にバンドルされるため、単体テスト可能になりました。
- 仕様は再利用可能です。つまり、同様のクエリを実行しようとする場合、コードの重複がないことを意味し、クエリ内のバグは1つの場所で修正する必要があります。
- 適切な種類の実装を使用すると、仕様を一緒にチェーンすることもできます。
repository.Find(
firstSpecification
.And(secondSpecification)
.Or(thirdSpecification)
.OrderBy(orderBySpecification));
他のヒント
渡しています func FoosearchArgsの代わりに、サービスレイヤーの検索方法のパラメーターとして、オプションですか? Enumerablesには、funcをパラメーターとして使用する方法(linq)があるため、結果をフィルタリングするために使用できます。