#2 と #3 のブレンドを使用しましたが、可能であれば厳密な汎用リポジトリを好みます (#3 のリンクで提案されているよりも厳密です)。#1 は、単体テストでうまく機能しないため、役に立ちません。
ドメインが小さい場合、またはドメインでクエリを許可するエンティティを制限する必要がある場合は、汎用リポジトリを実装するエンティティ固有のリポジトリ インターフェイスを定義する #2 または #3 が理にかなっていると思います。ただし、クエリを実行するすべてのエンティティに対してインターフェイスと具体的な実装を作成するのは面倒で不要であることがわかりました。public interface IFooRepository : IRepository<Foo>
(開発者を許可された集約ルートのセットに制限する必要がない限り)何が良いのでしょうか?
Add
、Remove
、Get
、GetDeferred
、Count
、およびFind
メソッド (Find はIQueryable
LINQ を許可するインターフェイスを返します) を使用して汎用リポジトリ インターフェイスを定義し、具体的な汎用実装を作成して、1 日で終了します。Find
私はLINQに大きく依存しています。特定のクエリを複数回使用する必要がある場合は、拡張メソッドを使用し、LINQ を使用してクエリを記述します。
これは、永続化のニーズの 95% をカバーします。一般的に実行できないある種の永続化アクションを実行する必要がある場合は、自家製のICommand
API を使用します。たとえば、NHibernate を使用していて、ドメインの一部として複雑なクエリを実行する必要がある、または一括コマンドを実行する必要があるとします。API はおおよそ次のようになります。
// marker interface, mainly used as a generic constraint
public interface ICommand
{
}
// commands that return no result, or a non-query
public interface ICommandNoResult : ICommand
{
void Execute();
}
// commands that return a result, either a scalar value or record set
public interface ICommandWithResult<TResult> : ICommand
{
TResult Execute();
}
// a query command that executes a record set and returns the resulting entities as an enumeration.
public interface IQuery<TEntity> : ICommandWithResult<IEnumerable<TEntity>>
{
int Count();
}
// used to create commands at runtime, looking up registered commands in an IoC container or service locator
public interface ICommandFactory
{
TCommand Create<TCommand>() where TCommand : ICommand;
}
これで、特定のコマンドを表すインターフェイスを作成できます。
public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance>
{
Decimal MinimumBalance { get; set; }
}
具体的な実装を作成し、未加工の SQL、NHibernate HQL などを使用して、それをサービス ロケーターに登録できます。
今、私のビジネスロジックでは、次のようなことができます:
var query = factory.Create<IAccountsWithBalanceQuery>();
query.MinimumBalance = 100.0;
var overdueAccounts = query.Execute();
また、何百万もの紛らわしいプロパティを備えたインターフェイスを使用するのではなく、仕様パターンを使用して、IQuery
意味のあるユーザー入力駆動型のクエリを構築することもできますが、それは、仕様パターン自体が混乱を招くとは思わないことを前提としています;)。
パズルの最後のピースは、リポジトリが特定の前後のリポジトリ操作を実行する必要がある場合です。これで、特定のエンティティの汎用リポジトリの実装を非常に簡単に作成し、関連するメソッドをオーバーライドして必要な処理を実行し、IoC またはサービス ロケーターの登録を更新して完了できます。
ただし、このロジックは分野横断的であり、リポジトリ メソッドをオーバーライドして実装するのが面倒な場合があります。そこでIRepositoryBehavior
、基本的にはイベント シンクである を作成しました。(以下は私の頭の上からの大まかな定義です)
public interface IRepositoryBehavior
{
void OnAdding(CancellableBehaviorContext context);
void OnAdd(BehaviorContext context);
void OnGetting(CancellableBehaviorContext context);
void OnGet(BehaviorContext context);
void OnRemoving(CancellableBehaviorContext context);
void OnRemove(BehaviorContext context);
void OnFinding(CancellableBehaviorContext context);
void OnFind(BehaviorContext context);
bool AppliesToEntityType(Type entityType);
}
さて、これらの動作は何でもかまいません。監査、セキュリティ チェック、ソフト削除、ドメイン制約の適用、検証など。ビヘイビアを作成し、それを IoC またはサービス ロケータに登録し、汎用リポジトリを変更して登録済みIRepositoryBehavior
のコレクションを取り込み、各ビヘイビアをチェックします。現在のリポジトリ タイプと、適用可能な各動作の事前/事後ハンドラーで操作をラップします。
論理的な削除の動作の例を次に示します (論理的な削除とは、誰かがエンティティを削除するように要求したときに、エンティティを削除済みとしてマークするだけで、再度返すことはできませんが、実際に物理的に削除されることはありません)。
public SoftDeleteBehavior : IRepositoryBehavior
{
// omitted
public bool AppliesToEntityType(Type entityType)
{
// check to see if type supports soft deleting
return true;
}
public void OnRemoving(CancellableBehaviorContext context)
{
var entity = context.Entity as ISoftDeletable;
entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated
context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity.
}
}
はい、これは基本的に NHibernate のイベント リスナーの単純化および抽象化された実装ですが、それが私が気に入っている理由です。A) NHibernate を使わずに振る舞いを単体テストできる B) これらの振る舞いを NHibernate の外部で使用できる (たとえば、リポジトリは REST サービス呼び出しをラップするクライアント実装である) C) NH のイベント リスナーは本当に厄介な問題になる可能性がある;)