レベル: 中級 James Snell (jasnell@us.ibm.com), Software Engineer, IBM
2004年 10月 19日 どのようにして明確かつ証明済みの Web アプリケーションの設計戦略を Web サービスの世界に応用するかを学習しましょう。このシリーズでの最初の記事にて、Java Messaging Service(JMS)のキューを使用することによりどのようにして非同期の照会オペレーションを実装するかを披露します。
SOAPでWebサービスを実装することを考慮する場合、同期的な要求/応答の形式を取り入れた操作を前提としていることでしょう。しかしながら、正真正銘のSOA(Service-Oriented Architecture)は、遥かに広範囲にわたるメッセージ・パターンと設計戦略を取り扱う能力を持ちます。この記事では、Webサービス・アプリケーションへのWebアプリケーションの基本的な設計パターンの応用に焦点を合わせる短いシリーズを開始します。紹介されるパターンは目新しくなく、実を言えばより伝統的なWebアプリケーションにて長年にわたり使われてきました。しかし、Webサービスの領域ではそのような戦略を実装する方法を多くの開発者が知らないか、それらの戦略の応用をまだ完全に考慮に入れていないかのどちらかなのです。シンプルでわかりやすい設計の別の方法をいくつか要求/応答のモデルに取り入れるのが、ここでの目的です。J2EEの環境にてWebサービスを実装する方法を(少なくとも基本的に)理解をしているのであれば、この記事で紹介される例を把握するのが少しは楽になります。
非同期照会パターン
まず手始めに、コード実行におけるタイムアウトや長時間にわたるハングアップを回避するために長期的な操作に割り込むことを目的とした非同期要求/応答の操作の実装を探究します。伝統的なHTTPそしてHTMLの Webアプリケーションでの非同期照会の実装を既に経験されているのでしたら、ここで実装されるパターンは見慣れたものでしょう。
図1. 非同期照会パターン
このパターンのフローはシンプルです。
- (Requester が後の段階で要求(Request)の状況をチェックするために使用できる相関ID(correlation ID)を戻しメッセージをキューする)Service Providerへの要求(Request)を要求者(Requester)は送信します。
- 要求処理プログラムは要求(Request)をデキューしてそれを処理します。一般的には要求の処理には時間がかかります。処理が一度終了すればProcessor は応答(Response)メッセージをキューします。
- 未来の不確定な時点にて、要求(Request)に対する応答(Response)の準備ができたかどうかをRequester がService Provider に問いかけます。応答(Response)がキューされたら、Provider は要求者(Requester)にその応答(Response)を戻します。応答(Response)が有効でなければ、Provider はその旨を(応答が使用可能になるまで選択された任意のインターバルにてProvider をポーリングしながら、要求(Request)をキャンセルするか待機を続行するかを選べる)要求者(Requester)に報告します。
Java開発リソースのWebサイトであるJava RanchのJ2EE Servlet アプリケーションにて、このパターンのアプリケーションに関する優れた記事(参考文献を参照)をKyle Brown氏は著作しました。この記事にて、このパターンの基本的な実装に関与する根本的な設計の論点と動機について、著者は適切に考察しています。このパターンのWebサービス実装にて実装はあまり変化していませんが、それは別段驚くべきことでもありません。
サービス・インターフェースを設計
非同期照会パターンを図解するには、パターンの概念を図解する以外の実用的な価値を全く持たないWebサービスの簡単な例を実装します。そのサービスがすることは、長時間実行される処理をシミュレートするために実行中に10秒間の一時停止を強制した後に3つの小文字のString 入力値を大文字に変換することだけです。
このサービスを実装するには、2つのWebサービス・オペレーション(submitRequest そしてcheckResponse)が公開されます。それぞれのオペレーションが何をするかは、自明の理です。文字どおり、submitRequest はRequest(要求)を送信し、checkResponseはResponse(応答)をチェックします。サービス・インターフェースを説明するWSDLを、リスト 1に示します。
リスト1. AsyncService.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
targetNamespace="http://one.wspattern.developerworks.ibm.com"
xmlns:impl="http://one.wspattern.developerworks.ibm.com"
xmlns:intf="http://one.wspattern.developerworks.ibm.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:types>
<schema
targetNamespace="http://one.wspattern.developerworks.ibm.com"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:impl="http://one.wspattern.developerworks.ibm.com"
xmlns:intf="http://one.wspattern.developerworks.ibm.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<complexType name="ResponseCheck">
<sequence>
<element name="correlationID" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
<element name="ResponseCheck" nillable="true" type="impl:ResponseCheck"/>
<complexType name="Response">
<sequence>
<element name="type" type="xsd:int"/>
<element name="correlationID" nillable="true" type="xsd:string"/>
<element name="refresh" type="xsd:int"/>
<element name="a" nillable="true" type="xsd:string"/>
<element name="b" nillable="true" type="xsd:string"/>
<element name="c" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
<element name="Response" nillable="true" type="impl:Response"/>
<complexType name="Request">
<sequence>
<element name="a" nillable="true" type="xsd:string"/>
<element name="b" nillable="true" type="xsd:string"/>
<element name="c" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
<element name="Request" nillable="true" type="impl:Request"/>
</schema>
</wsdl:types>
<wsdl:message name="submitRequestRequest">
<wsdl:part name="request" type="intf:Request"/>
</wsdl:message>
<wsdl:message name="checkResponseResponse">
<wsdl:part name="checkResponseReturn" type="intf:Response"/>
</wsdl:message>
<wsdl:message name="checkResponseRequest">
<wsdl:part name="check" type="intf:ResponseCheck"/>
</wsdl:message>
<wsdl:message name="submitRequestResponse">
<wsdl:part name="submitRequestReturn" type="intf:Response"/>
</wsdl:message>
<wsdl:portType name="AsyncService">
<wsdl:operation name="checkResponse" parameterOrder="check">
<wsdl:input
message="intf:checkResponseRequest"
name="checkResponseRequest"/>
<wsdl:output
message="intf:checkResponseResponse"
name="checkResponseResponse"/>
</wsdl:operation>
<wsdl:operation name="submitRequest" parameterOrder="request">
<wsdl:input
message="intf:submitRequestRequest"
name="submitRequestRequest"/>
<wsdl:output
message="intf:submitRequestResponse"
name="submitRequestResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="AsyncServiceSoapBinding" type="intf:AsyncService">
<wsdlsoap:binding
style="rpc"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="checkResponse">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="checkResponseRequest">
<wsdlsoap:body
namespace="http://one.wspattern.developerworks.ibm.com"
use="literal"/>
</wsdl:input>
<wsdl:output name="checkResponseResponse">
<wsdlsoap:body
namespace="http://one.wspattern.developerworks.ibm.com"
use="literal"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="submitRequest">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="submitRequestRequest">
<wsdlsoap:body
namespace="http://one.wspattern.developerworks.ibm.com"
use="literal"/>
</wsdl:input>
<wsdl:output name="submitRequestResponse">
<wsdlsoap:body
namespace="http://one.wspattern.developerworks.ibm.com"
use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="AsyncServiceService">
<wsdl:port binding="intf:AsyncServiceSoapBinding" name="AsyncService">
<wsdlsoap:address
location="http://localhost:9080/WSPattern1/services/AsyncService"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
|
このインターフェースについて述べるべきことがいくつかあります。
- サービスはRPC/Literalのエンコード方式を使用します。
-
submitRequest とcheckResponse のオペレーションの両方ともがResponseと呼ばれるオブジェクトを戻します。typeのプロパティーにより識別されるResponseのフレーバーが2種類あります。type のプロパティー の値が0ならば、Refresh Responseを参照し、type のプロパティー の値が1ならば、Request Responseを参照します。応答がまだ有効(使用可能)ではなく、(上記にて参照されるKyle Brown氏の記事にて考察されるHTTP META Refreshのメカニズムと同等の)リフレッシュ・プロパティーに指定される値をサブミットするや否や新規のcheckResponseのオペレーションを要求者がサブミットすることを、Refresh Response は表わします。Request Response は3つの大文字の入力ストリングを含み、要求処理の完了を表わします。
- Refresh Response は(その
checkResponse オペレーションの入力としてその値が使用される)Correlation ID のプロパティーを含みます。この識別子(ID)はクライアントが応答と初期の要求を関連付けさせる唯一の手段です。後の段階で少し触れますが、これを実装する手段は他にもいくつかあります。
このサービスの典型的なメッセージ交換を、リスト2に示します。
リスト2. AsyncService のメッセージ交換
Initial Request
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<m:submitRequest
xmlns:m="http://one.wspattern.developerworks.ibm.com">
<request>
<a>String</a>
<b>String</b>
<c>String</c>
</request>
</m:submitRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
submitRequest Response
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header/>
<soapenv:Body>
<p155:submitRequestResponse
xmlns:p155="http://one.wspattern.developerworks.ibm.com">
<submitRequestReturn>
<type>0</type>
<correlationID>1097517621904</correlationID>
<refresh>10000</refresh>
<a xsi:nil="true"/>
<b xsi:nil="true"/>
<c xsi:nil="true"/>
</submitRequestReturn>
</p155:submitRequestResponse>
</soapenv:Body>
</soapenv:Envelope>
Initial checkResponse attempt, no response available, submitted after 10000 miliseconds
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header/>
<soapenv:Body>
<p155:checkResponseResponse
xmlns:p155="http://one.wspattern.developerworks.ibm.com">
<checkResponseReturn>
<type>0</type>
<correlationID>1097517621904</correlationID>
<refresh>10000</refresh>
<a xsi:nil="true"/>
<b xsi:nil="true"/>
<c xsi:nil="true"/>
</checkResponseReturn>
</p155:checkResponseResponse>
</soapenv:Body>
</soapenv:Envelope>
Second checkResponse attempt, submitted after 10000 miliseconds
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header/>
<soapenv:Body>
<p155:checkResponseResponse
xmlns:p155="http://one.wspattern.developerworks.ibm.com">
<checkResponseReturn>
<type>1</type>
<correlationID xsi:nil="true"/>
<refresh>0</refresh>
<a>STRING</a>
<b>STRING</b>
<c>STRING</c>
</checkResponseReturn>
</p155:checkResponseResponse>
</soapenv:Body>
</soapenv:Envelope>
|

 |
サービスを実装
非同期照会パターンのサービス実装は、Java Messaging Serviceの単刀直入な応用です。この例の目的のために、OpenJMS のオープン・ソースであるJMSプロバイダー実装(参考文献を参照)そしてIBM® WebSphere ® Application Server V5(Application Server)を使用します。デフォルトのOpenJMS 構成を使用し、JSR-109に準拠するJ2EE Webサービスを実装します。developerWorks からダウンロード可能(参考文献を参照)なWebSphere Studio Application Developer (Application Developer) V5.1を使用して、例にて示されているコードを書き出しました。Application Developerへのアクセスが不可能な場合に備え、ここにてEARファイル(参考文献を参照)を提供します。
実装されるべきサーバー側のコンポーネントが2つあります。それは要求処理プログラムにWebサービス実装です。キューから要求の引き出し、そして「長時間実行される」10秒間の大文字化の処理の実行と言う仕事が要求処理プログラムに課せられています。checkResponse オペレーションに続くかたちでクライアントへの応答を配信そして処理するためにキューし、そしてWebサービス・クライアントからの要求を受信するタスクを、サービス実装は課せられます。
典型的なJ2EEアプリケーションでは、JMS仕様によれば要求処理装置はMessage Driven Beanとして実装されます。この例では、JMSのMessageListenerインターフェースを実装する簡素なHTTP Servlet を使用します。サーブレットはサーバー始動を初期化するように構成されており、受信される可能性の高い全ての要求にlistener が使用可能になるようになっています。要求がキューされると同時に、それはlistenのサーブレットに配信されます。
リスト3. JNDIListenerServlet.java
package com.ibm.developerworks.wspattern.one.helper;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.naming.Context;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
public class JNDIListenerServlet
extends HttpServlet
implements Servlet, MessageListener {
private Context context;
private QueueConnection connection;
private QueueSession session;
private Queue queue;
private QueueReceiver receiver;
public void init()
throws ServletException {
super.init();
try {
context = JNDIHelper.getInitialContext();
connection = JNDIHelper.getConnection(context);
session = JNDIHelper.getSession(connection);
queue = JNDIHelper.getQueue(context);
receiver = JNDIHelper.getQueueReceiver(session, queue);
receiver.setMessageListener(this);
System.out.println("Listener servlet is Listening");
} catch (Exception e) {}
}
public void destroy() {
try {
connection.close();
} catch (Exception e) {}
}
public void onMessage(Message message) {
try {
System.out.println("Processing message " + message.getJMSCorrelationID());
Thread.sleep(10 * 1000); // sleep for ten seconds
Queue responseQueue = JNDIHelper.getResponseQueue(context);
QueueSender sender = JNDIHelper.getQueueSender(session,responseQueue);
MapMessage request = (MapMessage)message;
MapMessage response = session.createMapMessage();
response.setJMSCorrelationID(request.getJMSCorrelationID());
for (Enumeration e = request.getMapNames(); e.hasMoreElements();) {
String name = (String) e.nextElement();
try {
response.setString(
name,
request.getString(name).toUpperCase());
} catch (Exception ex) {}
}
sender.send(response);
} catch (Exception e) {
System.out.println("==================");
try {
System.out.println(
"THERE WAS AN ERROR PROCESSING THE MESSAGE! " +
message.getJMSCorrelationID());
} catch (Exception ex) {}
e.printStackTrace(System.out);
System.out.println("==================");
}
}
}
|
JNDIListenerServlet とサービス実装は両方とも(セッションとJMS接続の初期化に関する詳細を隠すこのアプリケーションのために作成された)簡素なhelperクラスを使用します。
リスト4. JNDIHelper.java
package com.ibm.developerworks.wspattern.one.helper;
import java.util.Hashtable;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class JNDIHelper {
private static Context context;
public static Context getInitialContext()
throws NamingException {
if (context == null) {
Hashtable properties = new Hashtable();
properties.put(
Context.INITIAL_CONTEXT_FACTORY,
"org.exolab.jms.jndi.InitialContextFactory");
properties.put(
Context.PROVIDER_URL,
"rmi://localhost:1099");
context = new InitialContext(properties);
}
return context;
}
public static QueueConnection getConnection(
Context context)
throws NamingException,
JMSException {
QueueConnectionFactory factory =
(QueueConnectionFactory) context.lookup(
"JmsQueueConnectionFactory");
QueueConnection connection = factory.createQueueConnection();
connection.start();
return connection;
}
public static QueueSession getSession(QueueConnection connection)
throws JMSException {
QueueSession session =
connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
return session;
}
public static Queue getQueue(Context context)
throws NamingException {
Queue queue = (Queue) context.lookup("queue1");
return queue;
}
public static Queue getResponseQueue(Context context)
throws NamingException {
Queue queue = (Queue) context.lookup("queue2");
return queue;
}
public static QueueSender getQueueSender(
QueueSession session,
Queue queue)
throws JMSException {
QueueSender sender = session.createSender(queue);
return sender;
}
public static QueueReceiver getQueueReceiver(
QueueSession session,
Queue queue)
throws JMSException {
QueueReceiver receiver = session.createReceiver(queue);
return receiver;
}
public static QueueReceiver getQueueReceiver(
QueueSession session,
Queue queue,
String selector)
throws JMSException {
QueueReceiver receiver = session.createReceiver(queue, selector);
return receiver;
}
}
|
一度サーブレットが作成されれば、(サーバー始動にて初期化されるように)サーブレットを構成するようにWebアプリケーションのweb.xml を編集します。サーブレットが初期化されれば、それはJMS接続を開きOpenJMSのデフォルトのメッセージ・キューのlistenerとしてそれ自身を登録します。
第二段階はサービス実装の作成です。機能するJSR-109 Webサービスを手に入れるために生成されるべき多種にわたる成果物のおかげで、Application Developer とともに作業することが役立ちます。ここではサービス実装クラスに焦点を合わせます。Application Server が必要とする他の多種類にわたるJavaそしてXML構成ファイルをご覧になりたいのであれば、この記事の最初と最後にあるCode アイコンをクリックしてダウンロードすることにより得られるコードを参照してください。
リスト5. AsyncService.java
package com.ibm.developerworks.wspattern.one;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.naming.Context;
import com.ibm.developerworks.wspattern.one.helper.JNDIHelper;
public class AsyncService {
public Response submitRequest(Request request) {
Response response = null;
try {
Context context = JNDIHelper.getInitialContext();
QueueConnection connection = JNDIHelper.getConnection(context);
QueueSession session = JNDIHelper.getSession(connection);
Queue queue = JNDIHelper.getQueue(context);
QueueSender sender = JNDIHelper.getQueueSender(session,queue);
MapMessage message = session.createMapMessage();
String corrID = Long.toString(System.currentTimeMillis());
message.setJMSCorrelationID(corrID);
message.setString("one", request.getA());
message.setString("two", request.getB());
message.setString("three", request.getC());
sender.send(message);
response = new Response();
response.setType(Response.TYPE_REFRESH);
response.setCorrelationID(corrID);
response.setRefresh(10 * 1000);
return response;
} catch (Exception e) {
response = new Response();
response.setType(Response.TYPE_RESPONSE);
response.setA(e.getMessage());
}
return response;
}
public Response checkResponse(ResponseCheck check) {
String corrID = check.getCorrelationID();
Response response = null;
try {
Context context = JNDIHelper.getInitialContext();
QueueConnection connection = JNDIHelper.getConnection(context);
QueueSession session = JNDIHelper.getSession(connection);
Queue queue = JNDIHelper.getResponseQueue(context);
String selector = "JMSCorrelationID = '" + corrID + "'";
QueueReceiver receiver = JNDIHelper.getQueueReceiver(session, queue, selector);
Message message = receiver.receiveNoWait();
if (message == null) {
response = new Response();
response.setType(Response.TYPE_REFRESH);
response.setRefresh(10 * 1000);
response.setCorrelationID(corrID);
} else {
MapMessage resp = (MapMessage)message;
response = new Response();
response.setType(Response.TYPE_RESPONSE);
response.setA(resp.getString("one"));
response.setB(resp.getString("two"));
response.setC(resp.getString("three"));
}
} catch (Exception e) {}
return response;
}
}
|
驚愕の対象に値するものは、ここに見当たらないはずです。submitRequest メソッドは入力パラメーターを基にJMS のMapMessage を準備します。このマップ・メッセージは3つのストリング値から成ります。メッセージはそれからキューに送信され、相関IDを含むRefresh Response が準備されてクライアントに戻されます。
checkResponse のオペレーションは入力パラメーターから相関IDを取得して、(適切な相関IDをいだくメッセージを送信することを要求するように)応答キューへの接続を開きます。メッセージが存在しなければ、オペレーションはメッセージを待ち続けません。それは単に別のRefresh Responseを新規のリフレッシュ・インターバルの期間付きで準備して、それを呼び出し者に戻します。メッセージが配信されれば、オペレーションは適切なRequest Responseを準備し戻します。
Webサービスを配置し、OpenJMS とWebSphere のサーバーを起動すれば、非同期照会パターンのWebサービスが始動し稼動を開始します。
まとめ
非同期照会パターンの成功の中心に位置するは、Webサービスのクライアントとサービス・プロバイダーが持つ(要求と応答を相関する)能力です。ここで表記される例では、簡素かつ一度きりの相関IDを作成してひとつのアプリケーションの例に特化したタイマー機能をリフレッシュできます。しかしながら、同じ結果を達成するために「WS-*」仕様の組み合わせを使うことも可能です。WS-Addressing Endpoint Reference またはWS-Transaction Coordination Context ならば相関IDを簡単に含有してタイマー値をリフレッシュできたことでしょう。とにかく、このパターンの使用は特定のアプリケーションに特化され、標準のSOAPヘッダーのエレメントと多種にわたるWS-* 仕様を使うかどうかに関係なく、それぞれの操作が実装する振る舞いは明確かつドキュメントによる裏付けがなくてはなりません。
さらに、ここで実装されている例は、伝統的なSOAPの要求/応答メッセージ・パターンで要求を実行して応答を受信します。別の方法として、要求がHTTP POSTによりサーブレットへ送信されHTTP GET要求を使用することにより応答が回収されるREST-style のモデルを使うのも手です。それぞれのアプローチは同等に有効であり、互いに対して相対的に長所と短所があります(一長一短な)ので、アプリケーションの独自の必要性(ニーズ)に応じて選択されるべきです。例えば、checkResponse のオペレーションがWS-Securityを基にした認証を必要としているのであれば、REST-styleのもたらす相互作用のパターンを使う意味が無いでしょう。
最後に、長期的に運用される操作への詳細にわたる状況照会を実行すること、そして実行中の操作をキャンセルすることを要求者に許可するように、この用例の有効範囲を拡張することを想像するのはそう難しくはないでしょう。これらの多種にわたる可能性を図解・説明するのはこの記事の有効範囲から外れておりますので、ここから先の探究は読者自身の実践的な学習としておまかせします。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| WebSphere deployable EAR file | ws-tip-altdesign1ear.ear | 701 KB | HTTP |
|---|
| Example program source files | ws-tip-altdesign1code.zip | 719 KB | HTTP |
|---|
参考文献
著者について  | 
|  | James Snell 氏は IBM Emerging Technologies Toolkit チームの一員であり、過去数年間にわたり新興の Web サービスの技術そして標準に焦点を合わせてきました。developerWorks にて、新興の技術に焦点を定めるブログを管理しています。 |
記事の評価
|