PostSharp トランザクション管理

画像 最後にある会社にアドバイスをしたとき、エンタープライズデータベースと対話する内部SOAフレームワークについて議論しました。 このフレームワークは、名前だけのSOAであり、完全に自家製であり、最も悲しいことに、IT部門の長の「お気に入りのプロジェクト」でした。 安全ではなく、疑わしい技術とソリューションの上に構築されました。 一般に、存在しないか、簡単な手段では解決できない特定の問題を解決するために作成されました。 私のチームは、このフレームワークの構造に非常に失望しました。 しかし、コンサルタントとして、最初に問題を解決する方法を検討する必要があります。顧客に製品が「悪い」ことだけを伝えることはできません。まず自分自身に対する信頼を築き、それからもっと深刻な問題を解決する必要があります。 いずれにせよ、このようなプロセスには何年もかかることがあり、そのすべてはそのような変化が起こる会社に依存します。 次に、この問題を解決するためにアスペクト指向のフレームワークを使用し始めることを考えました。



これらのプログラムのすべてのソースコードは、 GitHubで入手できます。


同時に、このフレームワークはマルチスレッドアプリケーション(Webサーバーなど)を対象としたものではないため、失敗する運命にありました。 このため、事故は絶えず発生し、時にはそれが単に機能したり起動したりすることができません。 まず、顧客企業の側で一定レベルの信頼を築きたいので、この混乱と混乱の周りで、機能する適切に機能するアプリケーションを構築する必要がありました。 この会社のためにすでに行ったことは、以前の記事で見ることができます。これはロギングとエラートラップです。 これはうまく機能し、すべての「厄介なもの」をエンドユーザーから隠しました。 ただし、このシステムを使用するには、システムに接触するすべてのコードを保護する必要があります。 そして、これを行うことで、私はかなり早く疲れました。 結局、想像してみてください:フレームワークで呼び出されるか、クラスのプロパティとフィールドの値を読み取るメソッドごとに、同じチェックを実行する必要があり、それを無理しないでください。そうしないとパフォーマンスが低下します。

ただし、古いプロジェクトについて話すのはやめましょう。ビジネスについて話しましょう。自作のフレームワークでアプリケーションを構築するかどうかに関係なく、 PostSharpを使用してトランザクションを管理できます。 以下に、典型的なトランザクションサポートの基本の例を示すOnMethodBoudaryAspectの簡単な例を示します。

[Serializable] public class TransactionScopeAttribute : OnMethodBoundaryAspect { [NonSerialized] private ICharityService _charityService; private string _methodName; private string _className; public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo) { _className = method.DeclaringType.Name; _methodName = method.Name; } public override void RuntimeInitialize(System.Reflection.MethodBase method) { // in practice, the begin/rollback/commit might be in a more general service // but for convenience in this demo, they reside in CharityService alongside // the normal repository methods _charityService = new CharityService(); } public override void OnEntry(MethodExecutionArgs args) { _charityService.BeginTransaction(); } public override void OnException(MethodExecutionArgs args) { _charityService.RollbackTransaction(); var logMsg = string.Format("exception in {0}.{1}", _className, _methodName); // do some logging } public override void OnSuccess(MethodExecutionArgs args) { _charityService.CommitTransaction(); } }
      
      







すべてが本当にシンプルで、古い記事に関してまったく新しいものはありません。 トランザクションのロールバックの場合、ここで例外をスローするとよいことに注意してください。これはデフォルトで行われます(onExceptionを発生させるコードで)。 必要なのは、トランザクションをサポートする必要があるメソッドを[TransactionScope]属性でマークすることだけです。1つの単純な側面で、try / catch begin / commit / rollbackコードを何度も書くことから身を守ります。 また、共通のコードを1か所で取り出し、同じ方法でロギングまたは例外をキャッチするための特別なメソッドを追加できます。

上記のコードが適切に機能しないケースの1つは、ネストされたトランザクションです。 上記の私のコードでは、Begin / Commit / Rollbackメソッドは実際には何もしないダミーです。 開発する最終アプリケーションでは、トランザクション管理はデータアクセス方法に大きく依存しています。 SqlConnection、ODBC、OracleなどのADO.NET接続を使用する場合は、 TransactionScopeを使用して(アスペクト名と混同しないでください)、トランザクションの実装をシンプルでサポートします。 ただし、一部のスタンドアロンテクノロジを使用する場合は、まったく異なるAPIを使用するため、追加のソリューションが必要になります。

トランザクションサポートの基本的な側面ができたので、元の例に戻りましょう。 私が使用しているサービスは常に壊れています。 したがって、操作が成功するまで操作を実行しようとするサイクルを記述します。 そして、もちろん、私はその試みが一定であり、無限のサイクルに成長することを望まない。 したがって、試行回数に制限を設けています。 また、DataExceptionは失敗した試みの兆候であり、他のすべての例外は、それ以降の操作を実行しようとする試みがすべて無駄であることを示しています。 したがって、このような例外を誓約し、試行を停止します。

 [Serializable] public class TransactionScopeAttribute : MethodInterceptionAspect { [NonSerialized] private ICharityService _charityService; [NonSerialized] private ILogService _logService; private int _maxRetries; private string _methodName; private string _className; public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo) { _methodName = method.Name; _className = method.DeclaringType.Name; } public override void RuntimeInitialize(System.Reflection.MethodBase method) { _charityService = new CharityService(); _logService = new LogService(); _maxRetries = 4; // you could load this from XML instead } public override void OnInvoke(MethodInterceptionArgs args) { var retries = 1; while (retries <= _maxRetries) { try { _charityService.BeginTransaction(); args.Proceed(); _charityService.CommitTransaction(); break; } catch (DataException) { _charityService.RollbackTransaction(); if (retries <= _maxRetries) { _logService.AddLogMessage(string.Format( "[{3}] Retry #{2} in {0}.{1}", _methodName, _className, retries, DateTime.Now)); retries++; } else { _logService.AddLogMessage(string.Format( "[{2}] Max retries exceeded in {0}.{1}", _methodName, _className, DateTime.Now)); _logService.AddLogMessage("-------------------"); throw; } } catch (Exception ex) { _charityService.RollbackTransaction(); _logService.AddLogMessage(string.Format( "[{3}] {2} in {0}.{1}", _methodName, _className, ex.GetType().Name, DateTime.Now)); _logService.AddLogMessage("-------------------"); throw; } } _logService.AddLogMessage("-------------------"); } }
      
      







はい、少し恐ろしいですね! でも心配しないで、分解してみましょう。 CompileTimeInitializeとRuntimeInitializeの外観は、前述のものとまったく大きく異なりません。クラス名とメソッド名を記憶し、必要なサービスを初期化するだけです。

OnInvokeメソッドの場合、考えられる4つの作業シナリオを見てみましょう。



単純なアプリケーションでは、CharityServiceサービスを作成しました。これは、ケースの50%でDataExceptionエラーでクラッシュし、残りの50%で正常に終了します。 また、50回の呼び出しのうち約1回で、DataExceptionを返さず、他の例外を返します。 この例を実行すると、ログウィンドウが表示され、そこに膨大な数の試行が記録され、Begin、Rollback、Commitの呼び出しが記録されます。 また、プログラムが許可されているよりも多くの試行を行った場合、またはDataExceptionではない例外が発生した場合、ユーザーにはエラーメッセージが表示されます。



この側面を使用して、アプリケーションにこれらの種類の問題を解決するための堅牢性を与えます。 アスペクトコードは大きくなりましたが、管理しやすいです。 もっと複雑にしたい場合は、リファクタリングがあります。クラスとメソッドへの分割、およびその他の操作です。 このメソッドを使用して、1つのスレッドでメソッドへのマルチスレッド呼び出しを作成することもできます。



参照:




All Articles