web-dev-qa-db-ja.com

クリーンアーキテクチャでトランザクションをどのように使用しますか?

私がオンラインで見つけることができるそれの実装は、実際にはそれを実装するフレームワークにとらわれない実用的な方法をあなたに与えません。

私はそれを解決するためのいくつかの標準以下の提案を見てきました:

  1. リポジトリメソッドをアトミ​​ックにする

  2. ユースケースをアトミックにする

どちらも理想的ではありません。

ケース#1:ほとんどのユースケースは、仕事を遂行するために複数のリポジトリメソッドに依存しています。 「注文」する場合、「注文リポジトリ」の「挿入」メソッドと「ユーザーリポジトリ」の「更新」メソッドを呼び出す必要がある場合があります(例:ストアクレジットを差し引くため)。 「挿入」と「更新」がアトミックである場合、これは悲惨なことになります。注文することはできますが、実際にはユーザーに料金を支払わせることができません。または、ユーザーに支払いをさせますが、注文に失敗します。どちらも理想的ではありません。


ケース#2:良くありません。各ユースケースがサイロ内にある場合は機能しますが、コードを複製する必要がない限り、他のユースケースの操作に依存するユースケースがあることに気付くことがよくあります。

「注文する」ユースケースと「ポイントを与える」ユースケースがあるとします。どちらのユースケースも個別に使用できます。たとえば、上司は、システムの起動日の記念日にログインしたときに、システム内のすべてのユーザーに「ポイントを付与」したい場合があります。もちろん、ユーザーが購入するときはいつでも「注文する」ユースケースを使用します。

これで、システムの発売から10周年が始まります。あなたの上司は、「2018年7月、誰かが注文するたびに、ポイントを付与したい」と決定します。

おそらく来年までに放棄されるこの1回限りのアイデアの「発注」ユースケースを直接変更する必要をなくすために、「プロモーション中の発注」という別のユースケースを作成することにします。 「注文する」と「ポイントを与える」。素晴らしい。

ただ...できません。つまり、できます。しかし、あなたは正方形に戻っています。 「PlaceOrder」はアトミックであったため、成功したかどうかを保証できます。そして、同じ理由で「リワードポイントを与える」が成功したかどうかを保証することができます。ただし、どちらかが失敗した場合、もう一方をロールバックすることはできません。それらは同じトランザクションコンテキストを共有しません(内部でトランザクションを「開始」および「コミット」/「ロールバック」するため)。


上記のシナリオにはいくつかの可能な解決策がありますが、どれも非常に「クリーン」ではありません(作業単位が思い浮かびます-ユースケース間で作業単位を共有することでこれを解決できますが、UoWは醜いパターンであり、まだありますどのユースケースがトランザクションの開始/コミット/ロールバックを担当しているかを知ることの問題)。

12
aetheus

トランザクションをコントローラーに置きます。コントローラーは、おそらく少なくともフレームワークの注釈のようなメタデータを持っているので、より大きなフレームワークについて知っています。

作業単位に関しては、それは良い考えです。各ユースケースでトランザクションを開始することができます。内部的には、作業単位は実際のトランザクションを開始するか、呼び出された開始のカウンターを増やします。次に、各ユースケースはcommitまたはrejectを呼び出します。コミットカウントが0に等しい場合、実際のコミットを呼び出します。 Rejectはそのすべてをスキップし、ロールバックしてからエラーアウトします(例外またはリターンコード)。

あなたの例では、ラッピングのユースケースはstart(c = 1)を呼び出し、place orderはstart(c = 2)を呼び出し、place order commit(c = 1)、ボーナス呼び出しはstart(c = 2)、ボーナス呼び出しはcommit(c = 1)、ラッピングはコミット(c = 0)なので、実際にコミットします。

サブトランザクションはあなたにお任せします。

4
Virmundi

ケース#1

「注文」する場合は、「注文リポジトリ」の「挿入」メソッドと「ユーザーリポジトリ」の「更新」メソッドを呼び出す必要がある場合があります(例:ストアクレジットを差し引くため)。

これらの方法は両方ともアトミックである必要があり、ユースケース全体が作業単位です。作業単位の仕事は、両方の方法が全体として成功または失敗することを保証することです。


パターン

「作業単位は、データベースに影響を与える可能性のあるビジネストランザクション中に行うすべてのことを追跡します。完了すると、作業の結果としてデータベースを変更するために行う必要があるすべてのことを把握します。」 -マーティンファウラー

キャッチオールの作業単位/リポジトリパターンを探している場合は見つかりません。フレームワークに依存しない実装を探している場合はさらに可能性が低くなります。

これは、インターウェブ上で最も正直なEntity FrameworkリポジトリとUoWチュートリアルです(抜粋: https://docs.Microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started- with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

リポジトリと作業単位のパターンを実装する方法はたくさんあります。作業単位クラスの有無にかかわらず、リポジトリクラスを使用できます。すべてのエンティティタイプに単一のリポジトリを実装することも、タイプごとに1つ実装することもできます。タイプごとに1つ実装する場合は、個別のクラス、汎用基本クラスと派生クラス、または抽象基本クラスと派生クラスを使用できます。リポジトリにビジネスロジックを含めることも、データアクセスロジックに制限することもできます。エンティティセットのDbSetタイプの代わりにIDbSetインターフェイスを使用して、データベースコンテキストクラスに抽象化レイヤーを構築することもできます。このチュートリアルに示されている抽象化レイヤーを実装するためのアプローチは、検討すべき1つのオプションであり、すべてのシナリオと環境に対する推奨事項ではありません。

箱から出してすぐに、OR/Mは通常、何らかの形式のリポジトリと作業単位パターンを提供しますが、これらはすでに抽象化されており、実装はコンシューマー(ユーザー)から隠されています。

XMLでデータを永続化する必要がある場合、リポジトリはおそらく非常に似ているように見えますが、UoWは大きく異なって見え、トランザクション/ロールバックのサポートはありません。

「クリーンコード」の方法は、SOLIDの原則を満たすために必要な、必要な数の抽象化レベルを設計することです。

永続性フレームワークに実際に依存せずにリポジトリとUoWを設計する方法については、かなり良いビデオがありますが、エンティティフレームワークのような匂いがあるため、完全にフレームワークに依存しないとは言いません。

C#とエンティティフレームワークを使用したMosh Hamedaniリポジトリパターン、正しく実行 https://www.youtube.com/watch?v=rtXpYpZdOzM


ケース#2には、私の意見では根本的なアーキテクチャの問題があります。

LoginControllerは、「ユーザーにログインする」と「リワードポイントを与える」必要があります。2つのことを行います。

PurchaseControllerには「購入を完了する」と「リワードポイントを与える」が必要です-2つのことを行う

解決策は、RewardsPointsControllerを作成し、LoginControllerとPurchaseControllerの両方でRewardsPointsControllerを呼び出して、イベントの種類または日付に基づいてユーザーが報酬ポイントを取得するかどうかを決定することです。

このように考えると、より明確になります。

プロモーション期間中にユーザーがログインし、RewardsPointsControllerがリワードポイントの追加に失敗した場合、トランザクション全体が失敗する必要がありますか?その場合、ユーザーはログインできなくなります-リワードポイントは後でカスタマーサービスに電話することで修正できますが、ユーザーがログインできないか、購入を完了できない場合は、ビジネスを失います。

0
Adam Vincent

一般に、トランザクション定義は適切なレベルの抽象化と同時実行性の要件があるため、ユースケースレイヤーに配置することをお勧めします。私の意見では、最善の解決策は、ケース#2で公開するものです。さまざまなユースケースを再利用する問題を解決するために、一部のフレームワークはトランザクションでの概念伝播を使用します。たとえば、Springでは、トランザクションをREQUIREDまたはREQUIRES_NEWに定義できます。

https://www.byteslounge.com/tutorials/spring-transaction-propagation-tutorial

UseCasePlaceOrderAndGiveAwardsでTransactionalREQUIREDを定義すると、トランザクションは「ベース」ユースケースで再利用され、内部メソッドでのロールバックにより、ホールトランザクションがロールバックされます。

0
Alvaro Arranz