SOAP-to-RESTを使用して移行と統合を簡素化する

Jinhua Luo

March 31, 2023

Technology

1. Webサービスとは何か?

Webサービスは、World Wide Web Consortium (W3C) によって、ネットワーク上での相互運用可能な機械間の相互作用をサポートするために設計されたソフトウェアシステムと定義されています。

Webサービスは特定のタスクまたは一連のタスクを実行し、Web Services Description Language (WSDL) と呼ばれるサービスの標準的なXML表現によって記述されます。サービス記述は、メッセージ形式(操作を詳細に記述するために使用)、トランスポートプロトコル、場所など、サービスとの相互作用に必要なすべての詳細を提供します。

他のシステムは、通常HTTPとXMLシリアライゼーションおよび他のWeb関連標準を使用して、SOAPメッセージを使用してWebサービスと相互作用します。

Webサービスのアーキテクチャ図(サービスブローカーはオプション)は以下の通りです:

web services architecture

*画像ソース(CC 3.0 BY-SAライセンス):https://en.wikipedia.org/wiki/Web_service

WSDLインターフェースは、サービスの実装に関する詳細情報を隠蔽するため、サービスの使用は、サービスを実装するハードウェアやソフトウェアプラットフォーム、およびサービスを記述するために使用されたプログラミング言語に依存しません。

Webサービスに基づくアプリケーションは、疎結合でコンポーネント指向であり、クロステクノロジー実装です。Webサービスは単独で使用することも、他のWebサービスと組み合わせて複雑な集約やビジネストランザクションを実行することもできます。

Webサービスは、モノリシックシステムを置き換えるために使用される設計手法であるService-oriented architecture (SOA) の実装単位です。大規模なシステムは複数のWebサービスに分解でき、それらを組み合わせて大きなブラックボックスとして外部にビジネスロジックを提供できます。現在人気のコンテナベースのマイクロサービスは、Webサービスの最新の代替品です。しかし、多くのレガシーシステムは既にWebサービスに基づいて実装および運用されているため、技術の急速な進化にもかかわらず、これらのシステムとの互換性は依然として課題です。

WSDL (Web Services Description Language)

WSDLは、Webサービスを記述するために使用されるXML表記法です。WSDL定義は、クライアントがWebサービスリクエストをどのように記述するかを指示し、Webサービスプロバイダーが提供するインターフェースを定義します。

WSDL定義は複数の部分に分かれており、Webサービスの論理インターフェースと物理的な詳細を指定します。物理的な詳細には、エンドポイント情報(HTTPポート番号など)や、SOAPメッセージの内容をどのように表現し、どのトランスポート方法を使用するかを指定するバインディング情報が含まれます。

Representation of concepts defined by WSDL 1.1 and WSDL 2.0 documents.

画像ソース(CC 3.0 BY-SAライセンス):https://en.wikipedia.org/wiki/Web_Services_Description_Language

  • WSDLファイルには複数のサービスを含めることができます。
  • サービスには複数のポートを含めることができます。
  • ポートはURLアドレスを定義し(各ポートで異なる場合があります)、複数の操作を含めることができます。
  • 各操作には入力タイプと出力タイプが含まれます。
  • タイプはメッセージ構造を定義し、メッセージを構成するフィールド、各フィールドのデータタイプ(ネスト可能)、および許可されるフィールドの数を指定します。

1.1 SOAPとは何か

SOAPは、Webサービスの相互作用で使用されるXMLメッセージ形式です。SOAPメッセージは通常、HTTPまたはJMSを介して送信されますが、他のトランスポートプロトコルも使用できます。WSDL定義は、特定のWebサービスでのSOAPの使用法を記述します。

SOAPには、SOAP 1.1とSOAP 1.2の2つの一般的に使用されるバージョンがあります。

SOAP structure

画像ソース(CC 3.0 BY-SAライセンス):https://en.wikipedia.org/wiki/SOAP

SOAPメッセージには以下の部分が含まれます:

  • ヘッダーメタデータ(通常は空)
  • ボディ
    • WSDLで定義されたメッセージタイプ
    • 成功したレスポンスに加えて、レスポンスタイプの構造化されたエラーメッセージもあります。

例:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header></SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <ns2:getCountryResponse xmlns:ns2="http://spring.io/guides/gs-producing-web-service">
      <ns2:country>
        <ns2:name>Spain</ns2:name>
        <ns2:population>46704314</ns2:population>
        <ns2:capital>Madrid</ns2:capital>
        <ns2:currency>EUR</ns2:currency>
      </ns2:country>
    </ns2:getCountryResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

1.2 RESTとは何か

Webサービスは抽象的な概念であり、どのようにでも実装できます。例えば、RESTはその人気のある実装方法の一つです。

RESTは、Representational State Transferの略で、文字通りプレゼンテーションレイヤーの状態転送を意味します。Roy Thomas Fieldingが2000年に博士論文でこの用語を提案しました。当時、インターネットが急成長しており、ソフトウェア開発とネットワークとの相互作用の実用的な定義が必要とされていました。

長い間、ソフトウェア研究は主にソフトウェア設計の分類と設計方法の進化に焦点を当てていました。異なる設計選択がシステムの動作に与える影響を客観的に評価することはほとんどありませんでした。一方、ネットワーク研究は主にシステム間の通信動作の詳細と特定の通信メカニズムのパフォーマンスを向上させる方法に焦点を当てており、アプリケーションの相互作用スタイルを変更することが相互作用プロトコルを変更するよりも全体的なパフォーマンスに大きな影響を与えることをしばしば見落としていました。私の記事は、機能性が高く、パフォーマンスが良く、通信に適したネットワークベースのアプリケーションソフトウェアのアーキテクチャ設計を理解し評価することを目的としています。

ウェブサイトにアクセスすることは、クライアントとサーバー間の相互作用プロセスを表します。このプロセス中に、データと状態の変化が必ず発生します。HTTPプロトコルはステートレスであるため、すべての状態はサーバー側に保存されます。したがって、クライアントがサーバーを操作するためには、サーバー側で「状態転送」を引き起こす手段を使用する必要があります。この転送はプレゼンテーションレイヤー上で構築されているため、「プレゼンテーションレイヤーの状態転送」と呼ばれます。

RESTの4つの基本原則は以下の通りです:

  1. HTTP動詞を使用する:GET、POST、PUT、DELETE;
  2. ステートレスな接続、サーバーはあまりにも多くのコンテキスト状態を保存すべきではない;つまり、各リクエストは独立している;
  3. 各リソースにURIを設定する;
  4. x-www-form-urlencodedまたはJSONをデータ形式として使用する;

SOAPをRESTに変換することで、ユーザーが従来のWebサービスにRESTfulな方法でアクセスできるようになり、SOAPクライアントの開発コストを削減できます。さらに、ゼロコード開発で任意のWebサービスに動的に適応できるならば、それはさらに完璧です。

RESTの最大の利点は、スキーマがないため開発が容易であり、JSONは可読性が高く冗長性が低いことです。

2. SOAP-to-RESTプロキシの従来の実装

2.1 手動テンプレート変換

このアプローチでは、Webサービスの各操作に対してリクエストとレスポンスの変換テンプレートを提供する必要があります。これは他の多くのゲートウェイ製品でも使用されています。

APISIXのボディトランスフォーマープラグインを使用して、簡単なSOAP-to-RESTプロキシを作成し、このアプローチを試すことができます。

例えば、WSDLファイルのCountriesPortServicegetCountry操作に対して、タイプ定義に基づいてXML形式のリクエストテンプレートを構築します。

ここでは、JSONのnameフィールドをgetCountryRequestのnameフィールドに埋め込みます。

req_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr -d '\r\n'
<?xml version="1.0"?>
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
 <soap-env:Body>
  <ns0:getCountryRequest xmlns:ns0="http://spring.io/guides/gs-producing-web-service">
   <ns0:name>{{_escape_xml(name)}}</ns0:name>
  </ns0:getCountryRequest>
 </soap-env:Body>
</soap-env:Envelope>
EOF
)

レスポンスに対しては、XML-to-JSONテンプレートを提供する必要があります。これは少し複雑になる可能性があります(特にSOAPバージョンの違いを考慮する必要がある場合)。これは、レスポンスが成功したかどうかを判断する必要があるためです:

  • 成功したレスポンスの場合、フィールドを直接JSONにマッピングできます。
  • 失敗したレスポンス(つまり、fault)の場合、別のJSON構造が必要であり、特定のオプションフィールドが存在するかどうかを判断する必要があります。
rsp_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr -d '\r\n'
{% if Envelope.Body.Fault == nil then %}
{
   "currency":"{{Envelope.Body.getCountryResponse.country.currency}}",
   "population":{{Envelope.Body.getCountryResponse.country.population}},
   "capital":"{{Envelope.Body.getCountryResponse.country.capital}}",
   "name":"{{Envelope.Body.getCountryResponse.country.name}}"
}
{% else %}
{
   "message":{*_escape_json(Envelope.Body.Fault.faultstring[1])*},
   "code":"{{Envelope.Body.Fault.faultcode}}"
   {% if Envelope.Body.Fault.faultactor ~= nil then %}
   , "actor":"{{Envelope.Body.Fault.faultactor}}"
   {% end %}
}
{% end %}
EOF
)

APISIXのルーティングを設定し、テストを実行します:

curl http://127.0.0.1:9180/apisix/admin/routes/1 \
    -H 'X-API-KEY: xxx' -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/ws/getCountry",
    "plugins": {
        "body-transformer": {
            "request": {
                "template": "'"$req_template"'"
            },
            "response": {
                "template": "'"$rsp_template"'"
            }
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "localhost:8080": 1
        }
    }
}'
curl -s http://127.0.0.1:9080/ws/getCountry \
    -H 'content-type: application/json' \
    -X POST -d '{"name": "Spain"}' | jq
{
  "currency": "EUR",
  "population": 46704314,
  "capital": "Madrid",
  "name": "Spain"
}
# Fault response
{
  "message": "Your name is required.",
  "code": "SOAP-ENV:Server"
}

この方法では、WSDLファイル内の各操作の定義と各操作に対応するWebサービスのアドレスを手動で理解する必要があります。WSDLファイルが大きく、多数の操作を含み、複雑なネストされたタイプ定義がある場合、このアプローチは非常に面倒で、デバッグが難しく、エラーが発生しやすくなります。

2.2 Apache Camel

https://camel.apache.org/

Camelは、Javaの統合フレームワークとして有名で、異なるプロトコルやビジネスロジックを変換するルーティングパイプラインを実装するために使用されます。SOAP-to-RESTはその使用例の一つです。

Camelを使用するには、WSDLファイルをダウンロードしてインポートし、SOAPクライアントのスタブコードを生成し、Javaコードを記述する必要があります:

  • RESTエンドポイントの定義
  • プロトコル変換ルートの定義、例えばJSONフィールドがSOAPフィールドにどのようにマッピングされるか

例として、温度単位変換のWebサービスを考えます:

https://apps.learnwebservices.com/services/tempconverter?wsdl

  1. WSDLファイルに基づいてMavenを使用してSOAPクライアントコードを生成します。

cxf-codegen-pluginは、WebサービスにアクセスするためのSOAPクライアントエンドポイントを生成します。

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-codegen-plugin</artifactId>
      <executions>
        <execution>
          <id>generate-sources</id>
          <phase>generate-sources</phase>
          <configuration>
            <wsdlOptions>
              <wsdlOption>
                <wsdl>src/main/resources/TempConverter.wsdl</wsdl>
              </wsdlOption>
            </wsdlOptions>
          </configuration>
          <goals>
            <goal>wsdl2java</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
  1. SOAPクライアントビーンの記述。

ビーンの名前をcxfConvertTempとして覚えておきます。これは後でCamelルートを定義する際に使用されます。

import com.learnwebservices.services.tempconverter.TempConverterEndpoint;
@Configuration
public class CxfBeans {
    @Value("${endpoint.wsdl}")
    private String SOAP_URL;
    @Bean(name = "cxfConvertTemp")
    public CxfEndpoint buildCxfEndpoint() {
        CxfEndpoint cxf = new CxfEndpoint();
        cxf.setAddress(SOAP_URL);
        cxf.setServiceClass(TempConverterEndpoint.class);
        return cxf;
    }
}
  1. まず、ダウンストリームのRESTルートを記述します。

このルートから、RESTFulスタイルのURLとそのパラメータ定義が定義され、各URLの次のホップルートが定義されていることがわかります。例えば、/convert/celsius/to/fahrenheit/{num}は、URLの最後の部分をパラメータ(double型)として取り、次のホップルートdirect:celsius-to-fahrenheitに提供します。

rest("/convert")
    .get("/celsius/to/fahrenheit/{num}")
    .consumes("text/plain").produces("text/plain")
    .description("Convert a temperature in Celsius to Fahrenheit")
    .param().name("num").type(RestParamType.path).description("Temperature in Celsius").dataType("int").endParam()
    .to("direct:celsius-to-fahrenheit");
  1. 最後に、アップストリームのSOAPルートとアップストリームとダウンストリーム間の変換を記述します。
from("direct:celsius-to-fahrenheit")
    .removeHeaders("CamelHttp*")
    .process(new Processor() {
        @Override
        public void process(Exchange exchange) throws Exception {
            // SOAPリクエストを初期化
            // ダウンストリームのパラメータnumをボディに埋め込みます。これは単純なdouble型です。
            CelsiusToFahrenheitRequest c = new CelsiusToFahrenheitRequest();
            c.setTemperatureInCelsius(Double.valueOf(exchange.getIn().getHeader("num").toString()));
            exchange.getIn().setBody(c);
        }
    })
    // SOAP操作と名前空間を指定
    // application.propertiesファイルで定義
    .setHeader(CxfConstants.OPERATION_NAME, constant("{{endpoint.operation.celsius.to.fahrenheit}}"))
    .setHeader(CxfConstants.OPERATION_NAMESPACE, constant("{{endpoint.namespace}}"))
    // WSDLで生成されたSOAPクライアントビーンを使用してパッケージを送信します。
    .to("cxf:bean:cxfConvertTemp")
    .process(new Processor() {
        @Override
        public void process(Exchange exchange) throws Exception {
            // SOAPレスポンスを処理
            // ボディにdouble型の値を埋め込み、文字列に変換します
            // 文字列をダウンストリームに返します
            MessageContentsList response = (MessageContentsList) exchange.getIn().getBody();
            CelsiusToFahrenheitResponse r = (CelsiusToFahrenheitResponse) response.get(0);
            exchange.getIn().setBody("Temp in Farenheit: " + r.getTemperatureInFahrenheit());
        }
    })
    .to("mock:output");
  1. テスト
curl localhost:9090/convert/celsius/to/fahrenheit/50
Temp in Farenheit: 122.0

Camelを使用してSOAP-to-RESTを行うには、すべての操作に対してルートと変換ロジックをJavaコードで定義する必要があり、開発コストがかかります。

同様に、WSDLに多くのサービスと操作が含まれている場合、Camelを使用してプロキシすることも苦痛です。

2.3 結論

従来の方法の欠点をまとめます。

モジュールCamel
WSDL手動解析Mavenによるコード生成
アップストリーム手動解析自動変換
ボディ定義判断と変換のためのテンプレートを提供変換コードを記述
パラメータ取得Nginx変数コードで定義またはSOAPクライアントインターフェースを呼び出して取得

これらのアプローチには開発コストがかかり、新しいWebサービスごとにこの開発コストが繰り返されます。

開発コストはWebサービスの複雑さに比例します。

3. APISIXのSOAP-to-RESTプロキシ

従来のプロキシアプローチでは、変換テンプレートを提供するか、変換コードを記述する必要があり、ユーザーはWSDLファイルを深く分析し、大きな開発コストを負担する必要があります。

APISIXは、WSDLファイルを自動的に分析し、各操作に対して変換ロジックを提供する自動化されたアプローチを提供し、ユーザーの開発コストを削減します。

これはエンタープライズプラグインです。詳細についてはお問い合わせください

APISIX SOAP-to-REST proxy

3.1 コードレス自動変換

APISIX SOAPプロキシを使用すると:

  • WSDLファイルを手動で解析またはインポートする必要はありません
  • 変換テンプレートを定義する必要はありません
  • 変換や結合コードを記述する必要はありません。

ユーザーはWSDLのURLを設定するだけで、APISIXが自動的に変換を行います。これは任意のWebサービスに適しており、汎用的なプログラムであり、特定のニーズに対して二次開発を必要としません。

3.2 動的設定

  • WSDL URLは任意のルートにバインドでき、他のAPISIXリソースオブジェクトと同様に、実行時に更新できます。設定変更は動的に反映され、APISIXを再起動する必要はありません。
  • WSDLファイルに含まれるサービスURL(複数のURLがある場合があります)、つまりアップストリームアドレスは自動的に認識され、ユーザーが解析して設定する必要なくSOAPアップストリームとして使用されます。

3.3 実装メカニズム

  • WSDL URLからWSDLファイルの内容を取得し、解析後にプロキシオブジェクトを自動生成します。
  • プロキシオブジェクトはプロトコル変換を担当します:
    • JSON入力に基づいて準拠したSOAP XMLリクエストを生成します。
    • SOAP XMLレスポンスをJSONレスポンスに変換します。
    • Webサービスにアクセスし、SOAPプロトコルの詳細(faultタイプのレスポンスなど)を自動的に処理します。
    • SOAP1.1とSOAP1.2、およびいくつかの拡張機能(WS-Addressingなど)をサポートします。

3.4 設定例

SOAPプラグインの設定パラメータの説明:

パラメータ必須?説明
wsdl_urlはいWSDL URL、例:https://apps.learnwebservices.com/services/tempconverter?wsdl

テスト:

# SOAPプラグインを使用してAPISIXルーティングを設定
# 単一のルートで全ての操作を実行でき、操作名はURLパラメータで指定されます
# これは動的プロキシの利点を反映しており、WSDLファイル内の各操作を手動で解析する必要がありません。
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
    -H 'X-API-KEY: xxx' -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/getCountry",
    "plugins": {
        "soap": {
            "wsdl_url": "http://localhost:8080/ws/countries.wsdl"
        }
    }
}'
curl 'http://127.0.0.1:9080/getCountry' \
    -X POST -d '{"name": "Spain"}'
# 呼び出し成功
HTTP/1.1 200 OK
Date: Tue, 06 Dec 2022 08:07:48 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.99.0
{"currency":"EUR","population":46704314,"capital":"Madrid","name":"Spain"}
# 呼び出し失敗
HTTP/1.1 502 Bad Gateway
Date: Tue, 03 Jan 2023 13:43:33 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.99.0
{"message":"Your name is required.","actor":null,"code":"SOAP-ENV:Server","subcodes":null,"detail":null}

4. 結論

今日の世界では、Webサービスは多くの企業ユーザーが従来のSOAPベースのWebサービスに依存してサービスを提供するまでに進化しています。歴史的な理由とコストの考慮から、これらのサービスを完全にRESTfulサービスにリファクタリングすることは常に適しているわけではありません。その結果、多くの企業ユーザーにとってSOAP-to-RESTの需要が高まっています。

APISIXが提供するSOAP-to-RESTプラグインは、ゼロコードのプロキシ機能を実現し、動的に設定でき、二次開発を必要としないため、企業ユーザーのゼロコストでのビジネス移行と統合に役立ちます。

Tags: