今回はServiceMixにデプロイするServiceEngine(SE)の作り方を考えていきたいと思います。特にビジネスロジックであるInfoProviderです。
何度も書いていますがInfoProviderは条件に応じて情報を返す仕組みです。しかし、いかにServiceMixが簡単とはいえJBIの仕様にそったメッセージ送信を書くのは大変です。そこで、InfoProviderのコアであるロジック部分と、それをServiceMix(JBI)上で動かすためのコードを分離してしまう方法を考えましょう。参考にするのはServiceMix自身がアクティベートで使っているテクニックです。
まず、InfoProviderをServiceMixに依存しないように考えると、次のようなインターフェースになります。
package demo.info;
import java.util.Map;
public interface InfoProvider {
Info[] getInfos(Map condition);
}
また、クラスInfoは次のようになります。
package demo.info;
public class Info {
private String id;
private String lat;
private String lng;
private String imgSrc;
private String title;
private String message;
//各GetterとSetter
この状態ではパラメタをクラスMap、返却をクラスInfoによって行っており、ServiceMixに否依存であり、単体テストも非常に簡単に行えることがわかります。実装例は次のとおりです。
package demo.info.impl;
import java.util.Map;
import demo.info.Info;
import demo.info.InfoProvider;
public class InfoProviderImpl1 implements InfoProvider {
public Info[] getInfos(Map condition) {
Info[] infos = new Info[2];
infos[0] = new Info("simple1_1", "34.991305", "135.750579", "", "本願寺(西本願寺)", "...");
if ( "family".equals(condition.get("group")) ) {
infos[1] = new Info("simple1_2", "35.010271", "135.768378", "", "本能寺(家族向き)", "...");
}
else if ( "friends".equals(condition.get("group")) ) {
....
}
return infos;
}
}
しかし、このままではServiceMix上にデプロイすることはできません。そこで、これにServiceMix用のアダプタをつけて処理を行わせることにします。アダプタの役割は、
1.JBIのメッセージで届けられたパラメタのXMLをクラスMapに変換
2.InfoProviderを呼び出し
3.実行結果のクラスInfoをXMLにしてメッセージを返却
です。
アダプタを利用するのはDIを利用します。アダプタクラス側にInfoProviderを属性として用意し、そこに実行したいInfoProviderの実装をインジェクトするのです。前エントリで書いたservicemix.xmlを見ると、クラスdemo.servicemix.se.Adaptorの属性infoProviderに対してInfoProviderの実装クラスdemo.info.impl.InfoProviderImpl1がインジェクとされています(赤い部分)。
<sm:activationSpec componentName="provider1"
service="foo:provider1"
destinationService="foo:findAggregator">
<sm:component>
<bean xmlns="http://xbean.org/schemas/spring/1.0"
class="demo.servicemix.se.Adaptor">
<property name="infoProvider">
<bean class="demo.info.impl.InfoProviderImpl1" />
</property>
</bean>
</sm:component>
</sm:activationSpec>
このXMLをみるとactivationSpecによってJBIへの登録を行い、componentによってコンポーネントの初期化を行い、クラスAdaptorによってJBIの処理を隠蔽していることになります。
このように機能の合成をインジェクトによって簡単に表現できるのがDIの特徴です。これまでであれば継承による機能の合成をしていたものが、インジェクトによって実現しているようにも感じています。
では、アダプタの実装を見ながら、ServiceMixでの実装方法を見ていきましょう。
package demo.servicemix.se;
import //略
public class Adaptor extends ComponentSupport implements
MessageExchangeListener { ...[1]
private InfoProvider infoProvider; ...[2]
public void setInfoProvider(InfoProvider infoProvider) {
this.infoProvider = infoProvider;
}
private SourceTransformer transformer = new SourceTransformer(); ...[3]
public void setTransformer(SourceTransformer transformer) {
this.transformer = transformer;
}
private boolean encode = false; ...[4]
public void setEncode(boolean encode) {
this.encode = encode;
}
public void onMessageExchange(MessageExchange exchange)
throws MessagingException { ...[5]
if (exchange.getStatus() == ExchangeStatus.DONE) { ...[6]
return;
} else if (exchange.getStatus() == ExchangeStatus.ERROR) {
done(exchange);
return;
}
NormalizedMessage in = getInMessage(exchange); ...[7]
Node docNode = null;
try {
docNode = transformer.toDOMNode(in.getContent()); ...[8]
} catch (Exception e) {
e.printStackTrace();
fail(exchange, e);
}
Map queryParam = Util.createQueryMap((Element) docNode.getChildNodes().item(0)); ...[9]
Info infos[] = infoProvider.getInfos(queryParam); ...[10]
Element resultElem = Util.toElement(infos, encode); ...[11]
InOnly inOnly = getExchangeFactory().createInOnlyExchange(); ...[12]
NormalizedMessage out = inOnly.createMessage(); ...[13]
out.setContent(new DOMSource(resultElem));
inOnly.setInMessage(out);
send(inOnly); ...[14]
}
}
[1]まず、ServiceMix用のSEを実装するにはインターフェースMessageExchangeListenerを実装します。このメソッドonMessageExchangeを実装することでJBIのアクセプトイベントをハンドリングすることができます。また継承しているクラスComponentSupportは、その名のとおりJBI上のコンポーネントを実装するための基底クラスとして利用することができます。
[2]実際に実行するInfoProviderのインスタンスをインジェクとしてもらうために属性InfoProviderを用意します。
[3]SourceTransformerはJBIで用意されたJAXPのユーティリティクラスです。
[4]これは本質的ではないのですが、URLエンコードを行うかどうかをしめすためのフラグです。デモでは別サーバインスタンスのServiceMixにREST経由で処理を投げる場合に日本語のままだと文字化けしてしまいます。そこでURLエンコードするわけです。
[5]メソッドonMessageExchangeがインターフェースMessageExchangeListenerで定義されたものです。引数のクラスMessageExchangeはJBIにおけるメッセージ交換ためのオブジェクトです。
[6]JBIでは、メッセージが相手に届いて処理が終了したことをコールバックしてくれます。MessageExchangeのメソッドgetStatusによって状態を知ることができます。DONEは正常終了、ERRORは異常終了を示しますが、どちらも特には処理を行いません。
[7]ComponentSupportのメソッドgetInMessageによって、MessageExchangeから入力(In)のメッセージNormalizedMessageを取り出します。MessageExchangeが行き先やステータスをのあらわすのに対して、NormalizedMessageはメッセージそのものを示し、XML、バイナリの添付、任意のプロパティなどを持ちます。この丸山先生の絵がわかりやすいでしょうか。

[8]NormalizedMessageから、さらに生のXMLを取り出してDOMに変換しています。
[9]DOMを操作して、パラメタ用のMapを作ります。やっていることは単純でエレメントの名前とノード値をMapにいれているだけです。次のようなXMLであれば、numberOfPerson:4と、group:familyということになります。
<request>
<numberOfPerson>4</numberOfPerson>
<group>family</group>
<request>
[10]いよいよInfoProviderを呼び出します。
[11]結果としてInfoの配列が取得できたので、今度はそれをDOMに変換します。こちらもやっていることは単純で、次のようなXMLを作っているだけです。
<infos>
<info id="simple1_1" lat="34.991305" lng="... />
...
</infos>
[12]では、メッセージの返事を行います。まずメソッドgetExchangeFactoryを利用して、このSEに設定されたクラスMessageExchangeFactoryを取得し、非同期(InOnly)の返信用MessageEchangeを作ります。ちなみにメソッドcreateExchangeを使えば明示的にサービスに対応したMessageExchangeを作ることもできます。
[13]次に返信用MessageExchangeのメソッドcreateMessageを利用してNormalizedMessageを作ります。そこに先ほどのInfoをXML化したものを流し込みます。
[14]最後に返信用MessageExchangeをNMRに送信して終了です。内部的にはデリバリーチャンネルを取得して、そこに送信を行っています。
こうしてみてもらうと難しそうな、簡単そうな、微妙な感じだと思います(w。ただ、メインのビジネスロジック自体は、このアダプタを使うことによってシンプルに保たれていることがわかりますから、JBIに依存する部分と、しない部分を分離していくという戦略自体に問題はないでしょう。
僕がServiceMixの課題と書いたのはアダプタにあたる部分のサポートです。StrutsがHTTPリクエストのパラメタをActionFormにしたように、JBIのメッセージをもっと簡単にハンドリングする機構があると便利なはずです。たとえば、ESBメッセージング・フレームワークのMuleであれば、BindingComponentにあたる層にトランスフォーマー機構が用意されていて、ある程度は自動的にメッセージをオブジェクトにバインドしてくれます。
なお、今回のデモではクラスAdaptor以外にも、いくつかのクラスを作りましたが、汎用的な機能です。今後はライブラリとして用意されていくか、あるいはBPELやXSLTのようなプロセス・エンジンが使われるようになると思います。
なんにせよServiceMixはまだまだ発展途上です。なので複雑なことをさせるとか、業務アプリに使うといった場合には十分に注意してください。バージョン2.0といっても、ベータぐらいの気持ちで見てあげると良いでしょう。今後、広く普及していくにしたがって使い方が検討されていくと思っています。
これで一通りのデモアプリ解説は終了です。なにかあればエントリを追加します。もしくは解説して欲しいことがあればメール(yusukeあっとarclamp.jp)かコメントをくださいませ。
このServiceMix連載の一覧
]]>