嵐の前の静けさ?:「バックエンド、全部マイクロサービスに移行するから」(絶望)
どうも! ヨーロッパの片隅で、今日も Visual Studio とにらめっこしている C# WPF エンジニアです。普段は金融系か、たまに製造業向けのガチガチなデスクトップアプリの設計と開発をやっています。
海外で働くって、響きはカッコいいですよね。実際、コード書くこと自体は万国共通だし、合理的な議論ができるカルチャーは最高です。スタバのフラペチーノ片手に…なんてのは幻想で、実際はマグカップに濃すぎるコーヒーを淹れて、スタンドデスクで「なんでこの DataGrid の描画が遅いんだ…」って唸ってる毎日です。
そんな平和な(?)日常は、ある日突然、一本のSlackメンションで終わりを告げます。
「@here: [Project Phoenix] Kick-off Meeting tomorrow 10:00. Mandatory.」
(うわ、出たよ、”Phoenix” とか “Odyssey” とか、壮大な名前のプロジェクト…)
海外の現場あるあるですが、こういうデカいプロジェクト名は、だいたい現場のエンジニアにとってはロクなことになりません。
翌朝。ミーティングルームには、普段あまり顔を合わせないインフラチームや、SRE(Site Reliability Engineering)の連中、そしてCTOまでいます。嫌な予感しかしない。
そして、バックエンドのリードアーキテクト(仮に「マーク」としましょう)が、目を輝かせながらこう言いました。
「我々は、レガシーなモノリスシステムを、完全にモダンなマイクロサービス・エコシステムに移行する! Kubernetes上で、Kafkaを使ったイベントドリブンアーキテクチャだ。素晴らしいだろう?」
シーン…。
いや、シーンとしていたのは僕だけでした。周りのバックエンドエンジニアたちは「おぉ!」とか「やっとか!」みたいに盛り上がってる。DevOps チームは「CI/CD パイプラインどうする?」とか技術的な議論を始めてる。
僕ですか? 僕は、血の気が引いていくのを感じていました。
ちょっと待て。
僕が作ってるこの WPF アプリは、その「レガシーなモノリス」とガッツリ密結合してるんだが?
ユーザー(例えば工場のライン管理者とか)はこのデスクトップアプリ上で、リアルタイムにデータを更新し、複雑な業務ロジックを叩いてる。その裏側にあるのは、巨大で、古くて、でも(一応)安定稼働していたモノリスの API 群なんです。
僕の頭の中は、パニックです。
(え、あの50個ある REST API はどうなるの? レスポンスの JSON 構造は? 認証は? あの秘伝のタレみたいになってるトランザクション処理は、マイクロサービスでどう担保するんだ?っていうか、俺のアプリ、全部書き直しか?)
僕は恐る恐る手を挙げました。
「あの…マーク。クライアントサイド(WPF)への影響は?」
マークは、まるで「何を当たり前のことを?」という顔で、僕にこう言いました。
「心配ない。API ゲートウェイを立てて、既存の API 仕様は完全に維持(Maintain)する。 クライアント側は何も変更する必要はないよ。シームレスな移行(Smooth Transition)だ」
出ました。「シームレスな移行」。
海外でエンジニアやってて、この言葉ほど信用できないパワーワードはありません。これは、「理論上はそうなるはずだが、現実には誰も保証できない」の同義語です。
特に僕らのような C# WPF 開発者、つまりリッチクライアント(太ったクライアント)を作っている人間にとって、バックエンドの変更は死活問題です。
Web フロントエンドなら、最悪ブラウザをリロードすればセッションが切れるくらいで済みます(いや、済まないけど)。でも、デスクトップアプリはユーザーのローカル PC で「状態(ステート)」を持ち続けます。何時間も起動しっぱなしです。中途半端なデータがメモリ上に残ったまま、バックエンドの仕様が(彼らが「シームレス」と呼ぶ方法で)こっそり変わったら?
結果は「クラッシュ」です。
「原因不明の例外」として、僕らのチームにバグレポートが飛んできます。
これから海外で働こうとしている皆さんに、まず最初に叩き込んでほしい「人生術」があります。
それは、「バックエンドの刷新は、クライアントサイドエンジニアにとっての地獄の始まりである」 ということです。
そして、この問題は「海外」というフィルターを通すことで、さらに厄介になります。
日本では、こういう巨大プロジェクトの前には、関係各所への「根回し」や、クライアントサイドへの影響を精査する分厚い資料が用意されるかもしれません(まぁ、されない現場も多いですが)。
でも、こちら(海外)では、スピード重視。「アーキテクチャはこっちで決めた。君たちの仕事はそれに対応することだ」というスタンスが基本です。彼らは悪気があって言ってるんじゃない。それが「責務の分離」だと本気で信じているからです。
だから、彼らが言う「Essential migration tools」とか「Strategies for minimizing downtime」なんてのは、全部 彼ら(サーバーサイド) のための道具立てです。
僕らクライアントサイドのエンジニアは、その「移行」という名のハリケーンの中で、自分たちのアプリケーション(と、自分たちの精神衛生)をどう守るか、という全く別の戦略が必要になります。
彼らが「ダウンタイムゼロでカットオーバーする」と言っても、信じてはいけません。
彼らが「API の互換性は 100% 保証する」と言っても、信じてはいけません。
じゃあ、どうするのか?
このブログシリーズでは、インフラの教科書には載っていない、「移行」という名のプロジェクトを「クライアントサイド(特に WPF みたいなデスクトップアプリ)エンジニア」として、どうサバイブしていくか、その実体験ベースの泥臭いテクニックと「人生術」を共有していきます。
Kubernetes の設定方法や、Istio のサービスメッシュの話は一切しません。
僕らがやるべきなのは、嵐が来る前に「防空壕」を掘ることです。
次回(承)は、バックエンドチームが言う「移行ツール」を横目に、僕らが本当に準備すべき「防衛ツール」— つまり、Mock サーバーと API 契約テスト(Pact)について、実体験を交えてガッツリ語っていきます。
クライアントサイドの防衛線: 「移行ツール」って俺たちにとっては「Pact」と「Mock」のことだろ?
(「起」からの続き)
さて、キックオフミーティングで「シームレスな移行(笑)」を宣言された僕らクライアントサイドチーム。
バックエンドの連中は、目をキラキラさせながら「移行ツール」の話で盛り上がってます。曰く、「Terraform でインフラをコード化し」「Istio のサービスメッシュでカナリアリリースを…」「Kibana でログを可視化して…」
もうね、全部彼らの「都合」なんですよ。
彼らにとっての「移行ツール」は、サーバーをどうやって効率よくデプロイするか、どうやってサーバー間の通信を制御するかの話。僕ら WPF アプリが、その結果として「動く」かどうかは、彼らの「責務」の外側なんです。これが海外(というか、責務の分離がハッキリしすぎてる現場)の怖いところ。
「API の仕様は 100% 維持するから」
マークはそう言った。でも、僕ら C#er (Cシャープ使い) は知っています。静的型付け言語にとって、「100% の互換性」がいかに脆い約束かを。
例えば、旧 API(モノリス)では、ユーザー ID がない場合 null を返していたとしましょう。
新 API(マイクロサービス)の担当者が、「あ、null より空の配列 [] のがイケてるっしょ」と、良かれと思って(あるいは、うっかり)変更したとする。
これ、動的型付けの JavaScript とか Python なら、よしなに解釈してくれるかもしれない。
でも、僕らの C# は? Newtonsoft.Json や System.Text.Json でガチガチにデシリアライズしてるこっちは?
はい、一発で例外(Exception)吹いてアプリがクラッシュします。
彼らにとってこれは「仕様の範囲内」の「改善」かもしれない。でも、僕らにとっては「プロダクション障害」なんです。
この認識のズレこそが、僕らが乗り越えるべき最大の壁。
だから、僕らクライアントサイドエンジニアは、バックエンドチームが使う「移行ツール」とは全く別の、「防衛ツール」で武装する必要があります。
彼らがインフラ構築(彼らの「移行」)に夢中になっている間に、僕らは「防空壕」を掘るんです。
僕がいつもこの手のプロジェクトで、チーム(特に若手)に口を酸っぱくして言う「人生術」はこれです。
「性善説で API を待つな。性悪説で API を疑え。そして、動かぬ証拠(コード)で契約(コントラクト)を定義しろ」
この「動かぬ証拠」こそが、僕らにとっての「移行ツール」です。
具体的には、「Mock サーバー」と「Pact (Consumer-Driven Contract Testing)」。この二つです。
防衛ツール①: Mock サーバー(我々の聖域)
まず、バックエンドチームが「新しい開発環境(Dev Env)できたから、そっちに繋いでテストしてよ」とか言い出します。
絶対ダメです。
彼らの新しい Dev Env は、嵐の中心地です。昨日動いたエンドポイントが今日動かないなんてザラ。そんな不安定なものに依存してたら、僕らの開発やテストが全く進まない。「バックエンドが不安定で、今週の WPF のタスクは全部ブロックされました」なんて報告、マネージャーが許してくれるわけないですよね。
そこで、Mock サーバーです。
これは「テスト自動化のため」じゃありません。「我々の開発環境を、バックエンドの混沌から切り離すため」の政治的なツールです。
WireMock.NET でも、なんなら ASP.NET Core Kestrel でサクッと作った偽サーバーでもいい。
やることは一つ。今、現在(移行前)の「正しい」モノリス API が返す JSON レスポンスを、全部ハードコードで返すサーバーを作ることです。
これが最強の「防衛線」になります。
- 開発の継続: バックエンドが火を吹いていても、僕らはこの Mock サーバーに向かって開発を続行できる。
- バグの切り分け: 「Mock 相手だと動く。新 API 相手だと動かない」となれば、問題が 100% バックエンド側にあることが証明できます。犯人探しの泥沼に巻き込まれずに済む。
- 政治力: マネージャーに「なぜ進まない?」と言われた時、「こちらが用意した『仕様通りの』Mock サーバーでは完璧に動作しています。新バックエンド側が、この仕様(契約)を守れていません」と、客観的な事実を突きつけられる。
これは、海外で理不尽な状況から身を守るための、超重要なサバイバル術です。感情論(「そっちが悪い!」)ではなく、事実(「こっちでは動く」)で語るのがグローバルスタンダード。
防衛ツール②: Pact(停戦協定という名の時限爆弾)
Mock サーバーは「守り」のツールです。でも、これだけじゃ不十分。
バックエンドチームが、僕らの知らないところでコッソリ仕様を変えて、それが本番環境にデプロイされるのを防げません。
そこで**「Pact (Consumer-Driven Contract Testing)」**の出番です。
これが「攻め」のツールであり、僕らクライアントサイド(Consumer: 消費者)が主導権を握るための武器です。
難しく聞こえるかもしれませんが、やることはシンプル。
- 僕ら(WPF 側):「このエンドポイント(例: GET /api/v1/users/123)を叩いたら、こういう JSON 構造(例: { “id”: 123, “name”: “string”, “isActive”: true })が返ってこないと、ウチのアプリは死にます」という「期待」をコードで書きます。(これが Consumer Test です)
- Pact の仕事(その1):このテストを実行すると、pact-file.json という「契約書」ファイルが自動で生成されます。
- 僕ら(WPF 側):この「契約書」を、バックエンドチームに叩きつけます。(実際には Pact Broker という共有リポジトリに置きます)
- バックエンドチーム(Provider: 提供者):彼らは、自分たちの CI/CD パイプライン(自動ビルドの仕組み)に、Pact の検証ステップを組み込む義務を負います。
- Pact の仕事(その2):バックエンドの CI が動くたび、Pact があの「契約書」を読み込みます。そして、バックエンドチームが作った「新しいマイクロサービス」に対して、実際にリクエストを飛ばします。
- 結果:もし、彼らの API が返した JSON が、僕らが定義した「契約書」と 1 ミリでも違ったら(例: isActive が null になってた、とか)、Pact が CI を失敗させ、ビルドを赤(Failed)にします。
これが何を意味するか?
僕らのアプリをクラッシュさせるような「悪意なき変更」は、本番環境にデプロイされる前に、彼らのビルドプロセスで自動的にブロックされるんです。
海外の現場では、メールや Slack で「この仕様、変えないでくださいね!」なんてお願いしても、まず忘れられます。忙しいし、文化も違うし、そもそも読んでないかもしれない。
でも、**「CI が赤くなってる(ビルドが失敗してる)」**という事実は、世界中のどんなエンジニアも無視できません。ビルドが通らないコードは、デプロイできないからです。
これが、僕らクライアントサイドエンジニアにとっての、真の「移行ツール」であり「リスク最小化戦略」です。
彼らの Kubernetes や Kafka じゃない。
僕らのコード(アプリ)を守るのは、僕らが書いた「契約書(Pact)」と「聖域(Mock)」なんです。
もちろん、これをバックエンドチームに導入させるのは、タダじゃ済みません。
「なんで俺たちが、君たちの都合で CI をいじらないといけないんだ?」と、絶対に抵抗されます。
ここが、海外で働くエンジニアとしての「交渉術」の見せ所。
「これは『君たちのため』でもあるんだ。これを入れれば、カットオーバー(本番移行)の日に、クライアントが動かないって叩き起こされるリスクがゼロになるんだ」と、彼らのメリットとして説得するんです。
さて、こうして僕らは「防衛線」を張りました。
Mock で開発を守り、Pact で未来のバグを防ぐ。
これで万全か?
いやいや、現実はそんなに甘くありません。
Pact を導入してくれても、Pact ですら検知できない「何か」が、必ず本番移行(カットオーバー)の日に牙を剥きます。
いよいよ次回は、移行当日の地獄絵図(Cutovers & Downtime)と、そこで生き残るための「観測術」についてお話しします。
Cutovers & Downtime(地獄絵図): 「APIは仕様通りだ」「いや、動かない」— 犯人捜しが始まったら
(「承」からの続き)
さて、僕らクライアントサイド(WPF)チームは、やれるだけの準備はした。
バックエンドがまだ影も形もない頃から、WireMock.NET でガチガチの Mock サーバーを立て、全機能が(Mock相手なら)完璧に動くことを証明した。
そして、「契約(コントラクト)テスト」の Pact を導入させ、バックエンドチームの CI パイプラインに「時限爆弾」を仕掛けることにも成功した。
Pact のダッシュボードは、見事なまでにオールグリーン。
バックエンドチームのリード、マーク(仮名)も、「ほら見ろ、契約は 100% 守られてる。シームレスな移行(Smooth Transition)は可能だ」と自信満々だ。
…まぁ、僕はこの「オールグリーン」という表示を、信号機じゃなくて、病院の心電図がフラットライン(停止)になった不吉なサインみたいに感じてたけど。
そして、運命のカットオーバー当日。
ヨーロッパの冬の夜、深夜 2 時。僕ら関係者は、エナジードリンク片手に War Room(実際はデカい Zoom ミーティング)に集結した。
インフラチームが「DNS を切り替えるぞ… 3, 2, 1… Go!」と宣言する。
モノリスに向けられていたトラフィックが、ピカピカのマイクロサービス群(Kubernetes クラスター)へと一斉に向けけられた。
SRE(Site Reliability Engineering)チームのダッシュボードに、トラフィックが流れ込む様子が映し出される。
「サーバーサイド、CPU・メモリ安定。200 OK のレスポンスが多数!」
マークが、Zoom の向こうで満足げに頷いてる。
「よし、クライアントサイド、テストユーザーでログインして、基本動作を確認してくれ」
僕ですか? 僕はもう、祈るような気持ちで、工場のラインに置いてあるテスト端末(僕の WPF アプリが動いてる)にリモートデスクトップで接続して、ログインボタンをクリックした。
………。
………。
(ログイン画面から、遷移しない)
あれ?
クリックは効いてる。ローディングのクルクル(WPF でいう BusyIndicator)は回ってる。
でも、メイン画面が出てこない。
冷や汗が背中を伝うのが分かった。
僕:「…だめだ。ログインできない。タイムアウトしてるっぽい」
Zoom が一瞬、静まり返る。
マーク:「そんなはずはない! 認証サービスのログは? SRE!」
SRE:「認証サービス、200 OK 返してます! ログにも『Token Issued (トークン発行完了)』って出てる!」
僕:「いや、こっちには何も返ってこない。タイムアウトだ」
マーク:「ファイアウォールか!? ネットワークチーム!」
ネットワーク:「いや、疎通(Ping)は確認した! ポートも開いてる!」
地獄の始まりだった。
Pact が保証してくれない「3つの悪魔」
Pact でオールグリーンだったのに、なぜ動かないのか?
これから海外でエンジニアを目指す皆さんに、このリアルな「人生術」を叩き込んでほしい。
Pact(契約テスト)は、「JSON の構造」は保証するが、「それ以外」は何も保証しない。
この「それ以外」にこそ、悪魔は潜んでるんです。僕がこの日遭遇したのは、特にタチの悪い「3 体の悪魔」だった。
悪魔その①: パフォーマンス(応答時間)
散々揉めた結果、ログインできない原因は、ネットワークチームのファイアウォール設定ミスだったことが判明した(ほら見たことか)。
午前 4 時。ようやくログインできた。
「よし、これで一安心だ…」
そう思ったのも束の間、今度は QA チームから悲鳴が上がる。
「製品リストの画面を開くのに、30 秒 かかる!」
は?
その画面は、モノリス時代は 1 秒もかからず表示されていた、このアプリの「肝」となる画面だ。30 秒? 業務にならない。
バックエンドチームに確認する。
マーク:「いや、API は 200 OK を返してる。仕様通りだ」
出たよ、「仕様通り」。
Pact は、「製品リストの JSON が、定義した構造通りであること」はチェックする。でも、「その JSON が 1 秒で返ってくること」まではチェックしない。
原因は、典型的なマイクロサービスの罠だった。
モノリス時代は、1 回の API コールで、DB からゴソッとデータを取ってきて返していた。
新しいマイクロサービスでは、「製品サービス」「在庫サービス」「価格サービス」が全部バラバラ。API ゲートウェイが、裏側でそいつらを一個一個呼び出して、結果をマージして返してた(いわゆる N+1 問題の亜種)。
そりゃ遅いわ。
僕ら WPF みたいなリッチクライアントは、非同期(async/await)で組むのが当たり前だ。でも、非同期にしたって、データが 30 秒来なけりゃ、30 秒間ユーザーを待たせることに変わりはない。
悪魔その②: 認証とセキュリティ(「環境」の違い)
次の問題は、「特定のユーザーだけ、データが何も表示されない」というバグだった。
僕:「マーク、GET /api/v2/customer/xyz が 401 Unauthorized (認証エラー)を返してる。でも、他のユーザーは 200 OK だ」
マーク:「そんなはずはない。Pact は通ってるんだぞ?仕様通りだ」
もうそのセリフは聞き飽きた。
これも Pact の限界だった。
Pact の CI(自動テスト)で使う「認証トークン」は、たいていテスト用のダミーか、権限がガチガチに緩い「管理者トークン」だ。
でも、本番環境では?
「一般ユーザー」「マネージャー」「工場ラインのオペレーター」で、全部権限(スコープ)が違う。
新しい認証サービスが発行する JWT(JSON Web Token)の、scope クレーム(権限情報)の定義が、モノリス時代と微妙〜に違ってたんです。
Pact は、その「微妙な違い」があるダミートークンではテストしてなかった。だから CI はグリーン。でも、本番の「特定のユーザー」だけが弾かれる。
悪魔その③: データの「状態(ステート)」
一番最悪だったのがこれだ。
「注文を作成(POST)すると、アプリがクラッシュする」
僕:「おい! 注文 API が 500 Internal Server Error を返してるぞ!」
マーク:「こっちのログでは、DB にデータは入ってる! 仕様通りだ!」
僕:「こっちはクラッシュしてるんだよ! Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON object って例外が出てる!」
何が起きていたか?
- 旧モノリス API: 注文(POST)に成功したら、作成された注文データ(JSON オブジェクト)を返していた。
- 新マイクロサービス API: 注文(POST)に成功したら、
201 Createdというステータスコードだけを返し、**レスポンスボディは空(Empty)**だった。
バックエンドの連中からすれば、「POST なんだから、成功ステータスだけ返せば十分だろ? ボディが空なのは仕様通りだ」という理屈だ。
でも、僕の WPF アプリは?
POST が成功したら、必ず返ってくるはずの「注文オブジェクト」をデシリアライズ(C# のクラスに変換)しようとして、ボディが空だから例外を吐いてクラッシュしてた。
Pact は?
Pact は、POST リクエストの「レスポンスボディ」の契約をチェックするのが苦手(というか、そういうユースケースが弱い)なんです。特に「成功時はオブジェクト、失敗時はエラーオブジェクト」みたいな分岐があると、途端に複雑になる。
地獄の犯人捜しと、クライアントサイドの「人生術」
午前 6 時。空が白み始めてきた。
War Room は、疑心暗鬼と疲労で最悪の空気だ。
「クライアント側が古いキャッシュを使ってるんじゃないか?」
「WPF アプリのデシリアライズ処理がバグってるんだろ?」
バックエンドからすれば、自分たちのログは「正常(仕様通り)」なんだから、悪いのはクライアントだ、という論理になる。
これが「犯人捜し」の始まりだ。
ここで感情的になったら負け。
日本人的な「すみません、調査します…」もダメ。海外では、謝ったら「非を認めた」とみなされる。
ここでやるべき「人生術」は、**「客観的かつ定量的な事実」**だけを、冷静に、しかし断固として突きつけることだ。
人生術①: 「Mock」という最強の盾を使え
僕は Zoom で画面共有し、こう言った。
「OK、マーク。今からこのテスト端末の接続先を、君たちの新 API から、僕が『承』の工程で作った Mock サーバー(旧 API の仕様と 100% 同じレスポンスを返す)に切り替える」
WPF アプリの設定ファイル(appsettings.json)の URL を書き換えて、再起動する。
「はい、見ててくれ。ログイン…(1 秒)。製品リスト…(1 秒)。注文作成…(成功)。全部動く」
「これで、問題が WPF アプリ側(クライアントサイド)にないことは、100% 証明された。問題は、君たちの新 API が『旧 API の仕様通り』になっていないことだ」
この「切り分け」ができるだけで、クライアントサイドの立場は劇的に強くなる。
人生術②: 「フィーチャーフラグ」でダウンタイムを最小化しろ
パフォーマンス問題(30 秒かかる画面)はどうするか?
バックエンドチームが「今からチューニングする」と言い出したが、そんなの何時間かかるか分からない。
「マーク、その API はビジネスのコアだ。30 秒は許容できない。今からクライアント側で、その機能だけ旧 API(モノリス)にフォールバック(切り戻し)させる」
あらかじめ WPF アプリに仕込んでおいた「フィーチャーフラグ」を有効化する。
これで、他の API は新しいマイクロサービスを使いつつ、問題の「製品リスト API」だけ、まだ生きている旧モノリスを参照しにいく。
これで、ユーザーは業務を継続できる。
これが、僕らクライアントサイドにしかできない、真の「ダウンタイム最小化戦略」だ。
インフラチームがやる「全切り戻し(ロールバック)」より、よっぽどスマートだろ?
人生術③: 「生ログ」という動かぬ証拠を叩きつけろ
注文クラッシュ(レスポンスボディが空)の問題。
「仕様通りだ」と言い張るマークに、僕は WPF アプリが出力した「生ログ」を Slack に貼り付けた。
REQUEST: POST /api/v2/orders
...(リクエストボディ)...
RESPONSE: 201 Created
HEADERS: ...
BODY: (EMPTY)
「これが、君たちのサーバーが返した『生』のレスポンスだ。そして、こっちが旧 API(Mock)が返していたレスポンスだ」
RESPONSE: 200 OK
HEADERS: ...
BODY: { "orderId": 123, "status": "Created", ... }
「僕のアプリは、このボディが返ってくることを『期待』している。これが『契約』だ。Pact でカバーできていなかったのは、こちらの落ち度でもある。だが、事実として、互換性が壊れている」
地獄のような夜が明けた。
フィーチャーフラグでの部分的な切り戻しと、いくつかの API の緊急修正(空ボディじゃなく、ちゃんとオブジェクトを返すようにしてもらった)で、なんとかサービスは稼働した。
だが、戦いは終わっていない。
パフォーマンス問題は山積みだ。
そして、バックエンドチームは相変わらず「こっちのログは正常だ」と言い張るだろう。
僕らは、この「ブラックボックス」と化したマイクロサービス群の「中身」を知る必要がある。
彼らのダッシュボードに映る「CPU 使用率」や「メモリ使用量」じゃない。
僕らクライアントサイド(WPF)にとって、本当に「観測」したいものは何か?
次回(結)は、この地獄のカットオーバーを乗り越えた先に僕らが見つけた、真の「Observability(可観測性)」について語ろう。
生き残るための「観測」: 本当に知りたいのはサーバーのCPU使用率じゃない
(「転」からの続き)
地獄のカットオーバーナイトを乗り越え、僕らは勝利…とは言えないまでも、なんとか「全面停止」は避けることができました。
フィーチャーフラグによる旧 API への部分的な切り戻しや、認証 API の緊急修正で、ユーザーは再び WPF アプリを使えるようになりました。しかし、散発的な 500 Internal Server Error や、原因不明のパフォーマンス低下は、依然として残っていました。
午前 9 時。チームは疲労困憊ですが、業務は始まっています。
マーク(バックエンドリード)は、相変わらず Kibana と Grafana のダッシュボードを指差して言います。
「見てくれ。CPU 使用率は安定している。メモリも潤沢だ。エラーログ? ないね。このクラスターはグリーンだ」
僕:「マーク、うちのユーザーは『製品リストの画面が時々フリーズする』って言ってる。これ、クライアントのバグじゃない。API の応答が遅いんだ」
マーク:「いや、僕らの APM(Application Performance Monitoring)ツールでは、すべてのマイクロサービスが 100ms 以内でレスポンスを返してることになってる」
僕らの間に、深い、深い溝が走ります。
僕らのアプリが動いているのは、工場内のセキュリティがガチガチなローカル PC です。マークたちのサービスが動いているのは、最新鋭のクラウド環境。
「僕らの観測」と「彼らの観測」は、全く違う世界を映しているんです。
これから海外の現場に行く皆さん。この事実を肝に銘じてください。
バックエンドの「グリーンなダッシュボード」は、クライアントサイド(ユーザー体験)の健全性を何一つ証明しない。
では、僕らクライアントサイドのエンジニアが、この「ブラックボックス」と化したマイクロサービス群と、どう戦い、どうやって問題を解決に導けばいいのか?
答えは、**「彼らの土俵で戦うための武器」**を持つことです。それが、真の **Observability(可観測性)**です。
僕らが本当に知りたいのは、サーバーの CPU 使用率じゃありません。
知りたいのは、**「ユーザーがログインボタンを押してから、最終的な在庫データがユーザーの画面に描画されるまでの、全行程の所要時間と、その内訳」**です。
そして、そのための命綱が、次の 3 つです。
命綱①: Correlation ID (相関ID) を実装せよ
分散システムでは、一つのリクエストが、認証サービス、ユーザーサービス、在庫サービス…と、複数のマイクロサービスを渡り歩きます。ログも、それぞれのサービスにバラバラに吐き出されます。
このバラバラになったログを、一本の線で結びつけるIDが、Correlation ID(相関 ID)です。
これはもう、お願いとかじゃなくて、WPF アプリ側の責務として、ガチで実装すべきです。
- クライアント側(WPF)で ID を生成:ユーザーがアプリを起動した瞬間、もしくは新しいセッションが始まった瞬間に、Guid.NewGuid() などでユニークな ID を生成します。
- 全リクエストのヘッダーに埋め込む:HttpClient を使ったすべての API リクエストの HTTP ヘッダー(例: X-Correlation-ID)に、その ID をセットして送信します。
- バックエンドチームに義務付ける:「我々が送ったこの X-Correlation-ID を、君たちの API ゲートウェイが受け取り、すべての下流マイクロサービスのログに含めること。そして、すべての応答ヘッダーにも含めてクライアントに返すこと」を、マークに厳命します。
これが通れば、ユーザーから「10:05 にボタンを押したらエラーが出た」と報告があったとき、僕らは Kibana に行って、その時刻のログをCorrelation ID で一発検索できます。
エラーがどこで発生したのか、そのリクエストがどのサービスをどう巡ったのか、一瞬でトレース可能になります。海外の現場での犯人捜しにおいて、これほど強い武器はありません。
命綱②: 分散トレーシングを見せてもらえ
Correlation ID のログは「点」の情報です。それを「線」にしてくれるのが、**分散トレーシング(Distributed Tracing)**です。
これは Opentracing や Zipkin、Jaeger といったツールが実現する仕組みで、マークたちが導入しているはずです。
僕らは C# WPF 開発者だから、そのツールの仕組み自体を完全に理解する必要はありません。しかし、その「結果」を僕らのために活用する必要があります。
僕:「マーク、あのパフォーマンスが 30 秒かかる API の Correlation ID を見てくれ。このトレースを解析してほしい」
マークが分散トレーシングの UI で ID を検索すると、一本の線が表示されます。
その線には、「API ゲートウェイで 100ms」「ユーザーサービスで 50ms」「在庫サービスで 29,000ms(29 秒)」のように、時間がかかっているサービスが可視化されます。
これを見せつけられれば、マークはもう「仕様通りだ」とは言えません。客観的なデータで、「犯人」が在庫サービスであることが証明されたからです。
海外の現場で「議論」を制するのは、感情や肩書きじゃなく、この**視覚化されたファクト(事実)**なんです。
命綱③: クライアントサイドの「生の声」を上げよ
そして、これが最も重要な「人生術」です。
バックエンドの観測性は、あくまで「サーバー視点」。僕らクライアントサイドエンジニアは、「ユーザー視点」のメトリクスを収集し、それをデータとして突きつける必要があります。
WPF アプリ内に、メトリクス収集の仕組みを組み込むんです。
- API 応答時間(クライアント計測):
HttpClientがリクエストを送信してから、レスポンスヘッダーを受信するまでの時間をStopwatchで正確に計測する。 - UI 描画時間: データグリッドにデータが流し込まれてから、実際に画面に描画が完了するまでの時間を計測する。
これらのメトリクスを、Application Insights や Sentry といった APM ツール(クライアントサイド対応のもの)に送信します。
マークのダッシュボードがグリーンでも、僕らのダッシュボードには「平均製品リスト表示時間:5.2 秒」というレッドアラートが表示されます。
僕:「マーク、君のログでは 100ms かもしれない。でも、このユーザー端末から計測されたデータでは 5 秒だ。この 4.9 秒の差は、ネットワーク、セキュリティ層、あるいはクライアント側の UI スレッドのブロックが原因だ。一緒に切り分けよう」
**「ユーザー体験」という、最も強力な武器で戦う。**これが、海外のエンジニアとして、大プロジェクトで発言力を保つ唯一の方法です。
シリーズ総括: 海外エンジニアとしてのサバイバル人生術
このブログシリーズで僕がお伝えしたかったのは、単なる C# や WPF の話ではありません。海外の現場で、異なる文化や異なるチームと協業し、自分のプロダクトを守り抜くための「人生術」です。
- 性悪説で防御せよ(起・承): バックエンドの約束を鵜呑みにせず、Mock と Pact で自分の聖域と契約を確保する。
- データで戦え(転): 問題が発生したら、感情論ではなく、Mock での切り分けやフィーチャーフラグによる縮退運転など、「定量的なアクション」と「切り分けの証拠」で対応する。
- 観測で主導権を握れ(結): Correlation ID とクライアントサイドメトリクスで、バックエンドのブラックボックスをユーザー視点で可視化する。
海外で働くということは、技術力の高さ以上に、**「理不尽な状況を、いかに客観的なデータと戦略で乗りこなすか」**が問われる場所です。
WPF のような成熟した技術を支えるクライアントサイドのエンジニアの仕事は、「ユーザー体験」という最も重要な価値を守り抜くことです。それは、Kubernetes クラスターの安定性よりも、ずっと尊く、難しい仕事です。
大変なことも多いけど、この経験は間違いなく皆さんを強く、そして、どこでも通用するタフなエンジニアにしてくれるはずです。頑張ってください!

コメント