第6章 サンプル

6.1. メッセージの使用方法

コンセプト的には、メッセージは SOA 開発の手段において非常に重要なコンポーネントで、クライアントとサービスの間で送信されるアプリケーション固有のデータを含んでいます。メッセージの内容は、サービスとそのクライアント群間の規定に関する重要な側面を表しています。本セクションでは、メッセージに関するベストプラクティスや使用法について説明します。
まず、フライト予約サービスを使用する次のサンプルを見てみましょう。このサービスは以下の操作をサポートします。
reserveSeat
これはフライト番号と座席番号をとり、成功または失敗の表示を返します。
querySeat
これはフライト番号と座席番号をとり、その座席が現在予約されているかどうかの表示を返します。
upgradeSeat
これはフライト番号と 2 つの座席番号をとります (現在予約されている座席と移動先の座席)。
このサービスを開発する場合、 ビジネスロジックを実装するために Enterprise Java Beans (EJB3) や Hibernate などの技術を使用する可能性が高くなります。このサンプルでは、どのようにビジネスロジックが実装されるのかについては触れずサービスの方に集中することとします。
このサービスの役割は、ロジックをそのバスに接続することです。これを行うためには、クライアントに対して定義する規定など、サービスがバスに対してどのように公開されるかを判断する必要があります。現在の JBoss Enterprise Service Bus バージョンでは、この規定はクライアントとサービスが交換できるメッセージの形式をとっています。ESB 内ではこの規定に関する公式な仕様はありません。現在、仕様は開発者が定義し、ESB から帯域外でクライアントと通信しなければなりません。これについては今後のリリースで修正される予定です。

注記

現在、ESB にはこのコントラクトに対する正式な仕様はありません。言い換えると、開発者が Enterprise Service Bus に関係なく定義し、クライアントとやり取りを行うものです。この点については、今後のリリースで修正されます。

6.1.1. メッセージの構造

サービスの観点では、メッセージ内のすべてのコンポーネント中でボディが最も重要となります。これは、ビジネスロジック固有の情報を伝達するためにボディが使用されるからです。対話するには、クライアントとサービスの両方がお互いを理解しなければなりません。これには、トランスポートに同意する形式 (JMS や HTTPなど) やダイアレクトに同意する形式 (メッセージデータの表示や対応する形式) をとります。
クライアントがメッセージをフライト予約サービスに直接送信するといったシンプルなケースの場合、メッセージに関するオペレーションをサービスが判断する方法を確定する必要があります。この場合、org.example.flight.opcode と呼ばれる場所にて opcode (オペレーションコード) が文字列 (reservequeryupgrade) としてボディ内に表示されることを開発者が決定します。その他の文字列値 (または値の指定がない場合) は不正なメッセージとしてみなされます。

重要

メッセージ内のすべての値に固有の名前を与えて他のクライアントやサービスとのクラッシュを避けるのが重要となります。
メッセージボディはクライアントとサービス間でデータが交換される主要な手段となります。すべての任意データタイプの数を格納することができる十分な柔軟性を持ち合わせています (各オペレーションに関連するビジネスロジックを実行するために必要となる他のパラメーターも適切にエンコードされます)。
  • 座席番号には org.example.flight.seatnumber を使用します (整数)。
  • フライト番号には org.example.flight.flightnumber を使用します (文字列)。
  • アップグレードする座席番号には org.example.flight.upgradenumber を使用します (整数)。

表6.1 オペレーションのパラメータ

オペレーション opcode seatnumber flightnumber upgradenumber
reserveSeat String: reserve 整数 文字列 N/A
querySeat String: query 整数 文字列 N/A
upgradeSeat String: upgrade 整数 文字列 整数
前述の通り、これらのオペレーションはすべてクライアントに情報を返します。こうした情報は同様にメッセージ内でカプセル化されます。ここで説明するプロセスと同じやり方で、こうした応答メッセージの形式が判断されます。説明が難しくなるため、ここでは応答メッセージについては考慮しません。
JBossESB のアクションの観点では、サービスは 1 つ以上のアクションを使用して構築することができます。たとえば、メインのビジネスロジックに関与するアクションに受信メッセージを渡す前に、アクションは受信メッセージを事前処理し、その内容を何らかの方法で変換することができます。各アクションは分離して記述されている可能性があります (同じ構成内の別のグループまたは完全に異なる構成など)。各アクションは対応する Message データの独自のビューを持つことが重要となります。そうでない場合、チェーンされたアクションによって上書きされたり、アクション同士が干渉し合う可能性があります。

6.1.2. サービス

この時点でサービスを構成できるくらいまで学習されました。シンプルにするため、ビジネスロジックは次の疑似オブジェクト内でカプセル化されていると仮定します。
class AirlineReservationSystem
{
	public void reserveSeat (...);
	public void querySeat (...);
	public void upgradeSeat (...);
}	

注記

ビジネスロジックは POJO、EJB、Spring などで開発が可能です。JBoss Enterprise Service Bus ではこれら多くの手段について特に設定をすることなくそのままの状態によるサポートを提供します (関連のドキュメントとサンプルをお読みください)。
サービスアクションの処理は以下のようになります。
public Message process (Message message) throws Exception
{
	String opcode = message.getBody().get(“org.example.flight.opcode”);
	
	if (opcode.equals(“reserve”))
		reserveSeat(message);
	
	else if (opcode.equals(“query”))
		querySeat(message);
	
	else if (opcode.equals(“upgrade”))
		upgradeSeat(message);
		
	else
		throw new InvalidOpcode();
	
	return null;
}	

注記

WS-Addressing と同様、メッセージ内に組み込まれた opcode ではなく、メッセージヘッダーの action フィールドを使用することができます。この欠点は、複数の JBossESB アクションがチェーン化され、各アクションに異なる opcode が必要な場合は機能しないことです。

6.1.3. ペイロードのデコーディング

ご覧の通り、process メソッドはスタート地点にすぎません。次に、受信のメッセージペイロードをデコードするメソッドを提供しなければなりません。以下のように行います。
public void reserveSeat (Message message) throws Exception
{
	int seatNumber = message.getBody().get(“org.example.flight.seatnumber”);
	String flight = 
        message.getBody().get(“org.example.flight.flightnumber”);
	
	boolean success = 
        airlineReservationSystem.reserveSeat(seatNumber, flight);
	
	// now create a response Message
	Message responseMessage = ...
	
	responseMessage.getHeader().getCall().setTo(
        message.getHeader().getCall().getReplyTo()
    );
    
	responseMessage.getHeader().getCall().setRelatesTo(
        message.getHeader().getCall().getMessageID()
    );
	
	// now deliver the response Message
}
このメソッドは、ボディ内の情報がどのように抽出され、ビジネスロジックでメソッドを呼び出すのに使用されるかを表しています。reserveSeat のケースでは、クライアントにより応答が予期されます。この応答メッセージは、ビジネスロジックにより返される情報や、元の受信したメッセージより取得した配信情報を使って構成されます。この例の場合、受信メッセージの ReplyTo フィールドより取得する To アドレスが応答に必要となります。また、応答を元の要求に関連させる必要がありますが、これは応答の RelatesTo フィールドと要求の MessageID を使用して関連させます。
サービスでサポートされるこの他すべてのオペレーションは同様にコード化されます。

6.1.4. クライアント

サービスによりサポートされるメッセージ定義があれば、クライアントコードを作成できます。サービスをサポートするために使用するビジネスロジックは、サービスによって直接公開されることはありません (SOA の重要な原則の 1 つであるカプセル化に反するため)。クライアントコードは、基本的にサービスコードの反対になります。
ServiceInvoker flightService = new ServiceInvoker(...);
Message request = // create new Message of desired type

request.getBody().add(“org.example.flight.seatnumber”, 1);
request.getBody().add(“ org.example.flight.flightnumber”, “BA1234”);

request.getHeader().getCall().setMessageID(1234);
request.getHeader().getCall().setReplyTo(myEPR);

Message response = null;

do
{
	response = flightService.deliverSync(request, 1000);
	
	if (response.getHeader().getCall().getRelatesTo() == 1234)
	{
	// it's out response!
	
	break;
	}
	else
	response = null;  // and keep looping
	
} while maximumRetriesNotExceeded;	

注記

上記の多くは、従来のクライアント/サーバースタブジェネレーターと同様であるように見えるかもしれません。これらのシステムでは、(opcodes やパラメーターなどの) 低レベルの詳細は高レベルのスタブ抽象化の背後に隠されることになります。JBossESB の今後のリリースでは、開発アプローチを緩和するためこうした抽象化をサポートしていく予定です。ボディやヘッダーなどローメッセージコンポーネントの作業は、ほとんどの開発者から見えないようになります。

6.1.5. リモートサービス呼び出しの設定

カスタマイズなしに ESB のアクションから ServiceInvoker を使用できることがわかります。別途設定は必要ありません。しかし、リモートの Java 仮想マシンから使用するには (スタンドアローンの Java アプリケーション、サーブレット、Enterprise Java Bean などの場合)、まず以下の JAR ファイルが利用できる状態であるかを確認する必要があります。
jbossesb-rosetta.jartrove.jar
jbossesb-config-model-[version].jarjuddi-client-[version].aop.jar
jbossts-common.jarjuddi-core-[version].aop.jar
log4j.jarcommons-configuration-[version].jar
stax-ex.jarcommons-lang-[version].jar
stax-api-[version].jar jboss-messaging-client.jar
jbossall-client.jarjboss-remoting.jar
scout-[version].aop.jar commons-codec-[version].jar
xbean-[version].jarwstx.jar
commons-logging.jarxercesImpl.jar
javassist.jar 

注記

これらのファイルは、$SOA_HOME/jboss-as/client/, $SOA_HOME/jboss-as/common/lib/$SOA_HOME/jboss-as/server/$SOA_CONF/deployers/esb.deployer/lib/ にあります。
次に、以下のファイルがクラスパスにあるか確認します(quickstarts ディレクトリにあります)。
  • jbossesb-properties.xml
  • META-INF/uddi.xml

6.1.6. サンプルクライアント

以下の Java プログラムを使用して、リモートクライアントの設定が正しく機能しているかどうかを確認します (まず、helloworld quick-start がデプロイされており、Enterprise Service Bus サーバーが実行中であることを確認します)。
package org.jboss.esb.client;

import org.jboss.soa.esb.client.ServiceInvoker;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.format.MessageFactory;

public class EsbClient
{
    public static void main(String[] args)
    {
        System.setProperty("javax.xml.registry.ConnectionFactoryClass",
                "org.apache.ws.scout.registry.ConnectionFactoryImpl");
        try
        {
            Message message = MessageFactory.getInstance().getMessage();
            message.getBody().add("Sample payload");
            ServiceInvoker invoker = new ServiceInvoker("FirstServiceESB", "SimpleListener");
            invoker.deliverAsync(message);
        }
        catch (final MessageDeliverException e)
        {
            e.printStackTrace();
        }
    }
}

6.1.7. コツとヒント

以下のヒントやコツは、クライアントとサービスの開発に役立つはずです。
  • アクションを開発する際、アクション固有のペイロード情報は必ずメッセージボディ内の独自の場所で維持するようにしてください。
  • メッセージ内でバックエンドサービス実装の詳細を公開しないようにしてください。公開してしまうと、クライアントに影響を与えずに実装を変更することが難しくなります。実装にとらわれないメッセージ定義 (内容や形式など) は、疎結合を維持できるようにします。
  • ステートレスサービスの場合は、フェールオーバーを不透明に処理するため ServiceInvoker を使用します。
  • 要求/応答のアプリケーションを構築する場合は、メッセージヘッダー内で相互関係の情報 (MessageIDRelatesTo) を使用します。
  • メインのサービス opcodeHeader Action フィールドを使用するよう考慮してください。
  • 応答用の配信アドレスのない非同期の対話を使用する場合、後で監視できるようすべてのエラーを message store に送信することを考慮してみてください。
  • JBoss Enterprise Service Bus がサービス規定の定義や公開に対してより自動的なサポートを提供できるようになるまで、開発者とユーザーが使用できる定義のレポジトリを個別に維持するようにしてください。