はじめに
この記事では、外部連携を含む処理が失敗した事例をもとに、WireMock、SOAP XML、JAXB、複数の関連データ、再起動後の再読込が重なった障害の切り分け方を整理します。
画面には「処理結果IDを取得できない」とだけ表示されていました。しかし実際には、スタブの誤一致、レスポンスファイルの欠落、XML名前空間の不一致、再起動で戻る設定が順番に現れていました。
扱うのは特定の製品や組織に依存する話ではありません。外部サービスをWireMockで置き換えたアプリケーションで、HTTP 200なのに処理全体が失敗するときの調査手順を扱います。
先に結論
この種の障害では、単一の成功シグナルを正常判定に使わないことが重要です。
- HTTP 200でも、意図しないスタブが返した可能性があります
- XMLが整形式でも、JAXBが期待するQNameやスキーマに一致するとは限りません
- 主となるデータが正しくても、処理途中の追加照会で失敗することがあります
- WireMockのAdmin APIで直っても、再起動後に同じ状態とは限りません
- ジョブが成功しても、WireMockが実リクエストを安定して処理できるとは限りません
設定ファイルを眺めて判断するのではなく、実際に通ったリクエスト、選択されたスタブ、返ったレスポンス、アプリケーションの処理経路をつなげて確認します。
画面のエラーを原因と取り違えない
今回の画面エラーは、処理結果IDを取得できないというものでした。BFFのログを見ると、BFFとバックエンド間の通信は成立していました。バックエンドが異常結果と空の結果IDを返し、BFFが画面向けのエラーに変換していた状態です。
この時点で分かるのは、対象処理のどこかで失敗していることだけです。外部サービス呼び出し、テンプレート生成、追加照会、レスポンス変換のどれが原因かは分かりません。
画面操作の時刻と相関IDを記録し、BFFとバックエンドのログを追います。最後に成功した外部呼び出しと、最初に発生した例外を確認します。後段のNullPointerExceptionを直接原因と決めず、数ステップ前の外部レスポンスをDTOへ変換できているかまで遡ります。
エラーを画面向けに変換しても、原因まで捨てない
画面に内部例外を表示する必要はありません。ただし、画面向けのエラーへ変換するときも、ログには相関ID、失敗した依存先、元の例外を残します。
今回の画面には「処理結果IDを取得できない」とだけ表示されていました。相関IDでバックエンドのログを追うことで、画面エラーの数ステップ前にある外部レスポンスの変換失敗まで遡れます。後段のNullPointerExceptionを直接原因と決めず、最初の例外と最後に成功した外部呼び出しを確認します。
HTTP 200でも、正しいスタブとは限らない
WireMockのrequest journalを見ると、対象のリクエストはWireMockへ届いていました。しかし、個別ケース用に用意したスタブではなく、汎用のフォールバックスタブに一致していました。
フォールバックスタブがHTTP 200を返す設定なら、通信としては成功して見えます。それでもレスポンス内の識別子や属性値が別ケースのものであれば、後続処理は壊れます。
確認する対象はHTTPステータスだけではありません。
- request journalに記録された実リクエスト
- matched stubのIDまたは名称
- mappingのXPath、URL、bodyPatternsなどの条件
prioritybodyFileName- 実際に返ったレスポンスの識別子と期待データ
条件が重なるスタブには、個別ケース側へ小さい数値のpriorityを設定します。WireMockでは数値が小さいスタブが優先されます。
{
"priority": 1,
"request": {
"method": "POST",
"urlPath": "/external-service",
"bodyPatterns": [
{ "matchesXPath": "//lineNumber[text()='target-line']" }
]
},
"response": {
"status": 200,
"bodyFileName": "target-case.xml"
}
}
重要なのは、ローカルのJSONにpriorityが書かれていることではありません。稼働中のWireMockのAdmin APIで、実際に読み込まれたmappingに値が存在することを確認します。
一つのリクエストだけを見ない
対象処理では、最初に指定された主対象の情報を取得した後、関連対象を追加で照会していました。主対象には複数の属性情報が必要で、関連対象には補助情報が必要でした。
主対象のレスポンスを正しく直しても、関連対象用のmappingが参照するXMLが存在しなければ、WireMockはレスポンスを返せません。この場合、後段処理を呼ぶ前に処理が停止します。
処理フローとして必要なデータを表にすると、見落としを減らせます。
| 照会対象 | 役割 | 確認する内容 |
|---|---|---|
| 主対象 | 基本となる属性情報の取得 | 属性の件数、識別子、正常結果 |
| 関連対象 | 追加の補助情報の取得 | 個別対象の属性、正常結果 |
モックのXMLを一つずつ見て正しそうでも不十分です。複数リクエストを通したときに、処理全体が必要とするデータ一式が揃うかを確認します。
また、mappingが一致していてもbodyFileNameの指すファイルが__filesに存在しないと、実行時に失敗します。mappingとレスポンスファイルは常に対で確認します。
SOAP XMLは整形式だけでは足りない
レスポンスファイルを追加すると、次にDTOがnullになる問題が出ることがあります。よくある原因は、SOAP XMLの名前空間とJAXB生成クラスが期待するQNameの不一致です。
例えば、次のXMLは整形式です。
<outPayload xmlns:ns2="http://example.com/service">
しかし、これはns2という接頭辞を宣言しているだけです。接頭辞のないoutPayload自身は名前空間に属しません。JAXBが{http://example.com/service}outPayloadを期待するなら一致しません。
要素を対象の名前空間に属させるには、次のようにします。
<ns2:outPayload xmlns:ns2="http://example.com/service">
接頭辞がns2である必要はありません。重要なのは、要素のQNameがWSDLから生成されたクラスの期待と一致することです。
さらに、生成クラスに存在しない要素がレスポンスに混ざっていることもあります。XMLエディタの構文チェックが通っても、JAXBの変換が成功する保証にはなりません。
DTOが作れない場合は、次の順で比較します。
- WSDLまたはJAXB生成コードでルート要素のnamespaceとlocal nameを確認する
- レスポンスXMLのルート要素と子要素のQNameを確認する
- 正常に動くケースのXMLと差分を取る
- 生成クラスに存在しない要素を削除またはスキーマ側へ反映する
- XML単体ではなく、実際のクライアント経由でDTOが生成されることを確認する
即時反映と永続反映を分けて考える
WireMockのAdmin APIでmappingを登録・更新すると、その場の検証はすぐ行えます。ただし、多くの運用では再起動時にサーバー上のmappingsと__filesから状態が読み直されます。
そのため、次の流れが起きます。
Admin APIで修正する
→ 実リクエストが成功する
→ 再起動する
→ 古い永続ファイルが読み込まれる
→ 障害が再発する
Admin APIは原因確認と一時修正に役立ちます。恒久対応には、修正済みのmapping JSONとレスポンスXMLを、再読込元となる永続配置へ反映する必要があります。
同じmapping JSONを、Admin API経由では正常に登録でき、CI/CD経由では不安定になるなら、切り分けの助けになります。この場合、mappingの記述やWireMockのマッチング仕様だけでなく、ファイル配置、再読込、サービスが利用可能になるまでの経路を調査対象に絞れます。
今回の環境では、再反映の前後で同一と思われるmappingのUUIDが変わることもありました。固定IDを定義していない場合、以前のUUIDを直接参照して404になっても、mappingが消えたとは限りません。UUIDが変わった内部的な契機は確認できていないため、再反映で必ず再採番されるとは断定できません。
調査時は、過去のUUIDを恒久的な識別子として使わず、mapping一覧から、URL、HTTPメソッド、XPath、priority、bodyFileNameなどで現在の対象を探し直します。そのうえで、その時点のUUIDを使って個別のmappingを確認します。
設定があることと、実際に使われたことを分ける
/__admin/mappingsは、WireMockにどのルールが登録されているかを確認するためのAPIです。一方、/__admin/requestsは、WireMockに実際に届いた通信を確認するためのAPIです。
mappings = 設定
requests = 観測
対象mappingが一覧にあっても、実リクエストがWireMockに届いていること、そのmappingが選ばれること、期待するレスポンスを返すことまでは保証しません。設定と観測を分けると、次のように切り分けられます。
| 観測結果 | 主な調査対象 |
|---|---|
| mapping一覧に対象がない | ファイル反映、再読込、mappingの記述 |
| mappingはあるがrequest journalに要求がない | 接続先、名前解決、ルーティング、呼び出し元 |
| request journalにあるがunmatched | URL、ヘッダー、XPath、ボディなどの条件 |
| 別のstubにマッチしている | 汎用mapping、条件の重複、priority
|
| 期待するstubにマッチするが応答が不正 |
bodyFileName、レスポンスファイル、テンプレート |
未マッチのリクエストは/__admin/requests/unmatchedで確認します。さらにNear Missを使うと、どのstubに近く、どの条件が一致しなかったかを確認できます。URLは合うがXPathが違う、ボディは合うがヘッダーが違う、といった問題を見つけるときに有効です。
ジョブの成功ではなく、実リクエストで判定する
再起動ジョブが成功表示になった直後でも、mappingやレスポンスファイルの読み込みが完了していないように見える場合があります。今回も、追加変更をせず少し待つと同じ設定と同じリクエストが正常に処理されました。
原因がWireMockの再起動、ファイル配布、読み込み、コンテナのready状態のどれにあるかは、ログで裏付けられるまで断定できません。これは待機時間だけで解決する、と決めつける話でもありません。再起動後に次の確認を行い、利用可能になった事実を確認します。
- 少し待ってからAdmin APIでmapping一覧を取得する
- 対象mappingの条件、
priority、bodyFileNameを確認する - 主対象と追加照会先の両方へ実リクエストを送る
- request journalで意図したスタブへ一致したことを確認する
- アプリケーションから対象処理を実行し、結果IDなど最終結果を確認する
ジョブのSUCCESSはデプロイ処理の完了を示すもので、エンドツーエンドシナリオの成功を示すものではありません。最終的な判定は、処理フローを通した実行結果で行います。
恒久的に改善するなら、CI/CDの完了条件へreadiness checkを入れる方法があります。固定時間だけ待つのではなく、一定間隔で次を確認し、制限時間内に満たせなければジョブを失敗にします。
- Admin APIが応答する
- 必要なmappingが存在する
- 必要ならprobe用リクエストが期待するレスポンスを返す
これはプロセスが起動したことではなく、スタブサービスが利用可能であることを確認するためのチェックです。
複合障害を短く切り分ける順番
一つ直すと次の問題が見える複合障害では、仮説を増やすより、観測点を固定して順番に進めます。
この順番では、HTTP 200、XMLの構文チェック、mappingファイルの存在、ジョブの成功表示を途中の観測結果として扱います。どれも単独では、処理全体が正しく完了する証拠になりません。
まとめ
WireMockを使った障害では、モックも本番の外部サービスと同じように状態を持つデバッグ対象になります。
今回のようなケースで特に重要なのは、次の点です。
- request journalで、実際に選ばれたスタブを確認する
- mappingの条件だけでなく、
priorityとbodyFileNameまで確認する - 主リクエストの先にある追加照会を追う
- XMLの整形式ではなく、JAXBが期待するQNameとスキーマで確認する
- Admin APIの一時的な変更と、再起動後も残る永続ファイルを分ける
- 再起動ジョブの成功ではなく、実リクエストと最終的な処理結果で判定する