レベル: 中級 Mikhail Genkin (genkin@ca.ibm.com), Certified IT Architect, IBM
2007年 8月 21日 この記事では、SOA 環境のサービス・インターフェースを使ってサービス・エラーをレポートする方法を学びます。SOA (Service-Oriented Architecture) では、企業内のさまざまなシステム間を疎結合にすることを強調しています。アプリケーションは、適切に設計されたサービス・インターフェースのみを使ってお互いに通信を行い、相手側の実装を意識することはありません。SOA ではサービス・インターフェースの構造が非常に重要です。不適切な設計のサービス・インターフェースは、そのインターフェースを使用するすべてのアプリケーションにマイナスの影響を与える可能性があります。サービス・インターフェースを適切に設計することが、プロジェクトのスケジュールを加速する上で、また SOA ソリューションをビジネス・ニーズに迅速に応えられるようにする上で、いかに役に立っているのかを学びましょう。
はじめに
このシリーズの第 1 回では、設計や開発の方法、サービスの粒度、非同期設計と同期設計の比較、そしてオペレーションのシグニチャーなど、サービス・インターフェースの設計に関するベスト・プラクティスに焦点を当てました。
この記事では、サービスが、サービスを利用するアプリケーションに対してエラーをレポートするためのベスト・プラクティスに焦点を当てます。実動製品の品質を持つシステムは、応答が適切であることの他に、広範な種類のエラー条件を処理できる必要があります。サービスが、サービスを利用するアプリケーションに対してエラー情報をレポートするための方法は、サービス・インターフェースの構造に影響を与え、そしてそれが結局、サービスを利用するアプリケーションの構築方法に影響を与えます。
エラーをレポートするための WSDL 構成体
第 1 回では、サービス・インターフェースを記述するための最善の方法が WSDL (Web Services Description Language) を使うことだと結論しました。WSDL 仕様は下記を規定しています。
- サービス・インターフェース (ポート・タイプ) はいくつかのオペレーションを含みます。
- 各オペレーションは、リクエスト・レスポンス型、あるいは片方向です。
- リクエスト・レスポンス型の各オペレーションは、1 つの入力メッセージと 1 つのレスポンス・メッセージ、そして任意の数の障害メッセージを定義することができます。
- 片方向のオペレーションは、レスポンス・メッセージあるいは障害メッセージを定義することはできません。
WSDL 仕様はさらに、メッセージが 1 つ以上の部分を含むことができると定義しています。各部分の値は、単純な XSD 型か、あるいはユーザー定義の複合型です。WSDL の仕様へのリンクは「参考文献」を参照してください。
エラーのタイプ
サービスはエラーをレポートする必要があります。エラーには、基本的に次の 2 種類があります。
-
システム・レベルのエラー
- サービス実装の一部であるランタイム・ソフトウェアのコンポーネントや、ハードウェア、ネットワーク通信プロトコルの障害を表します。これらのエラーは、実行中のビジネス・ロジックやデータとは無関係の障害を表します。
典型的なシステム・レベルのエラーが発生する例として、サービス実装がアクセスする RDBMS サーバーがクラッシュし、そのサーバーにアプリケーション・サーバーが JDBC (Java™ Database Connectivity) 接続を割り当てられないため、そのサーバーがリクエスト・メッセージを処理できない場合などがあります。
-
ビジネス・レベルのエラー
- ビジネス・ロジックあるいはデータの違反に対応してサービスが発生させるエラーです。例えば、あるリクエスト・メッセージが旅行を予約するためのリクエストを表しており、帰着日のフィールドが出発日のフィールドよりも前の日を示している場合、そのサービス実装はおそらくビジネス・レベルのエラーを発生させるはずです。
|
ベスト・プラクティス: リクエスト・レスポンス型のオペレーションを行う同期アーキテクチャーの場合、オペレーション・シグニチャーにはシステム障害とビジネス障害の両方を定義します。システム障害とビジネス障害は、異なる XSD 型で記述する必要があります。 | |
|---|
サービスを利用するアプリケーションは通常、システム・レベルのエラーとビジネス・レベルのエラーに対して異なる応答をする必要があります。システム・レベルのエラーでは、サービスを利用するアプリケーションはしばらく待って、オリジナル・データを使ってリクエストを再試行する必要があるかもしれません。ビジネス・レベルのエラーの場合には、アプリケーションはおそらく、入力エラーを修正するように要求するメッセージをエンド・ユーザーに返送するでしょう。
リクエスト・レスポンス型のオペレーションに WSDL 障害を使う
障害は、リクエスト・レスポンス型のオペレーションでのみ使用できます。図 1 は、クライアント・アプリケーションにビジネス・レベルのエラーとシステム・レベルのエラーを返すための WSDL ポート・タイプの構造の例を示しています。このオペレーションは、(株取引のための) 一般的な StockQuote の例を基にしています。StockQuote サービスは 1 つの StockQuote ポート・タイプ (インターフェース) を含み、このポート・タイプは getQuote() というオペレーションを含んでいます。このオペレーションは getQuoteRequest メッセージの一部として入力パラメーターを受信します。そして結果は getQuoteResponse メッセージの一部として返されます。
図 1. ポート・タイプの例
getQuote() オペレーションは、サービスを利用するアプリケーションに障害を返す、次の 2 つのメッセージも定義します。
-
getQuoteBusinessFault は、サービスの不正な使い方 (例えばサービスを利用するアプリケーションが、未知の、あるいは空の株シンボルを提供した場合など) に関連するエラーを返します。
-
getQuoteSystemFault は、データベースとの接続の問題など、システムが引き起こしたエラーを返します。
第 1 回のベスト・プラクティスに従って、エラーの型は別の XSD ファイルの中で定義されます。この XSD は、SystemError と BusinessError を表す複合型を含んでいます。
|
ベスト・プラクティス: 障害メッセージは WSDL 部分を 1 つ含む必要があります。この部分の値は、そのエラーの完全な記述を含む複合 XSD 型です。 | |
|---|
上記の図 1 のポート・タイプの例は、障害を使ってエラーを返すリクエスト・レスポンス型のオペレーションを示しています。このオペレーションの構造は、IBM® WebSphere® Integration Developer 6.0、あるいは IBM Rational® Application Developer の 6.0 または 7.0 に用意された WSDL エディターに表示される通りに表示されています。
リスト 1 は、XSD ファイルの中で定義される複合型の構造を示しています。このサンプル・コードはダウンロードすることができ、このコードを見ると複合エラー型の定義方法がわかるはずです。BusinessErrorType と SystemErrorType は、共通のベースとなる型 BaseErrorType を拡張することで得られます。BaseErrorType は次の 2 つの要素を定義します。
-
faultName は、サービスを利用するアプリケーションに対して、そのエラーに対するアプリケーション固有の名前を伝えます。
例えば、サービスを利用するアプリケーションが株のシンボルのパラメーターとしてヌルまたは空のストリングを渡すと、そのアプリケーションに返される障害の名前は「Invalid symbol format fault (無効なシンボル・フォーマットによる障害)」のようになります。
-
message は詳しい説明を提供し、必要な修正アクションを示します。例えば「Null or empty symbol. Please pass a valid stock symbol to the getQuote operation (ヌルまたは空のシンボルです。有効な株のシンボルを getQuote オペレーションに渡してください)」などです。
faultName 要素と message 要素は、getQuote オペレーションによって発生する可能性のあるすべてのタイプの障害に共通です。そのため、すべてのエラーに対するベースの型として定義されます。
リスト 1. ErrorTypes.xsd の抜粋
<complexType name="BaseErrorType" abstract="true">
<sequence>
<element name="faultName"
type="string" minOccurs="1" maxOccurs="1">
</element>
<element name="message"
type="string" minOccurs="0" maxOccurs="1">
</element>
</sequence>
</complexType>
<complexType name="BusinessErrorType">
<complexContent>
<extension base="tns:BaseErrorType">
<sequence>
<element name="errorCode" type="tns:BusinessErrorCodeType"
minOccurs="1" maxOccurs="1">
</element>
</sequence>
</extension>
</complexContent>
</complexType>
<complexType name="SystemErrorType">
<complexContent>
<extension base="tns:BaseErrorType">
<sequence>
<element name="errorCode" type="tns:SystemErrorCodeType"
minOccurs="1" maxOccurs="1">
</element>
<element name="originatingError" type="string"
minOccurs="1" maxOccurs="1">
</element>
<element name="trace" type="string" minOccurs="0"
maxOccurs="unbounded"></element>
</sequence>
</extension>
</complexContent>
</complexType>
|
SystemError 型と BusinessError 型は、どちらも errorCode 要素を定義します。この要素は、サービスが返す可能性のあるそれぞれのエラーの型を固有識別する英数字コードを含みます。サービスが、エラーの説明をするテキスト情報と共に英数字コードを返すことは重要です。なぜなら、クライアント・アプリケーション (つまりサービスの利用側) は、このコードをプログラムで検査し、続いてエラー・コードが示すエラーの型に応じて別々の呼び出しを直接行う必要があるからです。
|
ベスト・プラクティス: サービスを利用するアプリケーション (クライアント) がプログラムでエラーを処理できるように、障害メッセージはエラー・コードを含む必要があります。サービスを利用するアプリケーションが、レポートされたエラーの意味を容易に理解できるように、エラー・コードを WSDL サービス定義の必須部分として公開する必要があります。 |
|---|
リスト 2 は、サービスを利用するアプリケーションにエラー・コードを伝えるために使われる単純型の定義を示しています。BusinessErrorCodeType と SystemErrorCodeType という 2 つの単純型は、xsd:string 型に制限をかけることで得られます。その制限の中にリストとして列挙されたものはエラー・コードを定義し、これらのコードをビジネス・レベルのエラーとシステム・レベルのエラーとして返すことができます。XSD に埋め込まれたコメントは、サービスを利用する (クライアント) アプリケーションの開発者のために、エラー・コードの意味を記述してあります。
リスト 2. 定義
<simpleType name="BusinessErrorCodeType">
<restriction base="string">
<enumeration value="SQ00010"/>
<!-- Invalid symbol string format -->
<enumeration value="SQ00020"/>
<!-- Symbol not found -->
</restriction>
</simpleType>
<simpleType name="SystemErrorCodeType">
<restriction base="string">
<enumeration value="SQ001001"/>
<!-- Authentication error -->
<enumeration value="SQ001002"/>
<!-- Data base connection not available -->
</restriction>
</simpleType>
|
この例は、ビジネス・レベルのエラー・コードとシステム・レベルのエラー・コードを別々の型にグループ分けします。こうすることで両者を明示的に分離し、必要に応じてサービスを利用するアプリケーションがプログラムでエラー・コードを処理しやすくします。多くの場合には、ビジネス・レベルのエラーとシステム・レベルのエラーのセマンティクスを記述するすべてのエラー・コードを含む単純型を 1 つ定義すれば十分です。しかし当然ながら、ビジネス・エラーのコードとシステム・エラーのコードを別の数字範囲にグループ分けし (例えばビジネス・レベルのエラーを 00010 から 00100、システム・レベルのエラーを 001000 から 010000、など)、これらのコードをプログラムで処理しやすくした方が望ましいと言えます。
例外の場合にもパフォーマンスが重要な場合には、エラー・コードのフィールドを int 型あるいは short 型にする必要があるかもしれません (数字フィールドでの比較はストリング・フィールドでの比較よりも高速です)。
|
ベスト・プラクティス: システム・エラーとビジネス・エラーの数値は、サービスを利用するアプリケーションのコードがプログラムでチェックしやすいように、別の範囲にグループ分けする必要があります (「ダウンロード」を参照)。 |
|---|
SystemErrorType 型は、問題の元となったエラーに関する情報を取得するためのオプション要素を含みます。例えば、StockQuote サービスはバックエンドのデータベースと通信しようとする際に java.sql.SQLException をキャッチするかもしれません。それに反応して、このサービスは SystemErrorType を作成してスローします。SystemErrorType は、本来発生した例外の名前と、オプションとして、さらに (問題の元となった) java.sql.SQLException 例外が発生してからのメッセージ・スタックのトレース情報を含む必要があります。
サービスを利用するアプリケーションとサービスを作成するアプリケーションが同じ組織に属する場合、大部分の場合には、サービスの開発者はサービスを利用するアプリケーションにオリジナルのエラーをレポートしないように選択するかもしれません。これは問題の判別と解決を促進する、適切なプラクティスです。
オペレーション・レスポンスの中でエラーを返す
WSDL 障害を使う代わりに、通常のレスポンス・メッセージの一部としてエラー情報を返す方法があります。アプリケーションの開発者は、特に Web サービスでのバッチ処理が関係する場合には、この方法を好む傾向があります。この場合のバッチ処理は、複数の明確な入力を含む入力メッセージと、それぞれが入力に対応した複数の明確な結果を含むことのできるレスポンス・メッセージのオペレーションに適用されます。例えば、StockQuote サービスへの入力メッセージは複数のシンボルを含むかもしれず、またレスポンス・メッセージは複数の結果を含むはずです。この方法には、いくつかの欠点と利点があります (このシリーズの今後の記事では、バッチ処理に関連する問題について詳細に解説します)。
障害ではなくレスポンスでエラー・コードを返すことによる基本的な利点は、サービスを利用するアプリケーション (クライアント) が、エラーではないレスポンスの処理と同じ方法でエラー情報を処理できることです。図 2 は、この方法でエラー情報がどのように返されるかを示しています。
この場合クライアント・アプリケーションは、Web サービスが返すレスポンスの Java オブジェクトを受け取り、それを (エンド・ユーザーに表示するために) 単純に表示階層 (通常は JavaServer Page) に渡します。これによって、サービスを利用するアプリケーションの表示階層を Web サービス階層から分離するために使われる転送オブジェクトの作成に関連するコーディングを追加で行う必要がなくなります。
図 2. レスポンス・メッセージの一部としてエラー情報を返す、片方向、およびリクエスト・レスポンス型のオペレーション
この方法の重要な欠点として、次のようなものがあります。
- クライアント・アプリケーションは、レスポンスにアクセスし、レスポンスに含まれる結果要素がヌルかどうかをチェックし、systemError の値と businessError の値を検査して返し、そして適切に反応する必要があります。
ビジネス・ロジックのコードは、技術的な参照や条件文が多く含まれていて、乱雑になります。WSBPEL で作成されたビジネス・プロセスは、特に悪影響を受けます。WSBPEL (Business Process Execution Language) は障害処理の構成体を提供しており、この構成体はグラフィカルなビルダー・ツールによって、ビジネス・ロジックのアクティビティーのメイン・シーケンスから明確に分離することができます。もし WSBPEL のフローが、レスポンス構造のイントロスペクションと、結果フィールドおよびエラー・フィールドの値の評価を連続的に行わなければならないとすると、作成されるグラフは、ビジネス・ユーザーにとって非常に見づらく、わかりにくいものになります。
- サービスを利用するアプリケーション (クライアント) コードのユーザー・インターフェースは、サービス・インターフェースと緊密に結合されてしまいます。将来、サービス・インターフェースを変更すると、ユーザー・インターフェースを作り直す必要があるかもしれません。
|
ベスト・プラクティス: バッチ処理を必要としないリクエスト・レスポンス型のオペレーションでは、レスポンス・メッセージとステータス・コードでエラー情報を返すのではなく、WSDL 障害を使って、サービス・インターフェースから、サービスを利用するアプリケーションにエラー情報を返します。 |
|---|
- サービスを利用するアプリケーションを実行するアプリケーション・サーバーは、Web サービスが引き起こす問題を認識しません。結果を返すレスポンスはすべて、エラーのない呼び出しのように見え、トレース・ファイルや system.out のログには問題を示すものが何もありません。そのため問題の判別がしにくくなります。
こうした欠点があるため、リクエスト・レスポンス型のオペレーションからエラーやメッセージを返すためのベスト・プラクティスとしては、WSDL の障害構成体を利用し、先ほど説明した方法を使って障害としてエラーを返すことです。
非同期トポロジーでエラーを返す
WSDL 仕様 (「参考文献」を参照) では、片方向のオペレーションに対して障害を定義することが許可されていません。しかし、非同期モデルを元に作成されたアプリケーションには、エラー情報にアクセスできる機能が必ず必要です。
非同期呼び出しは次の 3 つのいずれかのスタイルで構成されます。
-
レスポンスのない片方向の非同期呼び出し
公開と購読という対話動作は、このスタイルの一例です。アプリケーションが、レスポンスのない片方向の非同期呼び出しを実装する場合には、サービスを利用するアプリケーションにエラーをフィードバックする方法がありません。これは、WSDL 仕様では、(使用される) 片方向オペレーションに対して障害を定義することが許可されていないためです。サービスを利用するアプリケーションは後でレスポンスを取得することにも関心がないため、レスポンス・メッセージを使ってクライアントにエラーを提供することができません。この場合には、意図した宛先に行く途中でリクエスト・メッセージが失われないように、インフラの構成に十分注意する必要があります。
|
ベスト・プラクティス: 非同期トポロジーを実装する際には、レスポンス・メッセージを使って障害を返します。 |
|---|
-
非同期呼び出しの後、後からレスポンスを要求する
この方法は 2 つのリクエスト・レスポンス型オペレーションに対して実行する必要がありますが、処理の大部分はターゲットのサービスによって非同期で実行されます。例えば、
- サービスを利用するアプリケーションは
submitStockQuoteRequest() オペレーションを呼び出します。このオペレーションは、サービスを利用するアプリケーションに取引の確認番号を返します。
-
stockQuoteService 実装は株価の参照を行います。
- サービスを利用するアプリケーションは、後から
getStockQuoteResponse() オペレーションを呼び出し、submitStockQuoteRequest() が返した取引番号を渡します。このオペレーションで返されるレスポンス・メッセージには、株価の結果と、(もしエラー情報がある場合には) エラー情報の両方が含まれます。
図 3 は、この場合のオペレーション・シグニチャーの構造を示しています。リクエスト・オペレーションはシステム障害を発生させるかもしれません。これらの障害は、入力パラメーターの送達と非同期処理の開始に関連するエラーのみを伝えます。レスポンス・メッセージを取得するために使用されるオペレーションは、先ほど通常のリクエスト・レスポンス型のオペレーションで説明した方法と同じ方法で、システム障害とビジネス障害の両方を発生させる必要があります。
図 3. StockQuoteAsynch ポート・タイプの構造
-
コールバックを使ってリクエストを返す非同期呼び出し
サービスを利用するアプリケーションは、クライアント (つまりサービスを利用する) アプリケーションが定義するインターフェースに対してサービスがレスポンス・メッセージを返送できるように情報を提供します。もし非同期処理の段階でビジネス・エラーが発生した場合には、サービス実装は、レスポンス・メッセージを照会される際にビジネス障害を発生させます。
|
ベスト・プラクティス: リクエスト・レスポンス型のオペレーションで非同期処理を開始する場合には、リクエスト・オペレーションに対してのみシステム障害を定義し、レスポンス・メッセージを取得するために使用するオペレーションにはシステム・レベルの障害とビジネス・レベルの障害の両方を定義します。 |
|---|
クライアント・アプリケーションでエラーを処理する
リスト 3 は、StockQuote サービスによって発生するビジネス・エラーとシステム・エラーをクライアント・アプリケーションが処理する様子の一例です。
JAX-RPC (Java API for XML-based RPC) 仕様と JAX-WS (Java API for XML Web Services) 仕様は、WSDL と XSD で記述される型を、クライアントが使用する Java オブジェクトに変換する方法を記述しています。ここではこれらのエラーを表現するために別々の複合型を定義したので、Rational Application Developer の Web サービス・ツールは、それらを表現するために別々の Java Bean を生成します。これらの Bean は Java の Exception クラスから派生し、別々のキャッチ・ブロックで処理されます。
|
ベスト・プラクティス: Web サービスを呼び出す場合には、宣言されたすべての障害をキャッチし、そしてシステム・エラーとビジネス・エラーを別々のキャッチ・ブロックで処理します。 |
|---|
クライアント・アプリケーションは、Web サービスを呼び出す場合には、生成されるプロキシーのメソッド・シグニチャーで定義される例外を処理する必要があります。クライアント・スタックは、これらの例外の他にも、シリアライズや通信に関連する他のエラーも発生させるかもしれません。プロキシーのメソッド・シグニチャーで定義される例外の他にベースの java.lang.Throwable を処理することは、一般的には適切な考えです。こうすることで、クライアント・アプリケーションのコードは、クライアント・スタックで発生する可能性のある予見のできない例外、例えば不正な形式のレスポンス・メッセージ (シリアライズなどのエラー) を処理できるほど十分に堅牢になります。
リスト 3. リクエスト・レスポンス型の処理でのクライアント・サイドのエラー処理の例
public class SQFacade {
private int retryCount = 0;
private StockQuoteProxy sqProxy = new StockQuoteProxy();
public String getQuote(String symbol) throws UIError
{
String result = null;
UIError error = new UIError();
try {
result = sqProxy.getQuote(symbol);
} catch (BusinessErrorType e) {
// Check the code and return appropriate action to user
BusinessErrorCodeType errorCode = e.getErrorCode();
if (errorCode != null)
{
if (errorCode.getValue().equals(BusinessErrorCodeType._SQ00010))
{
error.setMessage("Invalid input format!");
error.setAction("Please re-enter the symbol value correctly.");
}
else if(errorCode.getValue().equals(BusinessErrorCodeType._SQ00020))
{
error.setMessage("Stock symbol not found!");
error.setAction("Please enter a different stock symbol.");
}
else // Just in case!
{
error.setMessage("User error!");
error.setAction("Please contact site administrator.");
}
}
else // Just in case!
{
error.setMessage("User error!");
error.setAction("Please contact site administrator.");
}
throw error;
}
catch(SystemErrorType e)
{
// Check the code and return appropriate action to user
SystemErrorCodeType errorCode = e.getErrorCode();
if (errorCode != null)
{
if (errorCode.getValue().equals(SystemErrorCodeType._SQ001001))
{
error.setMessage("Authentication problem!");
error.setAction("Please please contact the site administrator.");
}
else if(errorCode.getValue().equals(SystemErrorCodeType._SQ001002))
{
// Retry 3 times to see if db connection problem clears up
if (retryCount < 3)
{
retryCount++;
this.getQuote(symbol);
}
retryCount = 0;
error.setMessage("Data access problems!");
error.setAction("Please try again at a later time.");
}
else // Just in case!
{
error.setMessage("System error!");
error.setAction("Please contact site administrator.");
}
}
else // Just in case!
{
error.setMessage("System error!");
error.setAction("Please contact site administrator.");
}
throw error;
}
catch (Throwable e){
e.printStackTrace();
error.setMessage("System error!");
error.setAction("Please contact site administrator.");
throw error;
}
return result;
}
}
|
|
ベスト・プラクティス: システム・エラーとビジネス・エラーをキャッチする際には、クライアント・スタックで発生する想定外のエラー条件を扱うために、ベースのエラー・タイプ java.lang.Throwable もキャッチします。 |
|---|
リスト 3 は、この記事で説明したベスト・プラクティスを実装するための 1 つの方法を示しています。仮想的なクライアント・アプリケーションは、getQuote() というメソッドを持つ、SQFacade という単純なファサード・クラスを作成します。このメソッドのシグニチャーは Web サービスのプロキシーが定義する getQuote() メソッドのシグニチャーに非常に似ていますが、生成されたプロキシー・コードから UI コードを分離するために使われる異なる単純なエラーの型 (UIError) をスローし、ユーザーにとって、より意味のある情報を返します。
SQFacade クラスは、例外処理ロジックをカプセル化しています。このクラスは、システム・レベルのデータベース接続の問題が起きた場合のために、呼び出しを 3 回リトライするという再帰的な方法をとっています。
まとめ
Web サービスは SOA を実装するために理想的な技術です。この記事では、サービスを利用するアプリケーションに対してサービスがエラー情報をレポートする際のベスト・プラクティスを学びました。アプリケーションにとって推奨される方法は、リクエスト・レスポンス型の処理を行う際には WSDL 仕様が提供する障害機構を活用し、また非同期呼び出しを行う際にエラーをレポートするためにはレスポンス・メッセージを使うことです。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Error handling code | ar-servdsgn2code.zip | 3,113KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Mikhail Genkin は認定 IT アーキテクトであり、IBM の Integrated Software and Services for WebSphere に所属しています。彼は IBM の重要顧客に対して、最新の IBM 製品を使ったビジネス統合ソリューションとサービス指向アーキテクチャーの実装に対する協力を行っています。また、彼が貢献したいくつかのリリースとして、VisualAge for Java, Enterprise Edition や WebSphere Application Server, Enterprise Edition、そして WebSphere Application Developer, Integration Edition と WebSphere Business Integration Server Foundation、そして WebSphere Process Server があります。彼は Web サービスや Java Connector Architecture、そしてプロセス・コレオグラフィーに関して多くの業界出版物に寄稿しており、また業界の会議でも頻繁に講演を行っています。 |
記事の評価
|