複数のリポジトリの非自明なCRUD操作の作業単位
-
10-07-2019 - |
質問
次のコードのようなもので実装された作業単位パターンを見ました:
private HashSet<object> _newEntities = new HashSet<object>();
private HashSet<object> _updatedEntities = new HashSet<object>();
private HashSet<object> _deletedEntities = new HashSet<object>();
そして、これらの各HashSetにエンティティを追加するメソッドがあります。
コミット時に、UnitOfWorkはエンティティごとにマッパーインスタンスを作成し、仮想マッパーからInsert、Update、Deleteメソッドを呼び出します。
このアプローチの問題は、Insert、Update、Deleteメソッドの名前がハードコードされているため、このようなUnitOfWorkは単純なCRUD操作のみを実行できるように見えることです。しかし、次の使用法が必要な場合はどうなりますか:
UnitOfWork ouw = new UnitOfWork();
uow.Start();
ARepository arep = new ARepository();
BRepository brep = new BRepository();
arep.DoSomeNonSimpleUpdateHere();
brep.DoSomeNonSimpleDeleteHere();
uow.Commit();
挿入、更新、削除操作に対してのみAおよびBエンティティを登録できましたが、これらのカスタム操作が必要になったため、3ハッシュセットアプローチは失敗しました。
だから、リポジトリ操作を常に スタックしてから UnitOfWork.Commit();
この問題を解決するには?最初のアイデアは-メソッドのアドレスを保存できる
arep.DoSomeNonSimpleUpdateHere();
brep.DoSomeNonSimpleDeleteHere();
UoWインスタンスで uow.Commit()
で実行しますが、すべてのメソッドパラメーターも保存する必要があります。複雑に聞こえます。
もう1つのアイデアは、リポジトリを完全にUoW対応にすることです。 Repositoryインスタンスのスタックの操作パラメーターと「保留」ステータス(明らかに、UoWは具象リポジトリ実装に依存しないため、すべてをUoWに保存することはできません)。そして、関連するリポジトリをUoWインスタンスに登録します。 UoWは Commit
を呼び出すと、トランザクションを開き、保留中の各リポジトリに対してFlush()などを呼び出します。現在、リポジトリのすべてのメソッドには、UoWの検出と後の Commit()
を延期する操作のためにいくつかのものが必要です。
簡単な質問は-UoWの複数のリポジトリで保留中のすべての変更を登録し、それらをすべて単一のトランザクションで Commit()
する最も簡単な方法は何ですか?
解決
複雑な更新でも、1つ以上のDomainObjectsに対する一連の変更に分解できるように思われます。 DoSomeNonSimpleUpdateHere()を呼び出すと、いくつかの異なるDomainObjectsが変更される場合があり、各オブジェクトに対してUnitOfWork.registerDirty(DomainObject)の対応する呼び出しがトリガーされます。以下のサンプルコードでは、DoSomeNonSimpleUpdateHereの呼び出しを、システムから非アクティブなユーザーを削除するコードに置き換えました。
UnitOfWork uow = GetSession().GetUnitOfWork();
uow.Start();
UserRepository repository = new UserRespository();
UserList users = repository.GetAllUsers();
foreach (User user in users)
{
if (!user.IsActive())
users.Remove( user );
}
uow.Commit();
すべてのユーザーを反復処理する必要がある場合は、Criteriaオブジェクトを使用してデータベースから取得するユーザーの数を制限する代替アプローチを次に示します。
UnitOfWork uow = GetSession().GetUnitOfWork();
uow.Start();
Repository repository = new UserRespository();
Criteria inactiveUsersCriteria = new Criteria();
inactiveUsersCriteria.equal( User.ACTIVATED, 0 );
UserList inactiveUsers = repository.GetMatching( inactiveUsersCriteria );
inactiveUsers.RemoveAll();
uow.Commit();
UserList.RemoveおよびUserList.RemoveAllメソッドは、削除された各ユーザーをUnitOfWorkに通知します。 UnitOfWork.Commit()が呼び出されると、_deletedEntitiesで見つかった各ユーザーが削除されます。このアプローチにより、特殊なケースごとにSQLクエリを記述することなく、任意の複雑なコードを作成できます。 UnitOfWorkはすべての非アクティブなユーザーに対して1つのステートメントだけでなく複数の削除ステートメントを実行する必要があるため、ここではバッチ更新を使用すると便利です。
他のヒント
この問題があるという事実は、Repositoryパターン自体を使用しているのではなく、複数のテーブルデータゲートウェイのようなものを使用していることを示唆しています。一般に、リポジトリは集約ルートをロードおよび保存するためのものです。そのため、エンティティを保存すると、永続化レイヤーはその集約ルートエンティティインスタンスのオブジェクトグラフのすべての変更を保存します。
コードに約1つの「リポジトリ」がある場合1つのテーブル(またはエンティティ)ごとに、テーブルデータゲートウェイまたはデータ転送オブジェクトを実際に使用している可能性があります。その場合、おそらく各Save()メソッドでアクティブなトランザクション(または作業単位)への参照を渡す手段が必要になります。
Evans DDDの本で、彼はトランザクション制御をリポジトリのクライアントに任せることを推奨しています。実際にはテーブルデータゲートウェイパターンを使用している場合は避けるのが難しいかもしれませんが、それは良い習慣ではないことに同意します。
ようやくこれを見つけました:
http://www.goeleven.com/Blog/82
作成者は、更新/挿入/削除に3つのリストを使用して問題を解決しましたが、エンティティをそこに保存しません。代わりに、リポジトリのデリゲートとそのパラメーターが保存されます。そのため、「コミット」では、作成者が登録済みの各デリゲートを呼び出します。このアプローチを使用すると、いくつかの複雑なリポジトリメソッドを登録することもできるため、別個のTableDataGatewayを使用することは避けられます。