apache-camel
Transaction
errorhandling

camel transactionのエラーハンドリング方法

背景

camelContextのrouteでtransactionおよびrollbackを定義すると、
既存実装のErrorHandler(DeadLetterChannel)とTransactionErrorHandlerの組み合わせが上手くいかないです。

また、onExceptionを用いて直接ErrorResponseExceptionHandlerを呼びその後明示的にrollbackをすると両立が可能なのですが、ErrorHandlerの優先順位としてonException > CommonErrorHandlerであると考えられるため、
rollbackをした後にCommonErrorHandlerを呼べるような実装は何か考えられますでしょうか。

設計思想

Camelのエラーハンドリングは以下の3つの仕組みが提供されています。

  1. ErrorHandler
    CamelContext、もしくはルートの例外をキャッチする。
    例外の内容に応じたキャッチができない。デフォルトで動作する例外処理機構。
    onException を併用することで例外の内容に応じてキャッチする内容を変更できる。

  2. onException
    CamelContext、もしくはルートの例外をキャッチする。
    例外の内容に応じてキャッチする内容を変更できる。

  3. doTry, doCatch, doFinally
    Javaのtry, catch, finallyと同様に、ルート内で例外をキャッチするスコープを設定できる。
    例外の内容に応じてキャッチする内容を変更できる。

トランザクション処理における重要なポイントは、ErrorHandleronException が、基本的に、トランザクション外のリソースに対して補償(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}