ClassLab Engineering の Dev チームメンバーが執筆しました。
この記事は、外部API連携・SOAP/REST設計・SSL認証管理に興味があるバックエンド/インフラエンジニア向けです。
1. 背景
ClassLab は数千社超のパートナー企業と連携し、年間15万件超のライフライン(電気・ガス・水道)申込を処理しています。この処理の中核が、電力広域的運営推進機関(OCCTO)のスイッチング支援システムとのAPI連携です。
OCCTOのAPIはSOAP/WSDLベースで、SSL相互認証が必須です。RESTful APIが主流の現在において、SOAPベースの政府系APIと安定的に連携し続けるために得た教訓は、同様の外部API連携に取り組むエンジニアにとって参考になるはずです。
本記事では、前回の記事で紹介したシステム全体のアーキテクチャではなく、API連携を運用し続ける中で得た実践的な教訓にフォーカスします。
2. 課題
| 課題 | 想定 | 現実 |
|---|---|---|
| SOAP通信 | WSDLを読めば実装できる | WSDL仕様と実際の挙動に差異がある |
| SSL相互認証 | 証明書を設定すれば終わり | 証明書更新・環境差異・タイムアウトの罠 |
| エラーハンドリング | エラーコードを見れば原因がわかる | 同じエラーコードで複数の原因がある |
| バッチ処理 | 全件成功 or 全件失敗 | 部分的な失敗が日常的に発生 |
| テスト | モックで十分 | OCCTO本番環境の挙動は再現困難 |
REST APIの経験だけでSOAP連携に臨むと、これらのギャップに一つずつ躓くことになります。
3. 設計(教訓として体系化)
教訓1: WSDLを信じすぎない
OCCTOのWSDLファイルは型定義とメソッド名を提供しますが、以下の点で仕様書(Excel)と照合する必要がありました。
- 任意項目の扱い: WSDLでは
minOccurs="0"でも、実際には送信しないとエラーになるフィールドがある - 文字コード: WSDL上はUTF-8だが、一部フィールドでShift_JISエンコードが暗黙に期待される
- 日付フォーマット: XMLスキーマの
dateTime型だが、実際はyyyyMMdd形式の文字列を期待
flowchart LR
WSDL[WSDL定義] --> IMPL[自動生成コード]
SPEC[Excel仕様書] --> VALIDATE[バリデーション層]
IMPL --> |差異あり| FIX[手動補正]
VALIDATE --> FIX
FIX --> FINAL[最終実装]
教訓: WSDLの自動生成だけに頼らず、仕様書ベースのバリデーション層を別途構築する。この層がWSDLと現実のギャップを吸収する。
教訓2: SSL相互認証は「設定」ではなく「運用」
クライアント証明書のセットアップは初回だけの作業に見えますが、実際は以下の運用タスクが継続的に発生します。
| タスク | 頻度 | リスク |
|---|---|---|
| 証明書の有効期限監視 | 日次 | 期限切れ=API全停止 |
| 証明書ファイルのデプロイ | 更新時 | 環境差異による接続失敗 |
| 中間CA証明書の更新 | 不定期 | OCCTO側の変更通知が遅い |
| テスト環境と本番環境の証明書分離 | 常時 | テスト証明書で本番に接続するリスク |
// 証明書有効期限の自動チェック(日次バッチ)
$certPath = config('occto.client_cert_path');
// エラーハンドリングは省略(実装では証明書読み込み失敗時のアラートあり)
$certData = openssl_x509_parse(
file_get_contents($certPath)
);
$expiresAt = Carbon::createFromTimestamp(
$certData['validTo_time_t']
);
$daysLeft = now()->diffInDays($expiresAt, false);
if ($daysLeft <= 30) {
// Slack通知: 証明書更新が必要
$this->notifySlack(
"OCCTO証明書の有効期限まで{$daysLeft}日です"
);
}
教訓: 証明書管理を「初期設定」ではなく「運用プロセス」として設計し、有効期限監視と更新手順を自動化する。
教訓3: エラーの「原因」はAPIの外にある
OCCTO APIのエラーで最も厄介なのは、返却されたエラーコードだけでは原因が特定できないケースです。
たとえば再点申込(IF_10410)のエラーには以下の可能性があります。
- 供給地点特定番号が存在しない
- 既に別の小売事業者が申込済み
- 設備情報に問題がある(メーター交換中など)
- 前回の廃止手続きが完了していない
これらを切り分けるには、エラー発生時に追加で設備情報照会(IF_10110)と事業者一覧取得(IS_30110)を呼び出す必要があります。
flowchart TD
ERR[申込エラー発生] --> INV[自動調査開始]
INV --> EQ[設備情報照会]
INV --> BIZ[IS_30110 事業者一覧取得]
EQ --> |設備異常| R1[設備側の問題と判定]
EQ --> |正常| BIZ
BIZ --> |他社申込あり| R2[競合申込と判定]
BIZ --> |なし| R3[OCCTO原文を返却]
R1 --> SF[Salesforceに調査結果を自動記録]
R2 --> SF
R3 --> SF
教訓: 外部APIのエラーハンドリングは「エラーを捕捉する」だけでなく「エラーの原因を調査する」仕組みをセットで設計する。
教訓4: バッチの「部分失敗」は正常系
数百件の申込を一括処理するバッチでは、全件成功を前提にした設計は機能しません。部分的な失敗が日常的に発生するため、以下の設計が必要です。
- 個別レコード単位のtry-catch: 1件の失敗が残りの処理を止めない
- 失敗レコードの調査結果付きレスポンス: 失敗理由をレスポンスに含める
- 冪等性の確保: 同じバッチを再実行しても二重処理にならない
教訓: バッチ処理では「全件成功」を正常系、「部分失敗」を異常系と考えるのではなく、「部分失敗」を正常系として設計する。
4. 実装(モックテスト戦略)
OCCTO APIの実体はSSL相互認証付きSOAPエンドポイントであり、テスト環境でも本番同様の証明書設定が必要です。開発効率を維持するため、3層のテスト戦略を採用しました。
3層テスト
| 層 | テスト種別 | 対象 | 実行速度 |
|---|---|---|---|
| L1 | バリデーションテスト | DTO・FormRequest | 高速(API呼び出しなし) |
| L2 | モックレスポンステスト | サービスクラス | 中速(XMLファイルから応答) |
| L3 | API統合テスト | 実OCCTO API | 低速(認証情報必須) |
// L2: モックレスポンスを使ったテスト
public function test_relight_success(): void
{
// OCCTO実APIのXMLレスポンスを保存したファイルから読み込み
$mockXml = file_get_contents(
base_path('occto_mock_responses/IF_10410/success.xml')
);
$this->mock(SoapHttpClient::class)
->shouldReceive('__soapCall')
->andReturn(
simplexml_load_string($mockXml)
);
$response = $this->postJson('/api/occto/sf/relight', [
// テスト用の架空値(実在しない供給地点特定番号)
'supplyPointId' => '000000000000000000000',
'retailerCode' => 'X9999',
]);
$response->assertStatus(200)
->assertJson(['status' => 'success']);
}
教訓: 外部APIのテストは「モックで完結」ではなく、実際のAPIレスポンスXMLを保存してモックの材料にする。本番で遭遇した新しいエラーパターンは随時モックに追加する。
5. 結果(数値)
| 指標 | 状態 | 補足 |
|---|---|---|
| OCCTO連携API数 | 8 SOAP API | 25以上のエンドポイント |
| 月間処理件数 | 1万件超 | 再点・廃止・照会・変更の合計 |
| エラー自動調査率 | 100% | 全エラーに対して追加調査を実行 |
| SSL証明書の無停止更新 | 達成 | 有効期限30日前に自動通知 |
| モックレスポンス蓄積数 | 数十パターン | 本番で遭遇したエラーを随時追加 |
| テスト実行時間(L1+L2) | 数秒 | API呼び出しなしで高速 |
特に教訓3(エラー自動調査)の効果は大きく、オペレーターの手動調査時間が実質ゼロになりました。
6. 展望
次に取り組む課題
- SOAP–>REST移行のアダプタ設計: OCCTO側のAPI刷新に備え、プロトコル非依存のサービス層を一緒に設計してくれるエンジニアを探しています
- エラーパターンのナレッジベース化: 蓄積されたエラーパターンから自動分類・自動対処を構築する予定です
関連記事
- 15万件/年のライフライン基幹システムの信頼性設計 — 本記事のシステム全体のアーキテクチャ
- Claude CodeとGitHub Copilotを全員が使う開発チームの実態 — AI駆動開発で本システムも構築
採用情報
ClassLab では、政府系APIや大規模外部連携の設計・改善を一緒に進めてくれるバックエンドエンジニアを募集しています。SOAP/RESTを問わず、外部連携の実務経験を活かせるポジションです。