背景
camelContextのrouteでtransactionおよびrollbackを定義すると、
既存実装のErrorHandler(DeadLetterChannel)とTransactionErrorHandlerの組み合わせが上手くいかないです。
また、onExceptionを用いて直接ErrorResponseExceptionHandlerを呼びその後明示的にrollbackをすると両立が可能なのですが、ErrorHandlerの優先順位としてonException > CommonErrorHandlerであると考えられるため、
rollbackをした後にCommonErrorHandlerを呼べるような実装は何か考えられますでしょうか。
設計思想
Camelのエラーハンドリングは以下の3つの仕組みが提供されています。
-
ErrorHandler
CamelContext、もしくはルートの例外をキャッチする。
例外の内容に応じたキャッチができない。デフォルトで動作する例外処理機構。
onException
を併用することで例外の内容に応じてキャッチする内容を変更できる。 -
onException
CamelContext、もしくはルートの例外をキャッチする。
例外の内容に応じてキャッチする内容を変更できる。 -
doTry, doCatch, doFinally
Javaのtry, catch, finallyと同様に、ルート内で例外をキャッチするスコープを設定できる。
例外の内容に応じてキャッチする内容を変更できる。
トランザクション処理における重要なポイントは、ErrorHandler
と onException
が、基本的に、トランザクション外のリソースに対して補償(compensation)する仕組みであります。その為、ErrorHandler
だけでは、トランザクションをrollbackできません。
従いまして、トランザクションでカバーされている処理については、なるべく doTry, doCatch, doFinally
を使って、これらの処理をcommit/rollbackできるように設計した方が良いです。
問題解決のポイント1 - rollback呼び出す場所
"onExceptionを用いて明示的にrollbackをする"は適切ではないと考えます。
前述のように、onException
はロールバック用の仕組みではなく、CamelContextレベルのエラーハンドリングです。
一方で、rollbackはRoute内で開始したトランザクションに対するエラーハンドリングです。
その為、下記のように、rollbackはRoute内(即ちトランザクション範囲内)で doTry/doCatch
を使って使用した方が自然です。
<route … >
<…>
<transacted id="_transacted2"/>
<doTry id="_doTry1">
<to uri="sql:classpath:sql/updateDemo.sql?dataSource=ds_testdb"/>
<process ref="Process2"/>
<doCatch id="_doCatch1">
<exception>java.lang.Exception</exception>
<log id="_log1" message="Catch Exception of ${exception}"/>
<rollback id="_rollback1" markRollbackOnly="false"/>
</doCatch>
</doTry>
</route>
問題解決のポイント2 - rollbackをした後にErrorHandlerを呼べるように
rollbackの属性markRollbackOnly=trueに指定の場合、rollbackが発生した事(Exception)を通知せず、rollbackします。
markRollbackOnly=falseに指定すれば、rollbackが発生した事(org.apache.camel.RollbackExchangeException)がエラーハンドラ−に通知され、エラーハンドラの処理に委任されます。
<rollback id="_rollback1" markRollbackOnly="false"/>
更に、Routeにてエラーハンドラを明記し、エラーハンドラ内でCommonErrorHandlerを呼べるような実装すれば、rollbackをした後にCommonErrorHandlerを呼べるようになります。
<route ... errorHandlerRef="myErrorHandler">
実装サンプルで動作確認しましょう
実装サンプルは、github上、restdsl-sample3 で公開しました。
前述の解決ポイントは、CamelRouteのrestdsl-sample.xmlの下記の部分をご参照ください。
- route id="updateSample"
- errorHandler id="myErrorHandler"
- route id="CommonErrorHandler"
実装サンプルの動作確認するには下記の準備と実行の順に行ってください。
Route updateSample内のProcess2のExceptionによって、ロールバックされることを確認できます。
ログには以下のように出力されます。
updateSample INFO Excec updateSample 2
updateSample INFO Catch Exception of java.lang.RuntimeException: this is for rollback test.
DeadLetterChannel WARN Rollback (MessageId: ID-JFENG-MP-64075-1507111792224-0-2 …
CommonErrorHandler ERROR Exchange[Id: ID-JFENG-MP-64075-1507111792224-0-1 …
CommonErrorHandler INFO This is CommonErrorHandler: body={id=2}
準備 To prepare user, db and table
create database testdb;
GRANT ALL PRIVILEGES ON testdb.* TO 'test'@'%' IDENTIFIED BY 'test';
GRANT ALL PRIVILEGES ON testdb.* TO 'test'@'localhost' IDENTIFIED BY 'test';
use testdb;
CREATE TABLE demo
(
id INTEGER,
name TEXT,
datetime DATETIME,
PRIMARY KEY (id)
) COMMENT='this is my test table';
RESTサービスの起動 To run the project
下記のようにMavenコマンドで実行します。
mvn camel:run
RESTクライアントの実行
- HTTP POST - createSample
createSampleは、Body(HashMap)のidをSQL内でInsert部で使用します。
curl -X POST -H 'Content-type: application/json' -d '{"id":"2"}' localhost:9000/private-api/v1/sample
- HTTP PUT - updateSample
updateSampleは、Body(HashMap)のidをSQL内でUpdateで使用しますが、
Process2のExceptionによって、ロールバックされることを確認する目的です。
curl -X PUT -H 'Content-type: application/json' -d '{"id":"2"}' localhost:9000/private-api/v1/sample
実行結果
ログには以下のように出力されます。
updateSample INFO Excec updateSample 2
updateSample INFO Catch Exception of java.lang.RuntimeException: this is for rollback test.
DeadLetterChannel WARN Rollback (MessageId: ID-JFENG-MP-64075-1507111792224-0-2 …
CommonErrorHandler ERROR Exchange[Id: ID-JFENG-MP-64075-1507111792224-0-1 …
CommonErrorHandler INFO This is CommonErrorHandler: body={id=2}