BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル JMSとSpring.NETを使用したメッセージ連携

JMSとSpring.NETを使用したメッセージ連携

ブックマーク

はじめに

.NETとJava間の相互運用には、様々なアプローチが存在します。最も一般的なアプローチの一つは、両方の環境で動くWSDLとXML Schemaを作り上げ、Webサービスを使用することです。あなたが期待するとおり、Webサービスはインターネットベースのアプリケーションにとってはベストな解です。しかしもし、一つの部門内や組織内の、LAN上で動作するイントラネットアプリケーションを書くのであれば、他のミドルウェア技術も魅力的なものとなります。特に、メッセージ指向ミドルウェア(MOM)は、単一の企業内で様々なプラットフォームを統合するのに一般的な選択肢です。この記事では、.NETとJavaの間のコミュニケーションの基盤としてMOMを使用し、ローカルLANで動作する単純な株取引アプリケーションの中で、.NETクライアントとJavaの中間層が連携するというデモンストレーションをお見せします。実装にあたっては、アプリケーションの層をまたがって共通のプログラミングモデルを提供するため、SpringフレームワークによるJMSサポート(Javaと同様.NETでも利用可能です)を使用します。

なぜメッセージングなのか?

いろいろな意味で、メッセージ指向ミドルウェアは相互運用性における長老のようなものだと考えられます。IBMやTIBCOといったベンダーは、異質なオペレーティングシステムが混在する環境で動作し、複数の言語バインディングを持つメッセージングミドルウェアを20年以上にわたり提供してきました。1999年、メッセージングベンダーが実装すべき共通のAPIセットと振る舞いを定義したJava Message Service(JMS)仕様が、メッセージングの世界に登場しました。これまでの遺産から、JMS実装が複数のオペレーティングシステム上で動作することは驚くに値しませんし、その全てではないにせよほとんどが.NET API1を提供しています。この記事の例では、Java、.NET、.NET Compact Framework、そしてC++クライアントAPIを提供している、TIBCOのJMS実装を使用します。付け加えますが、もしあなたが選んだベンダーが.NETクライアントを提供していないとしても、.NETからJMSにアクセスする方法は少ないながらも選択の余地が残されています。その一つに、 JNBridge2 やCodemesh3などの相互運用のための製品を使用する、というのがあります。

メッセージングを使う理由が、相互運用のためにやむを得ず、というのであれば、デファクトではないにせよ、アプリケーションのアーキテクチャにあわせた魅力的な選択肢が(メッセージング以外の)他にもたくさん存在します。要するに、MOMを使う利点はプロセス間の非同期な対話、パブリッシュ/サブスクライブ型(1対多)のメッセージ配信、そして高いレベルの信頼性というところにあるのです。もしあなたのアプリケーションがこうした機能から恩恵を受けられるのであれば、極めて高い確率で、メッセージングソリューションを使用するのが自然かもしれません。もしあなたの会社が既に、こうした理由からJavaもしくはC++ベースのアプリケーションのみからなるメッセージングを使用しているのなら、すでにあるアーキテクチャを論理的に拡張して.NETクライアントを含めるようその役割を広げることができます。そうする動機は十分にあります - JMSベースのソリューション構築には、独自の学習曲線とベストプラクティスの集合を持つからです。この記事は、.NETとJavaの上でSpringフレームワークを使用し、いかに素早くJMSアプリケーションを作成できるかをお見せします。また、.NETとJava間の対話にメッセージングを利用するあたって生じるいくつかの問題点に対する手引きを提供したいと思います。

Spring.NETの紹介

たくさんの人が、Springフレームワークを用いてJavaアプリケーションを構築することに慣れ親しんでいますが、その.NET対応版である Spring.NET4についてはあまりよく知られていません。Spring.NETはSpringフレームワークの.NETバージョンです。C#で全て構築されており、アプリケーション開発に対するSpringのデザインとアプローチを.NETに持ち込みます。Java版におけるコアな機能セット、例えば Inversion of Control、アスペクト指向プログラミング、WEBフレームワーク、RPC Exporters、宣言的トランザクション管理、そしてADO.NETフレームワークなどを備えています。要するに、もしあなたが既に "Spring.Java"のユーザであれば、Spring.NETにもすぐ馴染むことができるでしょう。

Spring.Javaが多くのサードパーティ製ライブラリを基本セットに含めているのとは違い、Spring.NETは別々にダウンロードすることができるモジュールとしてサードパーティ製ライブラリに対するサポートを分割しています。これらのモジュールの一つが、TIBCOのJMS実装上で JMSのサポートを提供しています。もしあなたがこの記事で使用しているサンプルを動かしたければ、TIBCOのウェブサイト5からTIBCO EMS(Enterprice Message Service)の評価版をダウンロードする必要があります。

"なぜ、Spring.NETはTIBCOのJMSはサポートしているのに、<JMSプロバイダの名前をここに入れる>はサポートしないの? "という疑問は当然のことです。他のベンダーがサポートされていないのは、大した理由があるわけではありません。現時点では、各ベンダーが実装すべき JMS APIのデファクトが.NETに存在していない(ためAPIが統一されていない)という現実的な理由からです。そんなわけで結局各ベンダーは、.NET上 に各々JavaのJMS APIに着想を得て、コピーしています。オープンソースプロジェクトである、.Net Message Service API(NMS)のゴールはそうした共通のAPIを提供することであり、将来はそれを使用してJMSをSpring.NET内で動かすというのが有望で す。私がTIBCOのJMS製品を最初にSpring.NETでサポートしようと考えたのは単に、非常に慣れ親しんでいたので都合が良かったためです。

Springフレームワークによるメッセージング

SpringによるJMSサポートのゴールは、JMSを使用する際の抽象度を高め、メッセージングにおけるベストプラクティスをサポートすることです。Springはこれらのゴールを達成するため、簡単に使えるクラス(複数)を提供しています。それらのクラスは共通処理を単純化し、MessageConverterを用いて、"plain old object"(POJO/POCO)プログラミングモデルによるメッセージングを可能にします。MessageConverterの責務はJMSメッセージと "plain old objects"の間の変換を行うことです。これは(Webサービスにおける)XML/オブジェクトマッパーと同じ考え方であり、それをJMS用に変形したものです。メッセージコンバータを使用すると、JMS関連のコードをあなたのアプリケーションの最も外層に追いやることができるため、アプリケーションの大部分がその技術的な詳細を知らずに済みます。ミドルウェアを切り替える必要が生じたとしても、JMSのMapMessageがビジネス処理内に散見する場合に比べると、リファクタリングが非常に容易です。

 JDBCに似て、JMSは低いレベルのAPIであり、最も基本的なJMSタスクの場合でさえ多くの仲介者的なオブジェクトを作成、管理する必要があります。メッセージの送信においては、SpringのJmsTemplateがこれらの仲介オブジェクトをあなたに代わって管理してくれるので、一般的な JMSオペレーションを"ワンライナー(1行プログラム)"にします。受信側においては、SpringのMessageListenerContainerのおかげで、メッセージを並行、かつ非同期に処理するための基盤を簡単に作ることができます。JmsTemplateと MessageListenerContainerはどちらも、JMSとPOJO/POCOの間の変換を行うためにMessageConverterと関連付けることができます。

JmsTemplateMessageListenerContainersMessageConvertersはSpring によるJMSサポートの中核をなすものです。この記事はこれらを広く使用し、多少は詳細についても解説しますが、完全なリファレンスというわけではありません。さらに詳しく知りたければ、Springのリファレンスドキュメンテーションか、その他のSpringに関する文献を当たってみて下さい。

Spring JMSにおけるHello World

Spring JMSと相互運用性の詳細に踏み込む前に、.NET上でJmsTemplateを使用して、"test.queue"と言う名前のJMSキューに対し、"Hello World"というテキストメッセージを送信する例を見てみましょう。

ConnectionFactory factory = new ConnectionFactory("tcp://localhost:7222");
JmsTemplate template = new JmsTemplate(factory);
template.ConvertAndSend("test.queue", "Hello world!");

もしあなたがJMS APIに精通しているなら、JMS APIを直接使用するのに比べて、JmsTemplateを使用するのがいかに単純かがすぐに見て取れるでしょう。JmsTemplateは本来コーディングが必要な、JMS接続、セッション、メッセージプロデューサなどのリソース管理に関して鋳型を提供するのが主な役目だからです。さらに JmsTemplateは、Javaにおけるチェック例外を非チェック例外に変換したり、JMSのあて先を文字列ベースで指定できるようにしたり、オブジェクトをJMSメッセージに変換するためにMessageConverterに処理を委譲したり、などのいろいろ便利な機能も提供します。もし変換なしでメッセージを送信したい時のために、JmsTemplateは単純なSendメソッドも持っています。

ConvertAndSendメソッドを呼び出した場合は、JmsTemplateはデフォルトのMessageConverter実装であるSimpleMessageConverterを使用し、文字列"Hello World!"をJMSのTextMessageに変換します。SimpleMessageConverterはまた、バイト配列からByteMessageへの変換、ハッシュテーブルからJMSのMapMessageへの変換もサポートしています。複雑なSpring JMSアプリケーションを作るための中核となる作業は、カスタムのMessageConverterを実装し、提供することです。既にアプリケーションコード内で使われているオブジェクトに対するコンバータを作成して、単純にJMS(メッセージ)へのマーシャル/アンマーシャルを行うのが一般的なアプローチです。

JMSの相互運用性についての簡潔な説明

Springを.NETとJava双方における共通のフレームワークとして使用した場合、相互運用性に関する問題は、MessageConverterの実装と、.NET、Java間でやり取りされる"plain old objects"の実装が互換性を持っているかどうかという点に集約されます。便宜的にそうした"plain old objects"のことを"ビジネスオブジェクト"と呼ぶことにしましょう。それらは実際には、完全に独立したドメインオブジェクト、データトランスファーオブジェクト、もしくはUIに表示するため最適化されたバージョンのドメインオブジェクトとなります。このアプローチにおけるビジネスオブジェクトとは、本質的に、二つの層間におけるデータ規約と成り得ます。そのフィールドとプロパティはデータ規約の一部であり、MessageConverterの実装からは独立しています。ここで注意すべきは、ある特定のデータ型については明確に共通なものがないと言うことです。片側で使用されているデータ型を、もう片方の側で互換性のある形で使用するには、それらに関連付けられたメッセージコンバータが責任を持って変換処理を行う必要があります。

このアプローチは確かに、Webサービスのようにまず規約ありき、というアプローチよりも強制力が弱いです。そのため、やり取りされるデータに強い制約を課したり、ささいなミスマッチを減らすためのいくつかのテクニックが存在します。まずこの点に関して、技術的ではない面から助けになるのは、イントラネット内もしくは部門別のアプリケーションは同じグループ(または人物)が.NETクライアントとJavaミドル層を開発していることが多いと言うことです。従って、異なる開発グループの間であらかじめ定めた規約を使用する、ということの拡張として、コミュニケーションや頻繁に行われる統合テストを捕らえなおすことができます。もしあなたが、層同士の連携においてあらかじめ定められた形式ばった規約を定義するのがお好みなら、JMSのアプローチは多分好きになれないのではないでしょうか。そうは言っても、相互運用性に対するこのゆるい、確かにあまり標準的ではないアプローチは、私の知る限りいくつかのプロジェクトで成功を収めてきています。

以下の節で、MessageConverterを使用して.NETをJavaの間でビジネスオブジェクトを同期しておくための、より洗練されたアプローチをお見せしたいと思います。サンプルアプリケーションは、Springにが持つSimpleMessageConverterを使用して、ハッシュテーブルのやり取りを行う所からスタートします。最終的には、ソースコードトランスレータや汎用的な"万能"MessageConverterを用いて、プラットフォームそれぞれにおいてコンバータとビジネスオブジェクトを手書きするという、重複した作業を減らすためのいくつかのテクニックをお見せします。それぞれのアプローチにおける様々な良い点と悪い点は、途中で議論します。

株取引サンプル

お見せするサンプルは、単純化した株式取引アプリケーションです。我々のアプリケーションは、三つの大きな機能を持ちます - 市場情報のリアルタイム配信、新しい取引の開始、取引におけるポートフォリオの検索です。

市場情報の配信を行うにあたって、我々はJavaの中間層から.NETクライアントにメッセージを送るためのJMSインフラを作成します。Javaの中間層では、メッセージの送信のためJmsTemplateが作成され、クライアント側ではメッセージの受信のためSimpleMessageListenerContainerが作成されます。どちらのクラスも、デフォルトではSimpleMessageConverterを使用してJMSメッセージとオブジェクトの変換を行います。市場データは、本質的にはキーと値のペアのコレクション - 例えば、PRICE=28.5, TICKER="CSCO"のような - です。そのため、.NET/Java間では単純なハッシュテーブルをデータのやり取りに使い、デフォルトのコンバータを使用します。

JmsTemplateとSimpleMessageListenerの双方に、JMS接続ファクトリとJMSあて先の名称、タイプに関する設定が必要です。SimpleMessageListenerContainerには、さらに、メッセージの処理を行うJMS MessageListenerコールバックの実装への参照を指定する必要があります。SpringはJMS MessageListenerインターフェースの実装クラスとして、MessageConverter を使用して受信したJMSメッセージをオブジェクトに変換する機能を持つ、MessageListenerAdapterクラスを提供しています。 MessageListenerAdapterは、MessageConverterを使用してオブジェクトに変換した後、ユーザが提供したハンドラオブジェクト内の、シグネチャが適合するメソッドを呼び出します。

前述の流れが、最もよくサンプルについて言い表しています。もしMessageListenerAdapterがSimpleMessageConverterを使用するように構成されていた場合、JMSの MapMessageを受け取った場合、.NETのIDictionaryに変換され、ハンドラクラスの"void handle(IDictionary data)"というシグネチャのメソッドが呼び出されます。もしコンバータがTrade型のオブジェクトを生成した場合は、ハンドラクラスはhandle (Trade trade)というメソッドを持つ必要があります。以下のシーケンス図がイベントの流れを表しています。

こうした委譲の仕組みは、Javaでは一般的に、ハンドラが通常の"plain old object"であるため"Message-Driven POJOs"や"Message-Driven objects"と呼ばれており、JMS MessageListenerが直接メッセージングのコールバックとしては働きません。このアプローチの利点の一つは、ハンドラメソッドを直接呼び出して、アプリケーションの流れを試すような統合スタイルのテストケースを作成するのが容易になるという点です。その他の利点としては、MessageListenerAdapterがメッセージングアプリケーションに良く見られるif/elseやswitch/caseの肩代わりをしてくれると言う点です。SimpleMessageListenerContainerは、ハンドラメソッドの戻り値に応じて返答メッセージを自動的に送信したり、処理の流れをカスタマイズするためにサブクラス化されることを意図している、など他にも機能がたくさんあります。詳しくはSpringのリファレンスドキュメントを参照してください。

これらのオブジェクトに対するSpringの設定を以下に示します。

中間層のパブリッシャ

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory"><ref bean="connectionFactory"/></property>
<property name="pubSubDomain" value="true"/>
<property name="defaultDestinationName" value="APP.STOCK"/>
</bean>

クライアントのコンシューマ

<object id="jmsContainer"
type="Spring.Messaging.Tibco.Ems.Listener.SimpleMessageListenerContainer, Spring.Messaging.Tibco.Ems">
<property name="ConnectionFactory" ref="connectionFactory"/>
<property name="PubSubDomain" value="true"/>
<property name="DestinationName" value="APP.STOCK"/>
<property name="ConcurrentConsumers" value="1"/>
<property name="MessageListener" ref="messageListenerAdapter"/>
</object>

<object
id="messageListenerAdapter"
type="Spring.Messaging.Tibco.Ems.Listener.Adapter.MessageListenerAdapter, Spring.Messaging.Tibco.Ems">
<property name="DelegateObject">
<object type="Spring.JmsInterop.Handlers.SimpleHandler, Spring.JmsInterop"/>
</property>
<property name="DefaultListenerMethod" value="HandleObject"/>
</object>

Spring.NETは、オブジェクトを定義する際'bean'ではなく'object'というXML要素を使用するのに注意してください。ハンドラオブジェクトは、"DelegateObject"という.NETの感覚から言うと少し気持ち悪い名前を持ちます。このプロパティは.NETの delegateとは何の関係もありません。

クライアントへ送られる全てのデータはAPP.STOCKというJMSトピック上に送信され、クライアントから(Javaの)中間層へはAPP.STOCK.REQUESTというキューを用いて全てのデータが送信されます。

JMSメッセージの送信は、市場データ自体はちょっと嘘っぽいですが、以下に示すように単純なものです。

Map marketData = new HashMap();
marketData.Add("TICKER","CSCO");
marketData.Add("PRICE", new Float(23.54));
... jmsTemplate.send(marketData);

受信側は、StockAppHandlerクラスのメソッド内で処理が行われます。

public class SimpleHandler
{
public void HandleObject(IDictionary data)
{
log.InfoFormat("Received market data. Ticker = {0}, Price = {1}", data["TICKER"], data["PRICE"]);
// forward to controller to update view
. . .

}
}

始めるにあたって最少量のインフラコードは全て整いました。しかしながら、ハッシュテーブルのやり取りは非常に簡単で、かつ設定一切なしで利用できるものの、これは最も単純な相互運用シナリオの場合にのみ適切です。私は、10個未満のキーと値のペアからなる、5個未満のデータのやり取りを以って単純と意味づけています。その理由は、より複雑なシナリオでは上手く広げていけないのは極めて明白であり、データの規約がゆるすぎて、二つの層の間でキーと値のペアにミスマッチが生じないようにするのが難しくなってきます。もっと自然なアプローチは、データをクラスにカプセル化する事です。そうしたクラスは、例えば、パラメータを持つコンストラクタを提供したり、サードパーティのバリデーションライブラリを用いるなどして、自身の持つデータの内容が正しいことを保証することができます。バリデーションはさておき、ハッシュテーブルを持ち込み、MessageConverterの要件に合わせていじくるよりも、業務的な処理に使用している中間層のオブジェクトを直接扱うほうが単純に楽です。そうして直接オブジェクトを使用して動かすためには、カスタムのメッセージコンバータを作成し、SpringのJMSインフラにプラグインする必要があります。

カスタムコンバータの使用

SpringのMessageConverterインターフェースは非常に単純です。以下に示すように、二つのメソッドを持ちます。

public interface IMessageConverter
{
Message ToMessage(object objectToConvert, .Session session);
object FromMessage(Message message);
}

メッセージの変換に失敗した場合、MessageConversionExceptionが投げられます。

我々は、クライアントから中間層に対し取引の開始をリクエストするメッセージを送信するため、カスタムのメッセージコンバータを作成します。 TradeRequestクラスは、取引の開始に際してユーザが入力した情報を収集し、またバリデーションを行うメソッドを持ちます。 TradeRequestクラスは以下のプロパティを持ちます - 銘柄、株式数、価格、注文タイプ、アカウント名、アクション(買い/売り)、リクエストID、ユーザ名です。コンバータの実装は、これらのプロパティを JMSのMapMessageに単純に追加していくだけのものです。クライアントにおける"ToMessage"メソッドの実装を以下に示します。

public class TradeRequestConverter : IMessageConverter
{
public Message ToMessage(object objectToConvert, Session session)
{
TradeRequest tradeRequest = objectToConvert as TradeRequest;
if (tradeRequest == null)
{
throw new MessageConversionException("TradeRequestConverter can not convert object of type " +
objectToConvert.GetType());
}
try
{
MapMessage mm = session.CreateMapMessage();
mm.SetString("accountName", tradeRequest.AccountName);
mm.SetBoolean("buyRequest", tradeRequest.BuyRequest);
mm.SetString("orderType", tradeRequest.OrderType);
mm.SetDouble("price", tradeRequest.Price);
mm.SetLong("quantity", tradeRequest.Quantity);
mm.SetString("requestId", tradeRequest.RequestId);
mm.SetString("ticker", tradeRequest.Ticker);
mm.SetString("username", tradeRequest.UserName);

return mm;

} catch (Exception e)
{
throw new MessageConversionException("Could not convert TradeRequest to message", e);
}
}

... (FromMessage not shown)
}

"FromMessage"メソッドの実装は、TradeRequestオブジェクトを作り、単純にメッセージから取り出した値を使用してそのプロパティにセットするだけです。詳細はコードを見てください。もしあなたがデータのマーシャル/アンマーシャルにプロパティを使用するなら、マーシャル時におけるこれらのプロパティの呼び出しが副作用を伴わないことを確認するのを忘れないでください。

クライアントから中間層にデータを送信するには、前出のJMSインフラをちょうど鏡に映したようなイメージを作成する必要があります。つまり、 JmsTemplateはクライアント側で、SimpleMessageListenerContainerは中間層側で使用します。中間層のメッセージコンテナはTradeRequestConverterのJavaバージョンを使用するよう設定し、メッセージハンドラクラスである StockAppHandlerは、メソッド"void handle(TradeRequest)"を持ちます。クライアント側の設定は以下のようになります。

<object name="jmsTemplate" type="Spring...JmsTemplate, Spring.Messaging.Tibco.Ems">
<property name="ConnectionFactory" ref="connectionFactory"/>
<property name="DefaultDestinationName" value="APP.STOCK.REQUEST"/>
<property name="MessageConverter">
<object type="Spring.JmsInterop.Converters.TradeRequestConverter, Spring.JmsInterop"/>
</property>
</object>

テンプレートを利用してハードコードされたクライアントは以下のようになります。

public void SendTradeRequest()
{
TradeRequest tradeRequest = new TradeRequest();
tradeRequest.AccountName = "ACCT-123";
tradeRequest.BuyRequest = true;
tradeRequest.OrderType = "MARKET";
tradeRequest.Quantity = 314000000;
tradeRequest.RequestId = "REQ-1";
tradeRequest.Ticker = "CSCO";
tradeRequest.UserName = "Joe Trader";

jmsTemplate.ConvertAndSend(tradeRequest);
}

クライアントにおける、メッセージ送信のシーケンス図を以下に示します。

中間層の設定と、POJOメッセージハンドラの単純な実装は以下のようになります。

<bean id="jmsContainer" class="org.springframework.jms.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationName" value="APP.STOCK.REQUEST"/>
<property name="concurrentConsumers" value="10"/>
<property name="messageListener" ref="messageListenerAdapter"/>
</bean>

<bean id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<property name="delegate">
<bean class="org.spring.jmsinterop.handlers.StockAppHandler"/>
</property>
<property name="defaultListenerMethod" value="handleObject"/>
<property name="messageConverter">
<bean class="org.spring.jmsinterop.converters.TradeRequestConverter"/>
</property>
</bean>

public class StockAppHandler {
protected final Log logger = LogFactory.getLog(getClass());
public void handleObject(TradeRequest tradeRequest)
{
logger.info("Recieved TradeRequest object");
}
}

このアプローチの利点は、クライアントと中間層が本質的にクラスを"共有"できることです。この場合はTradeRequestクラスであり、データと機能の両方を持ちます。クラスを共有することの欠点の一つは、大きなプロジェクトでは特に、ビジネスオブジェクトとコンバータを作成する手間が二重になることです。もし層の間でやり取りされるデータが比較的安定しているなら、こうした手間は一度きりのコストだと考えられます。しかし、開発が活発な間は一般的なことですが、もしやり取りされるデータが週ごと、日ごとに元から変わってしまう場合には、とても面倒なタスクと成り得ます。

これらの問題に対処する効率のよい方法は、複数のメッセージタイプを処理できる単一の万能MessageConverterを作成すること、そしてビジネスオブジェクトをJavaで一度だけ書き、ソースコード変換ツールを使用して同等のC#コードを手に入れることです。次の節では、このアプローチについてさらに詳しく論じます。

汎用的なメッセージコンバータ

TradeRequestConverterのコードで前に見たような実装は、手作業でコーディングすべきではありません。解決策としては、コード生成かリフレクションを代わりに使用することです。サンプルコードには、'XStream'に着想を得たリフレクションベースのコンバータであるRefrectionMessageConverterが含まれており、広範囲のオブジェクトを変換することができます。このコンバータは以下のような機能と制限を持ちます。

  • 全てのフィールドメンバは、リフレクション経由でその値がマーシャルされます。プロパティのsetter/getterが持つかもしれない副作用を避けるためにこうした選択がなされました。(WCFのDataContract/DataMemberアトリビュートと同様の、どのフィールドが包含/除外されるかを制御するための外部設定、もしくはアトリビュート/アノテーションのサポートを追加することは、優れた拡張と言えるでしょう。)
  • サポートされているフィールド型は、プリミティブ型(int、stringなど)、Date、Timestamp、プリミティブ型で構成されたオブジェクト、ハッシュマップ、オブジェクトの(総称でない)コレクションです。循環参照は検出されます。
  • オブジェクト同士の結びつきは、容易に理解可能なフォーマットのJMS MapMessageとして表現されます。これにより、このコンバータを使用していない他のプロセスがJMSメッセージのやり取りに参加することが容易になります。
  • JMSプロバイダは、コレクションクラスの変換のため、入れ子のマップメッセージをサポートしている必要があります。
  • 任意のオブジェクト型に対する追加のコンバータを登録することができます。
  • コンバータが、コンバートされる先の型を知る必要がないため、.NET/Java間の結合度が疎になります。型の識別子はメッセージ内に保持されており、何という型のオブジェクトを生成すべきかを表しています。どちらの側でも、型の識別子は特定のデータ型からは独立しています。全てのビジネスオブジェクトが、名前空間/パッケージの違い以外全て同じ名前であるという場合に、最小限の設定で済むため便利です。

そのコンバータは現在も作成が進行中だとみなされており、Spring.NETウェブサイトから利用できるようになるべく改善が行われています。

その他の、単一の万能MessageConverterというアプローチで代わりになるものとしては、既存のマーシャリング技術に面倒ごとを委譲するなどがあります。例えばXML/オブジェクトコンバータに委譲し、XML文字列をJMSメッセージに載せて送信する事ができます。最近では、Tangosol 社はプラットフォームと言語から中立なPortable Object Format(POF)を(同社の製品である)Coherenceの一部として提供しており、同じ目的に利用できます。

サンプルアプリケーションは TradeRequestのレスポンスとしてTradeオブジェクトをクライアントに送る際RefrectionMessageConverterを使用しています。更に複雑なオブジェクトの例としては、クライアントがPortfolioRequestを送信し、UserとTradeのリストを含んだ Portfolioオブジェクトをレスポンスとして受信するというものもあります。コンバータに関する設定は以下の通りです。

<object name="reflectionMessageConverter"
type="Spring.JmsInterop.Converters.ReflectionMessageConverter, Spring.JmsInterop">
property name="ConversionContext" ref="conversionContext"/>
</object>

<object name="conversionContext" type="Spring.JmsInterop.Converters.ConversionContext, Spring.JmsInterop">
<property name="TypeMapper" ref="typeMapper"/>
</object>

<object name="typeMapper" type="Spring.JmsInterop.Converters.SimpleTypeMapper, Spring.JmsInterop">

<!-- use simple configuation style -->
<property name="DefaultNamespace" value="Spring.JmsInterop.Bo"/>
<property name="DefaultAssemblyName" value="Spring.JmsInterop"/>

<!--
<property name="IdTypeMapping">
<dictionary>
<entry key="1" value="Spring.JmsInterop.Bo.Trade, Spring.JmsInterop"/>
</dictionary>
</property>

-->
</object>

上で使用されている、TypeMapperの単純なスタイルのほうの設定は、マーシャリングの際メッセージ内に含められた、完全修飾型名の最後の部分を型の識別子として使用します。DefaultNamespaceとDefaultAssemblyNameプロパティは、アンマーシャリングの際に完全修飾型名を構築するのに使用されます。Javaの側において型マッパーの定義に相当する部分は以下に示すとおりです。

<bean id="classMapper" class="org.spring.jmsinterop.converters.SimpleClassMapper">
<property name="defaultPackage" value="org.spring.jmsinterop.bo"/>

<!--
<property name="idClassMapping">
<map>
<entry key="1" value="org.spring.jmsinterop.bo.Trade"/>
</map>
</property>
-->


</bean>

(コメントアウトされている)IdTypeMapping/IdClassMappingプロパティは、完全なクラス名の使用を避けるためにはどうしたらよいか、型を指定するために任意の識別子を使用するにはどうしたらよいか、を表しています。

ビジネスオブジェクトを共有する

ビジネスオブジェクトを同期しておくための手間を減らすことのできるテクニックの一つに、Java Language Converter(JLCA)を使用してJavaクラスをC#7のものに自動的に変換する、というものがあります。このツールはJavaコードベースの" 一度きりの"翻訳を意図しており、自動化されたビルドプロセスに取込、Javaと.NET間でビジネスオブジェクトを同期するのに使用できます。実際ビジネスオブジェクトは、データアクセスやWebプログラミングなど手作業による修正なしには正しく変換するのが困難なテクノロジ固有のAPIを含まないため、この変換ツールにとって非常に良い候補者です。

しかしながら、JLCAには短所もあります。手作業による介入なしにJavaへと変換できる複雑なC#クラスを書くことができるにもかかわらず、様々な制限や奇妙な動作が存在します。最も目だっておかしいのは、JavaBeanのget/setメソッドが.NETプロパティに変換されるのは良いとして、メソッド名が小文字のままになってしまう点です。他の制限としてはアノテーションはアトリビュートに変換されない、ジェネリクスについてのサポートがない、などです。名前空間がJavaのパッケージ名のまま残ってしまうのは、単純な正規表現を用いて後処理を行えば簡単に修正できます。またこのツールは、必要に応じていくつかのサポートされているクラスの C#実装を作成します。例えば、java.util.SetのC#版などです。あなたが自身のプロジェクトでこのテクノロジーをどこまで使用できるかは、少し検証作業を行ってみてください。Gaurav Sethのブログ8に、このコンバータの機能をまとめた"カンニングペーパー"があります。最後に、JLCAに関するちょっとした情報を一つ。JLCAの背後にある企業、ArtinSoftはJLCA Companionという翻訳ルール9を変更、または追加できる製品も販売しています。

この例では、Javaクラスに対してJLCAを動作させるのは非常にうまく行きます。.NETソリューション内で"Bo"ディレクトリと"Jlca"ディレクトリをインクルード/エクスクルードすることにより、手作業で記述したC#のビジネスオブジェクトと、JLCAが生成したそれは相互に切り替えることができます。特に、TradeRequestクラス内のバリデーションメソッドは見て/いじってみてください。単純な条件分岐のロジックとコレクションクラスの操作を使用しています。Javaビジネスオブジェクトに対してJLCAを走らせ、パッケージ名を正しく.NET名前空間に変更する、antスクリプトもサンプルコードに含まれています。

以下に示すのは、いくつか市場データを受信し、TradeRequestとPortfolioRequestを送信した直後の、クライアントのスクリーンショットです。

結論

もしあなたが既にメッセージングを行っているか、非同期のコミュニケーションやパブリッシュ/サブスクライブ型配信などのメッセージング機能を必要としているなら、SpringのJMSサポートをJavaと.NETの双方で利用することは、相互運用ソリューションへの非常にパワフルな第一歩と成り得ます。 Spring(を使用したメッセージング)は、JMSのプロデューサとコンシューマの間の互換性を維持するための規約という点からはあまりがっちりしたものではありませんが、あなたが考え出した規約を取り入れるためのシンプルな拡張ポイントであるMessageConverterを提供します。コンバータとそれに関連したオブジェクトの洗練度が、あなたのアプリケーションの複雑さを左右します。この株取引アプリケーションと RefrectionMessageConverterは、あなた自身が簡単に実験を行うための土台として使用できます。

一般的にSpringフレームワークについて言われていることの繰り返しになりますが - "易しい事柄は易しいままに、難しい事柄を可能にします(it makes the easy stuff easy and the hard stuff possible)"。私はこの言葉が、.NET/Javaが混在した環境におけるSpringのJMSサポートについても当てはまると良いと思っています。最後になりますが、相互運用についてどんな方法を選択したかに関係なく、.NETとJava両方の環境でSpringを使用することは非常に有益です - なぜなら二つの世界で同じプログラミングモデルとベストプラクティスを簡単に共有できるからです。

この記事で使用したソースコードはこちら(zip)からダウンロードできます。


この記事に星をつける

おすすめ度
スタイル

BT