多くのチームは、既存の SOAP を置き換える権限を持っていません。
現実に必要なのは、「SOAP を消すこと」よりも、「利用側のチームが生の SOAP を直接扱わなくてよい状態を作ること」です。
その意味で、現実的な移行は全面リライトではなく、よりよい境界を前に置いて利用側を順番に移していく作業です。この記事では、その進め方を解説します。
なぜ移行するのか
移行を検討する前に、その理由を明確にしておきましょう。チーム全体の合意形成に不可欠です。
開発速度: REST + JSON は SOAP + XML と比べて、リクエストの構築・デバッグ・テストがはるかに高速です。新しい開発者のオンボーディングも短縮できます。
ツールの選択肢: REST API のツール(Postman、Swagger UI、各種クライアントライブラリ)は SOAP のツールと比べて圧倒的に充実しています。
フロントエンド連携: JavaScript / TypeScript のフロントエンドから直接呼び出せます。SOAP ではサーバーサイドのプロキシが必要なケースがほとんどです。
採用とサポート: SOAP 対応のライブラリは新規開発が減少しており、メンテナンスも縮小傾向にあります。
移行戦略の選択
ストラテジー 1: ビッグバン移行(非推奨)
既存の SOAP サービスを一度にすべて REST に書き換える方法です。
[クライアント] → [SOAP サービス]
↓ 一斉切り替え
[クライアント] → [新 REST サービス]
リスク: 移行期間中のダウンタイム、バグ混入時の広範な影響、ロールバックの困難さ。小規模なサービスを除いて推奨しません。
ストラテジー 2: Strangler Fig パターン(推奨)
既存の SOAP サービスの前にプロキシ層を置き、エンドポイントを一つずつ REST に移していく方法です。Martin Fowler が提唱した Strangler Fig パターンに基づいています。
[クライアント] → [API ゲートウェイ / プロキシ]
├─→ [REST エンドポイント A] (移行済み)
├─→ [REST エンドポイント B] (移行済み)
└─→ [SOAP サービス] (未移行のオペレーション)
ストラテジー 3: SOAP-to-REST プロキシ(最速)
SOAP サービスはそのまま稼働させ、前段に REST プロキシを置いて JSON ⇔ XML の変換を自動化する方法です。
[クライアント] → [REST プロキシ] → [SOAP サービス]
JSON ⇔ XML 変換
ステップバイステップの移行手順
ステップ 1: 現状の棚卸し
まず、移行対象の SOAP サービスのオペレーションを一覧化します。
# WSDL からオペレーション一覧を抽出
curl -s "https://example.com/service?wsdl" | \
grep -oP '<wsdl:operation name="\K[^"]+' | sort -u
# 出力例:
# CreateUser
# DeleteUser
# GetUser
# ListUsers
# UpdateUser
各オペレーションについて以下を記録します。
| オペレーション | 呼び出し頻度 | 依存クライアント数 | 複雑度 | 優先度 |
|---|---|---|---|---|
| GetUser | 高 | 5 | 低 | 1 |
| ListUsers | 中 | 3 | 低 | 2 |
| CreateUser | 中 | 2 | 中 | 3 |
| UpdateUser | 低 | 2 | 中 | 4 |
| DeleteUser | 低 | 1 | 低 | 5 |
ステップ 2: REST API の設計
SOAP オペレーションを REST のリソース指向に変換します。
# SOAP オペレーション → REST エンドポイントの変換
GetUser(userId) → GET /api/v1/users/{userId}
ListUsers(filters) → GET /api/v1/users?department=engineering
CreateUser(data) → POST /api/v1/users
UpdateUser(data) → PUT /api/v1/users/{userId}
DeleteUser(userId) → DELETE /api/v1/users/{userId}
設計のポイント:
- SOAP のオペレーション名を HTTP メソッド + リソースパスに変換
- リクエストボディは XML ではなく JSON に
- エラーレスポンスは HTTP ステータスコードを適切に使用
// SOAP Fault → REST エラーレスポンスの変換例
// soap:Client Fault → 400 Bad Request
{
"error": {
"code": "INVALID_REQUEST",
"message": "userId は正の整数である必要があります",
"details": [
{
"field": "userId",
"reason": "値が 0 以下です"
}
]
}
}
ステップ 3: プロキシ層の構築
最もシンプルなアプローチとして、Express.js でプロキシを構築する例を示します。
const express = require('express');
const soap = require('soap');
const app = express();
app.use(express.json());
let soapClient;
// 起動時に SOAP クライアントを初期化
async function initSoapClient() {
soapClient = await soap.createClientAsync(
'https://example.com/service?wsdl'
);
soapClient.setSecurity(
new soap.WSSecurity('user', 'pass')
);
}
// REST エンドポイント: GET /api/v1/users/:id
app.get('/api/v1/users/:id', async (req, res) => {
try {
const [result] = await soapClient.GetUserAsync({
userId: parseInt(req.params.id)
});
// SOAP レスポンスを JSON に変換
res.json({
id: result.user.id,
name: result.user.name,
email: result.user.email,
department: result.user.department || null
});
} catch (error) {
// SOAP Fault を HTTP エラーに変換
if (error.root?.Envelope?.Body?.Fault) {
const fault = error.root.Envelope.Body.Fault;
const isClientFault = fault.faultcode?.includes('Client');
res.status(isClientFault ? 400 : 500).json({
error: {
code: fault.faultcode,
message: fault.faultstring
}
});
} else {
res.status(500).json({
error: { message: 'Internal server error' }
});
}
}
});
// REST エンドポイント: POST /api/v1/users
app.post('/api/v1/users', async (req, res) => {
try {
const [result] = await soapClient.CreateUserAsync({
name: req.body.name,
email: req.body.email,
department: req.body.department
});
res.status(201).json(result.user);
} catch (error) {
res.status(500).json({ error: { message: error.message } });
}
});
initSoapClient().then(() => {
app.listen(3000, () => console.log('REST proxy running on port 3000'));
});
ステップ 4: テストの整備
移行前後でレスポンスの一貫性を検証するテストを作成します。
const assert = require('assert');
// SOAP と REST の結果を比較するテスト
async function verifyMigration(userId) {
// SOAP で直接呼び出し
const [soapResult] = await soapClient.GetUserAsync({ userId });
// REST プロキシ経由で呼び出し
const restResponse = await fetch(`http://localhost:3000/api/v1/users/${userId}`);
const restResult = await restResponse.json();
// 結果が一致することを確認
assert.strictEqual(restResult.id, soapResult.user.id);
assert.strictEqual(restResult.name, soapResult.user.name);
assert.strictEqual(restResult.email, soapResult.user.email);
console.log(`ユーザー ${userId} の検証: OK`);
}
ステップ 5: 段階的な切り替え
クライアントを一つずつ REST エンドポイントに切り替えます。
Week 1: 内部管理ツール → REST に切り替え(影響範囲小)
Week 2: モバイルアプリ → REST に切り替え
Week 3: パートナー連携 → REST に切り替え(事前通知必要)
Week 4: 全クライアント移行完了 → SOAP エンドポイント廃止予告
ステップ 6: SOAP サービスの段階的廃止
すべてのクライアントが REST に移行した後、SOAP エンドポイントの廃止を進めます。
- SOAP エンドポイントに非推奨ヘッダーを追加
- 一定期間(3-6 ヶ月)の並行稼働
- アクセスログで残存クライアントがないか確認
- SOAP エンドポイントの停止
移行を加速する方法
自前のプロキシ構築は柔軟性がありますが、SOAP オペレーションごとにエンドポイント定義、型変換、エラーハンドリングを実装する必要があり、数十のオペレーションがある場合は膨大な作業量になります。
SOAPless は WSDL を自動解析して REST エンドポイントを生成するため、プロキシ層のコーディングが不要です。WSDL の URL を入力するだけで、約 30 秒ですべてのオペレーションに対応する REST API が利用可能になります。既存の SOAP サービスには一切変更を加えず、クライアント側だけを REST に切り替えられるため、移行リスクを最小限に抑えられます。
段階的な移行でも、一括移行でも、SOAPless がプロキシ層の構築コストをゼロにします。