マイクロサービスにおけるCamelのVETROパターンの適用方法

## はじめに

マイクロサービスの世界で、***[Apache Camel](http://camel.apache.org/)*** は ***Integration***, ***Composite***, ***Asynchronous*** の役割に最も適切でしょう。

因みに、Business Logicも***Camel*** を適用することは可能だが、会社の事情(開発要員スキルセットなど)によって、Camelが第一選択肢になれないことが予想される。なので、一旦ここでの議論を省略。

- ***Integration*** とは、外部システムと統合する機能をサービスとして提供すること
- ***Composite*** とは、バックエンドにある、Business Logicの実装した複数のマイクロサービスを組み合わせて１つのサービスとして提供すること
- ***Asynchronous*** とは、バックエンドにあるマイクロサービス(同期呼出)を非同期方式で利用できる機能をサービスとして提供すること

Integration, Composite, Asynchronousの役割を果たすため、Camelルート設計の基本は、***VETROパターン*** と言えるでしょう。

## VETROパターンとは

VETROパターンは、システム統合で最も使われるパターンである。  
VETROパターンとは、一般的に、メッセージ処理の過程で、Validate、Enrich、Transform、Route、Operateの５つの要素(ステップ)のこと。
1. Validate: メッセージの検証
2. Enrich: メッセージに情報付加
3. Transform: メッセージの変換
4. Route: ルーティング
5. Operate: 送受信処理

**Integration Service** に対して、VETROパターンを適用した例は、
<img width="745" alt="integration-service.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/93527/a3433865-de8b-afcf-b9d9-b04514f8d120.jpeg">


**Composite Service** に対して、VETROパターンを適用した例は、
<img width="758" alt="composite-service.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/93527/9cf5750a-a2f8-6c18-7cf5-93542fa65ef7.jpeg">
どうでしょう。基本設計時に、VETROパターンを適用すると、共通性が見えて、詳細設計が容易になることが分かるね。

参考情報のリンク:
- [Camel Design Patterns by Bilgin Ibryam](http://amzn.asia/d/anrTqoV)
- [Enterprise Service Bus by David Chappell](http://shop.oreilly.com/product/9780596006754.do)
- [ESBの処理パターン by 高安 厚思](https://thinkit.co.jp/free/article/0606/3/4)
- [VETRO Pattern for API Gateways - DZone Microservices](https://dzone.com/articles/vetro-for-rest-apis)


これからは本題に入る。  
マイクロサービスにおけるCamelのVETROパターンの設計方法、即ち、VETRO各要素における、Camelコンポーネント候補の組み合わせ方法と選別方法を以降に説明したいと思う。

## 1. Validation（検証）

Validationステップとは、入力されたメッセージが正しい形式（例えば、JSON、CSV、XMLなど）になっており、該当するスキーマに準拠しているかを確認することである。マイクロサービスの世界では、さらにIdemptencyとCircuit Breakerもここで行う。

#### ❏ 単純な文字列検証は、[Camel EIP Validate][1]で行う

[Validate][1]は、Camelのサポートする条件評価式の戻り値(true/false)をベースに検証できる。

例えば、ヘッダーbarの検証は、simple("${in.header.bar} == 100")

[1]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_development_guide/#Validate

#### ❏ XML形式の検証は、[CamelコンポーネントValidator][2]で行う

[Validation][2]は、JAXP Validation APIを利用して、XMLスキーマ(XSD)をベースに検証する。

[2]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_component_reference/#validator-component

#### ❏ XML形式以外の検証は、２段階の検証を行う

第一段階は、メッセージをJavaBean(POJO推奨)に変換することで形式の妥当性を確認する。

第二段階は、Java Bean(POJO)に対して、[CamelコンポーネントBean Validation][3]で検証する。Bean Validationは、Java Bean Validation APIを利用して、Beanのアノテーション記述(JSR-303 API)をベースに検証する。

[3]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_component_reference/#bean-validator-component

#### ❏ メッセージ重複の検証は、[CamelコンポジットIdempotentConsumer][4]で行う

[IdempotentConsumer][4]は、常に、過去に処理済みMessageID(メッセージの特定するユニークID)をリポジトリに格納する。そして、現在処理中MessageIDとリポジトリにあるものと突き合わせることで重複であるかを検証できる。

[4]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_development_guide/#MsgEnd-Idempotent

#### ❏ サーキットブレーカーは、[Camelコンポーネントhystrix][5]を利用

特にComposite Serviceにおいては、複数のサービスを呼び出しのため、1つのサービス呼出障害により、連鎖的な障害が発生する可能性がある。

例えば、`検索A`->`検索B`->`検索C`のような流れのComposite Serviceがあると仮定する。  
`検索C`だけが一時通信不可が発生する場合、API利用者がサービス呼び出すとエラーになる。そして、API利用者がリトライすると、本サービスはリクエストを受け、再び`検索A`->`検索B`を実行し、`検索C`でエラーとなる。即ち、API利用者が障害の状況を識別できないので、頻繁なリトライによってバックエンドにある`検索A`と`検索B`のマイクロサービスに負荷集中し、障害拡大する恐れがある。

このような状況を回避するには、サーキットブレーカー、Camelコンポーネント[hystrix][5]を利用する。  
例えば、以下のようにサーキットブレーカーに設定する。
- 10秒以内に処理が終了しなかったらタイムアウトエラーにする
- 何らかのエラーが15秒以内に2回以上発生したら本サービスを120秒間停止(サーキットブレーカーをOpen)する

上記設定したサービス提供の際、`検索C`障害発生直後の2分間は、サービス停止によって `検索A` も `検索B` の呼出しない。２分後は、サービス再開し、`検索C` が正常であれば、サービスが復旧。`検索C` がまだ正常でない（２回エラー）であれば、再び２分間サービス停止になる。こうすることで、`検索A` と `検索B` のマイクロサービスを負荷集中から保護できる。

[5]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_development_guide/#MsgRout-Hystrix

## 2. Enrich(データ付加)

Enrichステップとは、後続のTransform、Route、Operateステップの前提条件を満たすため、入力されたメッセージにデータを付加することである。

例えば、あるサービス内のRouteステップであるユーザ情報送信先振り分けを行うために、前提条件が"ユーザ登録元システム名"が必要な場合を考えてみよう。このサービスを呼び出す度にクライアントはユーザ詳細情報を検索して格納しなければならないとすると、サービスの利便性は高くない。

そこでEnrichステップを利用して、メッセージの中にユーザIDを含めて、EnrichステップでユーザIDから別APIで"ユーザ登録元システム名"を取り出してメッセージに追加し、後続のステップを呼び出すとすれば、このサービスの前提条件を少なくすることができ、再利用の可能性をあげることができる。

#### ❏ Enrichステップは、[Camel EIP Enrich][6]を利用する

Enrichは、指定したエンドポイント呼出の結果からデータ取り出しとデータ付加までの処理を行える。

[6]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html/apache_camel_development_guide/msgtran#MsgTran-Enricher



## 3. Transform(変換)
Transformステップとは、メッセージフォーマットの変換を行うステップである。

このステップでは入力メッセージの一部を取り出してOperationのメッセージとし、 Operationが呼び出された後の出力メッセージを取り出して入力メッセージの一部とするなどの処理を行う。

#### ❏ 簡単な変換は、[Camelで用意されるメソッド][7]を使う

例えば、
- ヘッダー設定:: `setHeader(String name, Expression expression)`
- 文字列置換:: `regexReplaceAll(Expression content, String regex, String replacement)`

[7]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html/apache_camel_development_guide/basicprinciples#BasicPrinciples-Transform-Simple

#### ❏ マーシャル・アンマーシャルは、[Camelコンポーネントmarshal・unmarshal][8]を利用

JavaオブジェクトからJSON/XML/CSVデータを生成するプロセスのことをマーシャル（marshal）と呼び、逆にJSON/XML/CSVデータからJavaオブジェクトを生成するプロセスのことをアンマーシャル（unmarshal）と呼ぶ。

[8]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html/apache_camel_development_guide/basicprinciples#BasicPrinciples-Transform-Marshal

#### ❏ 複雑な変換は、[CamelコンポーネントTransformer][9]を利用

例えば、ユーザ情報と所属部署情報をマージして出力したり、項目マッピングに加え項目演算なども必要な場合はGUIマッピングツールも使える。

[9]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html/apache_camel_development_guide/msgtran#Transformer-Transformer

#### ❏ 上記以外ケースはテンプレートエンジンを使う

テンプレートベースの変換はCamelコンポーネント、例えば、[FreeMaker][10], [Velocity][11], XQuery, XSLT, Mustache, Chunk, MVELなどが利用可能。

[10]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_component_reference/#freemarker-component

[11]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_component_reference/#velocity-component


## 4. Route(ルーティング)

Routeステップは、メッセージの内容からどのOperate(送受信処理)を送り先とするかを決定する。

これは、ネットワークのルーティング処理が送信先のIPアドレスからどのネットワーク先かを判定して物理的な送り先を決定しているのと同様で、Routeステップもメッセージの中身からどのOperateを呼び出すのかを決定する。

このようにメッセージの中身に基づいてルーティングすることをCBR（Content Based Routing）と呼ぶ。

#### ❏ 判定条件中の送り先が設計時に確定のケースは、[Camel EIPのContent Based Routing][12]を利用する

例えば、メッセージのヘッダーfoo=barであれば送り先barに決定する、これは、設計時に送り先barが確定済みである。この場合、Camel EIPのContent Based Routing[1]を利用する。

[12]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_development_guide/#MsgRout-ContentBased

#### ❏ 判定条件中の送り先が設計時に非確定のケースは、[Camel EIPのRecipient List][13]を利用する
ランタイム値を送り先にするの場合、例えば、メッセージのヘッダーfooの値を送り先とする、これは、fooランタイム値が送り先となるため、設計時では送り先が不定である。この場合、Camel EIPの[Recipient List][13]を利用する。

[13]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_development_guide/#DynamicRouter

#### ❏ 上記以外のケースは、判定条件の詳細によって以下のCamel EIPも併用できる
  - メッセージ分割してルーティングのケースは、[Camel EIPのSplitter][14]を利用する。

  - 複数メッセージの待ち合わせ後にルーティング(集約)の場合は、Camel EIPの[Aggregator][15]を利用する。

  - 複数送り先へ同報の場合は、Camel EIPの[Multicast][16]を利用する。

  - 複雑なルーティングは送り先の処理結果によって次の送り先を決定する場合は、Camel EIPの[Dynamic Route][17]を利用する。

[14]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_development_guide/#MsgRout-Splitter

[15]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_development_guide/#MsgRout-Aggregator

[16]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_development_guide/#MsgRout-Multicast

[17]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_development_guide/#DynamicRouter

## 5. Operate(送受信処理)

Operateステップは、Routeステップによって決定された送受信処理の実装を呼び出すステップである。呼び出す送受信処理は、外部システムにアクセスするコネクタ、カスタマイズの処理などがあげられる。


#### ❏ 外部システムにアクセスするコネクタは、サポートされる[Camelコンポーネント][18]からtype=endpointのものを選んで利用する

[18]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_component_reference/#Components-Overvew-Supported

#### ❏ カスタマイズの処理はPOJOで実装か、Camel Processorで実装かの選択できる

- 一般的に、処理中にCamel APIを使わない場合は、[Java Bean(POJO)][19]で実装するとUnitTestが簡単のため推奨。
- しかしながら、Camel APIを使う場合、例えば、Exchange APIでメッセージ中身をフルアクセスしたい、[Camel Processor][20]で実装する。この場合、UnitTestは[Camel Test Kit][21]を併用する必要。

[19]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_development_guide/#BasicPrinciples-BeanIntegration

[20]: https://access.redhat.com/documentation/en-us/red_hat_fuse/7.1/html-single/apache_camel_development_guide/#Processors

[21]: http://camel.apache.org/testing.html