JBoss Fuseを使い倒す その3:デザインパターン詳細編

2015年9月18日(金)
佐藤 匡剛(さとう ただよし)

はじめに

JBoss Fuseの魅力と使い方を紹介する本連載の締めくくりとして、前回と今回の2回にわたって、エンタープライズ統合パターン(Enterprise Integration Patterns:EIP)を紹介しています。EIPはJBoss Fuseの基盤をなすシステム間統合のデザインパターンです。そのためEIPを理解し、活用できるようになることは、JBoss Fuseを使いこなし、システム間統合のソリューションを効果的に構築する上で非常に重要です。

前回は、EIPの全体像とパターンの一覧を紹介しました。今回は、実際にJBoss Fuse上でEIPを使ってみます。この記事では、Camelで最も頻繁に使われるルーティングのパターンから、代表的な以下の4パターンを取り上げます。

  • Content-Based Routerパターン
  • Recipient Listパターン
  • Aggregatorパターン
  • Scatter-Gatherパターン

シームレスに設計と実装をつなぐJBoss Fuse

本題に入る前に、いま一度、JBoss Fuseの特徴を強調しておきたいと思います。

JBoss Fuse(より具体的にはその主要コンポーネントであるApache Camel)の特徴は、なんと言ってもDSL(Domain Specific Language、ドメイン固有言語)によるEIPのサポートです。多くのEIPがCamelルート上にほぼそのままDSLとしてマッピングされるため、たいへん容易にEIPを実装できます。さらに、システム間統合上の抽象度の高い設計判断がDSLで直接的に表現されているため、実装されたCamelルートの理解や保守という観点でも優れています。もちろん、XML DSLで実装されていれば、EIPを組み合わせた複雑なルートであってもJBoss Developer Studio(JBDS)上でグラフィカルにCamelルートの表示・編集が可能となり、その理解や保守の容易性はさらに高まります。

このように、EIPを通して設計と実装とが明示的にマッピングされるJBoss Fuseだからこそ、品質の高いシステム間統合ソリューションを構築できると言えます。例えば、EIPをサポートしないESBミドルウェアを想像してみてください。その上で構築されたソリューションは、設計時に考えた数々のEIPの組み合わせや工夫も、そのプログラミング言語中にクラスやメソッドの名前やコメントとして設計意図を残し、注意深く保守していかない限り、次第に実装と乖離していくことでしょう。さらに、もし残念なことに開発者が詳細なドキュメントを残してくれなかったとしたら、どうでしょう。その独自に実装されたソリューションから、当初のEIPをベースとした詳細な設計判断をくまなく読み取ることは果たしてできるでしょうか。

この特徴を念頭においた上で、これから実際に手を動かしてみましょう。EIPがJBoss Fuse上でどれだけ簡単に実装できるか、JBDSのグラフィカルなエディタを通して高度な設計がいかにシームレスに実装にマッピングされるかを体感してください。

Camel組み込みのEIPサポート

まずは、CamelのルートDSLに直接組み込まれているEIPから始めましょう。

Content-Based Router パターン

パターン概要
[fig-a]Content-Based Router
コンテンツベースルータ
メッセージをその内容(コンテンツ)に基づいて、異なる宛先に振り分ける

メッセージのルーティングを、その中身のデータに基づいて条件分岐するという、いわばプログラミングのif文に相当するようなルーティングの基本構成要素です。少しでも複雑なルーティングをしようとするなら、ほぼ必ず登場するパターンと言えます。これまでの記事でも、すでに何度か登場したのを覚えているでしょう。

Content-Based Routerは、JBDSのCamelルートエディタ上で、PaletteペインのRoutingカテゴリにあるChoice、WhenおよびOtherwiseの各ノードを組み合わせて実装します。

Content-Based Router パターンアイコン

図1:Content-Based Router パターンアイコン

Choiceノードがルート上にContent-Based Routerによる条件分岐があることを表し、WhenとOtherwiseのノードがその中で各条件に基づくそれぞれのルートの分岐を定義します。プログラミング言語のif文と同様に、到着したメッセージに対してまず一番上のWhenノードから順に条件式が評価され、条件式に最初にマッチしたルートへメッセージが流されます。Otherwiseノードはif文のelse句に相当する部分で、メッセージがどの条件式にもマッチしなかった場合に、最終的にこのルートが選択されます。

Whenノードにおける分岐の条件式は、以下のプロパティで設定します。

プロパティ説明
Language条件式の記述に用いる式言語xpath
Expression条件式が真だった場合にこの分岐が選ばれる//type = 'aaa'
Whenノードのプロパティ設定

図2:Whenノードのプロパティ設定

Whenノードの条件式は、メッセージ自身が持つ何らかのコンテンツ(ヘッダまたは本文)に対しての条件式にするのがポイントです。これがContent-Based Routerパターンを正しく理解し、実装する上で重要な点です。メッセージ中に存在しないデータ、例えばデータベースのレコードや環境変数などに基づいてメッセージをルーティングすることも可能です。しかし、その場合はコンテンツベースのルーティングとは言えず、実質的にDynamic Routerなどの別のパターンになることに注意してください。

XML DSLによる定義は、以下のようになります。式言語にXPathを指定した場合の例です。

<from uri="direct:in" />
...
<choice>
  <when>
    <xpath>//type = 'aaa'</xpath>
    ...
    <to uri="direct:aaa" />
  </when>
  <when>
    <xpath>//type = 'bbb'</xpath>
    ...
    <to uri="direct:bbb" />
  </when>
  <otherwise>
    ...
    <to uri="direct:invalid" />
  </otherwise>
</choice>

Camelがサポートする式言語

ここで、Camelの式言語(Expression Language)について触れておきましょう。

Content-Based Routerと同様、あらゆるEIPで何らかの式(expression)を記述するのに式言語が用いられます。例えば、Content-Based Routerではルーティングで分岐する条件式に用いられていました。他にもMessage Filterパターンではメッセージをフィルタリングする際の条件式、次に取り上げるRecipient Listパターンでは宛先リストの情報がどこに格納されているかを示す式などで使われます。

CamelのDSLがサポートしている式言語は、以下の通りです。

言語説明
Constant定数AAA
ELJSPとJSFで使われる統一式言語(JSR-245)${in.headers.type = 'aaa'}
Groovyスクリプト言語Groovyによる式request.headers.get('type') == 'aaa'
Headerメッセージヘッダの参照
MyHeader
JavaScriptスクリプト言語JavaScriptによる式request.headers.get('type') == 'aaa'
JXPathインメモリJavaオブジェクトへのXPathクエリin/body/type = 'aaa'
MVEL式言語MVELrequest.headers.type == 'aaa'
OGNL式言語OGNLrequest.headers.type == 'aaa'
PHPスクリプト言語PHPによる式strpos(request.headers.get('type'), 'aaa') !== false
PropertyExchangeプロパティの参照myProperty
Pythonスクリプト言語Pythonによる式request.headers['type'] == 'aaa'
Refレジストリの参照myExpression
Rubyスクリプト言語Rubyによる式$request.headers['type'] == 'aaa'
SimpleCamelの式言語${in.header.type} == 'aaa'
SpELSpring Frameworkの式言語#{request.headers['type'] == 'aaa'}
SQLインメモリJavaオブジェクトへのSQLクエリselect * from MyClass
XPathXPath言語//type = 'aaa'
XQueryXQueryクエリ言語/product[@type='aaa']

詳細は、以下のドキュメントを参照してください。
Apache Camel Development Guide - Part II. Routing Expression and Predicate Languages

Recipient List パターン

   パターン概要
[fig-b]Recipient List
受信者リスト
メッセージを複数の宛先に一斉に送信する。宛先のリストは動的に変更できる

Content-Based Routerは、いくつかあるルートの選択肢のうち1つを、メッセージのコンテンツに基づいて選択的に振り分けるパターンでした。しかし、複数あるルートのうちどれか1つではなく、全てのルートにメッセージを送りたい場合にはどうすればいいでしょうか。

EIPでは、このようないわゆるメッセージのブロードキャストを実現するのに2つのアプローチがあります。1つはメッセージチャネルを活用するアプローチで、Publish-Subscribe Channelパターンを使って実現します。具体的には、JMSのトピックなどを使って複数のコンシューマにメッセージを配信します。そしてもう1つがルーティングを活用するアプローチで、Recipient Listパターンを使って実現します。

当然ながら2つのアプローチにはそれぞれ長所・短所がありますが、ここでは触れません。詳細が気になる方は、ぜひ書籍のEIPを読んでみてください。

Recipient Listは、PaletteペインのRoutingカテゴリにあるRecipientListノードを使って実装します。

Recipient Listパターンアイコン

図3:Recipient Listパターンアイコン

Recipient Listのアイデアは、メッセージ中のどこかに宛先を指定した受信者リストを埋め込んでおき、そのリストに基づいてRecipientListノードに全宛先にメッセージをコピーして配信させるというものです。Publish-Subscribe Channelを用いた場合のアプローチとの大きな違いは、受信者リストに指定される可能性のあるエンドポイントを、あらかじめ全て定義しておく必要がある点です。

RecipientListノードでは、受信者リストを読み込む先を以下のプロパティで設定します。

プロパティ説明
Language式の記述に用いる言語header
Expression受信者リストを指定する式addresses
Delimiter(オプション)受信者リストの区切り文字を指定する。デフォルトはカンマ(,)
;
RecipientListノードのプロパティ設定

図4:RecipientListノードのプロパティ設定

XML DSLによる定義は、以下のようになります。ここでは、式言語にヘッダを指定した場合の例を示します。

<from uri="direct:in" />
...
<recipientList>
  <header>addresses</header>
</recipientList>

受信者リストの値は、以下のいずれかのデータ型で表現できます。

  • カンマ区切りの文字列
  • 配列
  • java.util.Collection
  • java.util.Iterator
  • org.w3c.dom.NodeList

例えば文字列で受信者リストを表現すると、以下のようになります。

"direct:aaa, direct:bbb, direct:ccc"

Aggregator パターン

   パターン概要
[fig-c] Aggregator
集約器
複数のメッセージを1つに集約する

1つのメッセージを複数の宛先へブロードキャストするのがRecipient Listパターンでしたが、今度は、その逆に複数のメッセージを1つに集約するパターンを考えてみましょう。それがAggregatorパターンです。

「複数のメッセージを1つに集約する」とだけ聞くと一見簡単そうですが、 Aggregatorはなかなか奥の深いパターンです。なぜならAggregatorは、他の多くのルーティングパターンと異なり、「ステートフル」なパターンだからです。例えばA、B、Cという3つのメッセージを集約するAggregatorを考えたとき、そのコンポーネントがそれらを1つのメッセージに集約するには、「A」「A、B」「B、C」など、A、B、Cのうちいくつまで集められているかという状態を常に保持しないといけないからです。

またパターンがステートフルであるということは、クラスタリングとの親和性が低いことを意味します。他の「ステートレス」なルーティングパターンの場合、コンポーネントが状態を持たないので、クラスタノードにそのまま分散させることで、容易にスケールアウトできます。しかしステートフルなパターンの場合は、正しい動作を得るためにはコンポーネントをクラスタ内にシングルトンサービスとしてデプロイする必要があり、そこが常にボトルネックとなります。ステートフルなパターンは、Aggregatorの他にもResequencerなどがあります。

Aggregatorは、PaletteペインのRoutingカテゴリにあるAggregateノードを使って実装します。

Aggregatorパターンアイコン

図5:Aggregatorパターンアイコン

Aggregatorパターンの奥深いポイントのもう1つは、メッセージをどうやって集約するかの設定にあります。一口にメッセージを集約すると言っても、それを実際にシステムに実装しようとすると、どのメッセージを1つのグループにまとめるのか、集約に必要なメッセージが集まったかどうかをどのように判定するのか、そして集まったメッセージをどうやって1つにまとめるのか、最低でもこの3点を考える必要があります。

つまり、Aggregatorパターンを実装するには、以下の3つのポリシーを設計する必要があります。

  • 集約するメッセージの相関性(Correlation)
  • メッセージ集約の完了条件(Completeness Condition)
  • 複数のメッセージをどのように1つに集約するかの戦略(Aggregation Algorithm)

CamelのAggregateノードでは、主に以下のプロパティを設定することで、この3つのポリシーを実装します。

プロパティ説明
Correlation Expressionメッセージの相関を指定する式。式の値が同じメッセージが1つに集約される
Completion Size Expression集約の完了条件。一定数のメッセージが集まったら集約する
Completion Timeout Expression集約の完了条件。一定の待機時間が過ぎたらメッセージを集約する
Completion Interval集約の完了条件。一定時間間隔で強制的にメッセージを集約する(Completion Timeoutと同時には使えない)
Completion Predicate集約の完了条件。条件式がtrueを返す場合にメッセージを集約する
Strategy Refメッセージの集約戦略。AggregationStrategyインターフェース実装への参照。
Aggregateノードのプロパティ設定

図6:Aggregateノードのプロパティ設定

メッセージ集約の完了条件には複数のプロパティが用意されていますが、Completion TimeoutとCompletion Intervalが併用不可である以外は、複数のプロパティを同時に指定可能です。複数条件を指定した場合、どれか1つでも条件を満たせば集約が完了したことになります。

Strategy Refプロパティには、集約戦略オブジェクトへの参照を指定するだけですが、この集約戦略オブジェクト自体はJavaクラスとして自分自身で実装しなくてはいけません。

XML DSLによる定義は、以下のようになります。

<from uri="direct:in" />
...
<aggregate strategyRef="aggregationStrategy">
  <correlationExpression>
    <header>aggregateId</header>
  </correlationExpression>
  <completionPredicate>
    <simple>${header.forceComplete} == 'true'</simple>
  </completionPredicate>
  <completionTimeout>
    <constant>1000</constant>
  </completionTimeout>
  <completionSize>
    <header>totalSize</header>
  </completionSize>
  ...
  <to uri="direct:out" />
</aggregate>

...

<bean id="aggregationStrategy"
    class="com.redhat.examples.fuse.eip.ListAggregationStrategy" />
著者
佐藤 匡剛(さとう ただよし)
レッドハット株式会社

グローバルサポートサービス JBossシニアソフトウェアメンテナンスエンジニア
オブジェクト指向ソフトウェアアーキテクト、SOA/ESBコンサルタントなどを経験した後、レッドハットに入社。レッドハットでは、JBoss Fuse Service Works、JBoss SOA Platform、JBoss WS、RESTEasyなどのインテグレーションミドルウェア製品に関する技術サポート、バグ修正、上流オープンソースプロジェクト(SwitchYard、JBoss ESB、JBoss WS、Apache Camel、Apache CXF)への貢献を行っている。ドメイン駆動設計(DDD)をはじめとするオブジェクト指向方法論や、システム間統合技術を専門としているが、最近は関数型プログラミングにも興味があり、勉強している。毎朝、娘を幼稚園に送り届けるのが日課。その後、自宅に戻って在宅勤務(Work From Home)をしている。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています