【海外WPFエンジニアの告白】「状態」しか見てなかった俺が、「イベント」を追うようになって設計者として“覚醒”した話。

その設計、「なぜ」に答えられる?

海外の現場に放り込まれて、最初にぶち当たった壁。それは「英語」でも「コーディング規約」でもなく、**「『なぜ』の深掘りレベル」**でした。

俺がアサインされたのは、とある金融系のトレーディングシステム。もちろんクライアントはWPF。ゴリゴリのデスクトップアプリです。日本でも似たような業務アプリは散々やってきたんで、「まぁ、ドメイン知識さえキャッチアップすれば余裕っしょ」とタカをくくってたんすよ。

ある日の設計レビュー。俺は、トレーダーの注文情報を管理する画面の設計について話してました。

「ここは注文テーブル(Order Table)があって、ステータス(Pending, Executed, Canceled)をカラムで持ちます。WPF側ではViewModelがこのステータスを監視して、変更があればUIに通知する、と。よくあるMVVMパターンですね」

自信満々にそう言うと、チームのシニアエンジニア(確かインド出身の、めちゃくちゃキレる人)が、静かにこう切り出しました。

「OK。じゃあ、**『3日前の15時30分13秒の時点』で、この注文が『Pending』だったことを、どうやって『100%確実に』**証明する?」

俺は一瞬、思考が停止しました。

「え? あ、いや、それはトランザクションログとか、DBのバックアップを……」

「違う」と、彼は首を横に振る。「ログは『いつDBが変更されたか』は分かるかもしれないけど、『なぜその状態になったのか』の『業務的な文脈』は分からない。それに、もし『Canceled』になった注文を、後で『やっぱりExecuteされてました』って手動でデータ修正したら、どうなる?」

「……あ」

「君の設計だと、『最後の状態(Latest State)』は分かる。でも、我々が欲しいのは**『全ての事実(All Facts)』**だ。この業界(金融)では、監査(Audit)が命だ。全ての操作は、後から完璧に再現(Replay)できなきゃいけない」

ぐうの音も出ませんでした。

俺が日本でやってきた「設計」は、DBのテーブル定義(スキーマ)を決めて、いかに効率よくCRUD(Create, Read, Update, Delete)を実装するか、という「状態管理」が中心でした。WPFアプリが扱うのは、常に「今、現在の、最新の状態」だったんすよ。

でも、彼らが求めていたのは、全く違う次元の話でした。

彼らは「状態」を保存しようとしてなかった。彼らが保存しようとしていたのは、**「起きたこと(イベント)」**そのものだったんです。

「注文が作成された」(イベント)

「注文が承認された」(イベント)

「注文がキャンセルされた」(イベント)

彼らの設計では、データベースには「現在の注文状態テーブル」なんてものは、プライマリ(主要)な存在じゃなかった。あったのは、「イベント」をひたすら時系列でブチ込んでいく、「イベントストア」と呼ばれる場所だけ。

「注文がキャンセルされてる」という「状態」が欲しい時は?

簡単だ、と彼は言いました。

「最初の『注文作成イベント』から順に『承認イベント』『キャンセルイベント』を全部再生(リプレイ)すれば、その時点での『状態』はいつでも復元できるだろ?」

……これが、俺と「イベントソーシング」の出会いでした。

正直、最初は「は? ナニソレ?」「全イベントを毎回リプレイとか、パフォーマンスどうすんの?」「ただのCRUDでいいじゃん、めんどくさ!」って思いましたよ(笑)

それに、これってゴリゴリのバックエンドの話で、WPF(クライアント)には関係ないじゃん、って。

でもね、この「イベントソーシング」っていう一見とっつきにくいアーキテクチャが、実は俺たちWPFエンジニアが主戦場とする**「複雑なドメイン」**(金融、製造、医療、エネルギー制御とか)で、めちゃくちゃ輝くシロモノだったんすよ。

そして何より、この「状態」じゃなくて「イベント」で物事を考えるっていう思想が、海外のエンジニアと対等に「設計」について語り合うための「共通言語」なんだってことに、後々気づかされるわけです。

クライアントサイド専門だった俺が、どうやってこの「イベントソーシング」っていう“怪物”と取っ組み合い、それがどう「海外で働くエンジニア」としての自分の価値を高めてくれたのか。

次の「承」からは、その具体的な話をシェアしていこうと思います。

ぶっちゃけ、WPFとイベントソーシングって、一見ミスマッチに見えて、実は「結果整合性」っていうヤバい落とし穴があったりして……。

まぁ、今日のところは「へー、海外の現場って、そんな『なぜ』を問われるんだ」くらいに思ってくれればOKです。

もしあなたが今、日本で「ただ動くもの」を作ってるだけなら、ちょっと立ち止まって「なんでこのデータ、この『状態』で持ってるんだっけ?」って考えてみると、新しい景色が見えるかもしれませんよ。

そのアーキテクチャ、「誰のため」にある?

イベントソーシング(ES)を学び始めて、まず分かったこと。

それは、ESが**「光り輝くシナリオ」**が、驚くほど俺のいる現場(金融トレーディングシステム)と、そして俺たちが作ってるWPFアプリのドメインと、ドンピシャで一致してたってことです。

ESが「神」になる瞬間

フック(前回の記事の導入文)にもあったけど、ESが真価を発揮する場所って、だいたい決まってるんすよ。

1. 複雑なドメイン(Complex Domains)

まさにウチの現場。金融、医療、製造ラインの制御、物流システム……こういう「業務ルールが超複雑」で、「なんでそうなった?」の『なぜ』が最重要な世界。

「起」で書いた「監査(Auditability)」がまさにそれ。

CRUD(Create, Read, Update, Delete)の「Update」って、実は一番タチが悪い。だって、「前の状態」を消し去る行為だから。UPDATE Orders SET Status = ‘Canceled’ WHERE OrderID = 123 ってやったら、その注文が昨日まで Pending だった事実は、DB上から消え失せる(ログを漁れば別だけど)。

でもESなら、「OrderCreated」「OrderApproved」「OrderCanceled」っていう「起きた事実」が全部、時系列で記録されてる。「歴史を改ざんしない」。これが、コンプライアンスが厳しい業界で、どれだけ価値を持つか。

2. 分散システム(Distributed Systems)

こっち(海外)のシステムって、マジでデカい。日本みたいに「全部俺たちのチームが作ったモノリシックなシステムです」なんてことは稀で、大体マイクロサービス化されてる。

「注文サービス」「顧客サービス」「決済サービス」「通知サービス」……全部バラバラ。

ここで「状態」を同期させようとすると、地獄が始まります。

「注文ステータスが更新されたら、決済サービスに通知して、顧客サービスのマスタも更新して……あ、決済サービスがコケたら、注文サービスのステータスもロールバックして……」

考えるだけで吐き気がする(笑)

でもESなら? 注文サービスは「OrderApproved(注文承認されたよ)」っていうイベントを発行(Publish)するだけ。あとは「決済サービス」とか「顧客サービス」が、そのイベントを勝手に購読(Subscribe)して、自分のとこのデータを更新すりゃいい。超シンプル。疎結合って、こういうことを言うんすよ。

3. デバッグと再現性(Debugging & Replay)

これが俺らエンジニアにとって、一番「得する情報」かもしれない。

「お客さんから『なんかデータが変』って連絡きたけど、再現しねぇ……」って経験、ありますよね?

ESなら、本番環境で起きたイベントのシーケンスを、そのまま開発環境に持ってきてリプレイ(再生)できる。「あの時、あの瞬間に、あの順番でイベントが起きたから、この状態(バグ)が生まれたんだ」っていうのが、100%再現できる。

これ、CRUDベースの「状態」だけ見てたら、絶対に不可能。

「やべぇ……イベントソーシング、最強じゃん」

「こんなスゲー設計思想も知らずに、俺はWPFでViewModelがどうとか、ちっちぇ話を……」

そう思ってました。そう、アイツに出会うまでは。

WPFエンジニアを襲う最大の“沼”:「結果整合性」

ESの仕組みをもうちょい詳しく言うと、大体「CQRS(Command Query Responsibility Segregation)」っていう、コマンド(書き込み)とクエリ(読み取り)を分離するパターンとセットで使われます。

  • コマンド側: 「注文をキャンセルしろ」みたいな命令が来たら、それを「OrderCanceled」イベントとしてイベントストアに書き込む。(超速い)
  • クエリ側: 書き込まれたイベントを非同期でキャッチして、UIが表示するための「状態」テーブル(リードモデルと呼ばれる)を更新する。

……ん?

「非同期で」?

そう、ここが最大の“沼”でした。

ESの設計は、「書き込み(イベントの保存)」と「読み取り(状態の更新)」の間に、タイムラグがあることを許容するんです。これを**「結果整合性(Eventual Consistency)」**って言います。

でも、考えてみてください。

俺たちが作ってるWPFアプリって、どういうもんですか?

特に俺がやってる金融トレーダー向けの画面。一瞬の遅れが数百万、数千万の損失に繋がる世界。

ユーザー(トレーダー)が「キャンセル」ボタンを**“カチッ”**と押す。

そのコンマ1秒後には、画面上のグリッドのステータスが「Canceled」に変わってないと、トレーダーはパニックになる。

「あれ? キャンセルできてない!?」ってもう一回ボタンを連打するかもしれない。

でも、裏側(バックエンド)で起きてることはこうです。

  1. (0.0秒)ユーザーがWPFのボタンをクリック。
  2. (0.1秒)ViewModelから「キャンセルコマンド」が飛ぶ。
  3. (0.3秒)バックエンドが「OrderCanceled」イベントをイベントストアに書き込む。(書き込み完了)
  4. (0.3秒)WPF(UI)側は「コマンド成功したっぽいな」と思う。
  5. (0.4秒)WPFのViewModelが「よし、最新の状態をDB(リードモデル)から再取得するぞ」とクエリを投げる。
  6. (0.5秒)バックエンドのクエリ側は、まだイベントストアの更新を検知できてない(非同期処理が追いついてない)。
  7. (0.6秒)クエリ側は、古い「Pending(処理中)」っていう**「状態」**をWPFに返す。
  8. (0.7秒)WPFのグリッドには「Pending」のまま表示される。
  9. (0.8秒)ユーザー:「ファッ!?(連打)」
  10. (1.5秒)バックエンドの非同期処理がやっと追いついて、リードモデルが「Canceled」に更新される。

……地獄です。

UIの「即時性」を求めるWPFと、バックエンドの「結果整合性」を前提とするES。

水と油。 まさに最悪の組み合わせ。

「いやいや、ES最強とか言ってたじゃん!」

「監査性? 再現性? そんなもんより、目の前のユーザーがキレてんだぞ!」

「やっぱCRUDでいいじゃん! 同期的にUpdateすりゃ、こんな問題起きねーだろ!」

チームのミーティングで、俺は(拙い英語で)そうまくし立てました。

「日本じゃ、こんなレスポンスの悪いUI、リリースしたら炎上モンですよ!」って。

すると、あのシニアエンジニアが、また静かに言ったんすよ。

「面白いことを言うな。君は『ユーザー体験(UX)』の話をしているのか? それとも『アーキテクチャの制約』の話をしているのか?」

「……え?」

「君は、WPFエンジニア(クライアントサイドのプロ)なんだろ? なら、その『非同期のギャップ』をユーザーに感じさせないようにするのが、君の『設計』じゃないのか?」

……あ、れ?

俺、また、やらかしてる?

俺は、バックエンドの設計(ES)が、クライアント(WPF)の足を引っ張ってるんだと、そう思い込んでました。

でも、違った。

彼は、「バックエンドは『結果整合性』という制約(ルール)の上で動く。そのルールの上で、最高のUX(即時性)を“演出しろ”」と。それがWPFエンジニア(俺)の仕事だ、と。

「状態」にしか興味がなかった俺が、「イベント」っていう新しい武器を手に入れた気になってたけど、結局は「UIが即座に変わる」っていう、目に見える「状態」にしか、頭が回ってなかったんすよ。

この「結果整合性」っていう“沼”を、どうやってWPF側で“ハック”していくか。

どうやってユーザーに「即座に処理された」と“錯覚”させるか。

こっち(海外)のエンジニアは、そのためのデザインパターンを、ちゃんと持ってたんです。

それが、俺のWPFエンジニアとしてのキャリアを、根本から変えることになる「あるパターン」との出会いでした。

サーバーを「待つ」な。UIを「騙せ」。

正直、最初は意味が分かりませんでした。

「だって、バックエンドが『OK』って言うまで、データ(状態)は変わってないんだぞ? クライアント(WPF)が勝手にUIを変えちゃったら、データ不整合じゃん」

「そんな“嘘”をユーザーに見せるなんて、金融システムじゃご法度だろ」

日本で叩き込まれた「サーバー(DB)絶対主義」とでも言うんでしょうか。サーバーこそが「真実」であり、UIはそれを忠実に映し出す「鏡」でなければならない。そう信じて疑ってませんでした。

でも、そのガチガチの固定観念は、こっち(海外)の現場では通用しなかった。

俺が「無理だ」「不整合だ」って悩んでる横で、彼ら(他のクライアントサイドエンジニア)は、とっくにその「ギャップ」を埋めるコードを実装してたんです。

そのコードを見た時、俺は「あ、こいつら“悪いこと”してる」って思いました(笑)

でも、違った。それが、彼らにとっての「設計」であり、俺が知らなかった「あるパターン」でした。

その名も、「楽観的更新(Optimistic UI / Optimistic Update)」

名前からして、なんかチャラい(笑)

「まぁ、だいたい成功するっしょ? 先にUI変えとこ!」っていう、超ポジティブ思考なパターンです。

「承」で書いた、あの地獄の「ボタン押してもUI変わらない問題」のフローを思い出してください。

<今までの俺(失敗フロー)>

  1. ユーザーが「キャンセル」ボタンをクリック。
  2. ViewModelがバックエンドに「コマンド」を送信。
  3. ViewModelは、バックエンドからの「成功応答」をひたすら待つ
  4. (その間、バックエンド側ではESの非同期処理が走ってる)
  5. ViewModelは「よし、コマンドが通ったっぽいから、最新の『状態』をリードモデル(DB)に問い合わせ(クエリ)るぞ」
  6. リードモデルは、まだ非同期処理が追いついてないので、古い「Pending(処理中)」を返す。
  7. UIのグリッドは「Pending」のまま。
  8. ユーザー、ブチギレ。

このフローの最大の問題点は、**「ViewModelが、バックエンド(リードモデル)の“お許し”が出るまで、UIを変えようとしないこと」**でした。

じゃあ、「楽観的更新」だと、どうなるか。

<ヤツら(海外のWPFエンジニア)のフロー>

  1. (0.0秒)ユーザーが「キャンセル」ボタンをクリック。
  2. (0.1秒)ViewModelが、ローカルで保持しているOrderオブジェクトのステータスを、問答無用でCanceledに変更する。
  3. (0.15秒)ViewModelがINotifyPropertyChangedをブッ放す。
  4. (0.2秒)WPFのUIグリッドが、バインディングの力で即座に「Canceled」に変わる。
  5. (0.21秒)ユーザーは「お、キャンセルできたな」と、次の作業に移る。(← UX大勝利!
  6. (0.3秒)ViewModelは、UIを変える処理と**“同時に”、バックエンドへ「キャンセルコマンド」を非同期で**送信する。(UIスレッドはブロックしない)

……分かりました?

「サーバーからの応答を待つ」んじゃなくて、**「先にUIを変えちゃう(騙す)」**んです。

そして、その“裏”で、ゆっくりとバックエンドに「コマンド」を投げてる。

「いやいや、待て」と。

「もし、バックエンド側で、そのコマンドが失敗(Reject)したらどうすんだよ!」と。

「例えば、もう既に約定(Execute)しちゃってて、キャンセル不可だった場合とか!」

そう、それこそが「楽観的更新」のキモでした。

<失敗した場合(1%のレアケース)の処理>

7. (0.5秒)バックエンドがコマンドを処理。「あ、これもう約定済みだわ。キャンセル無理」と判断。

8. (0.6秒)バックエンドがViewModelに「エラー(例: キャンセル不可でした)」を返す。

9. (0.7秒)ViewModelは、そのエラーをキャッチする。

10. (0.8秒)ViewModelが、さっき勝手にCanceledに変えたローカルのOrderオブジェクトのステータスを、元の状態(バックエンドから返された真実の状態、例: Executed)に“こっそり”戻す。

11. (0.9秒)同時に「キャンセルできませんでした(理由:約定済み)」というエラーメッセージを画面にポップアップさせる。

これが、彼らの言う「設計」でした。

99%の成功するケースでは、ユーザーに「即時性」という最高のUXを提供する。

残り1%の失敗するケースでは、UIを「真実の状態」にロールバック(差し戻)して、明確なエラー理由を提示する。

俺が「不整合だ」と騒いでいたのは、「失敗した場合」のことだけを考えて、ガチガチにガードを固めようとしていたから。

彼らは「99%のハッピーパス」を最優先し、そのためにUIが一時的に「楽観的な“嘘”」をつくことを許容していた。

この瞬間に、俺の中で「設計」という言葉の意味が、完全に「転」じました。

俺が日本でやってきたWPFの設計は、「バックエンドのDB(状態)を、いかに正確にUI(ViewModel)に同期させるか」という「データ同期」の設計でした。

でも、彼らがやっていたWPFの設計は、「バックエンドの制約(結果整合性)と、ユーザーの期待(即時性)の間に存在する“ギャップ”を、いかにエレガントに埋めるか」という**「体験(UX)の設計」**だったんです。

イベントソーシング(ES)は、「絶対的な真実(Source of Truth)」をバックエンドで保証してくれる、めちゃくちゃ強力な仕組み。

そして、俺たちクライアントサイド(WPF)エンジニアの仕事は、その「真実」が非同期で構築されるまでの“わずかな時間”を、ユーザーにストレスなく過ごしてもらうための「最高の“演出”」をすることだった。

ESは「監査性」や「再現性」っていう、ビジネス上のデカい価値を提供する。

WPF(楽観的更新)は「応答性」や「即時性」っていう、ユーザー体験上のデカい価値を提供する。

「結果整合性」は、俺にとって「遅くて使えないクソ仕様」だったのが、「バックエンドの真実を保証する仕組み」と「クライアントのUXを担保する仕組み」を分離・分担するための、見事な「アーキテクチャ上の境界線」に見えてきたんです。

もう、俺は「バックエンドのせいでUIのレスポンスが悪い」なんて文句を言うのをやめました。

代わりに、「OK、バックエンドの整合性モデルは理解した。じゃあこっち(WPF)は、楽観的更新でUXを担保するから、コマンド失敗時のエラーコードだけちゃんと定義してくれ」って、会話ができるようになった。

C#でViewModelのプロパティをいじってるだけなのは、日本にいた時と変わらない。

でも、その一行一行に込める「設計思想」が、180度変わった瞬間でした。

WPFエンジニアが「バックエンドを学ぶ」本当の意味

「転」で書いた「楽観的更新(Optimistic UI)」を実装した時、俺は一つの真理に気づいた(気がした)んすよ。

俺たちクライアントサイドエンジニアの本当の仕事は、「システムの“不完全さ”を隠すこと」だ、と。

「は? 不完全って何だよ?」って思います?

ここで言う「不完全さ」ってのは、「承」で俺があれだけ苦しめられた**「結果整合性(Eventual Consistency)」**みたいなヤツのことです。

昔ながらの単純なCRUDシステムなら、DBに書き込んだ瞬間に「真実」が確定した。

でも、今の(特に海外の)デカくて複雑なシステム――マイクロサービスとか、分散システムとか、俺がぶち当たったイベントソーシング(ES)とか――は、「真実(状態)」が確定するまでに、どうしても時間がかかる。

イベントストアに書き込まれて、それが非同期でリードモデル(UIが見に行くDB)に反映されて……この「コンマ数秒~数秒のラグ」は、システムがデカく、強くなる(=監査性や分散性を手に入れる)ための「トレードオフ」であり、「仕様」であり、「制約」なんです。

日本にいた頃の俺は、「WPFエンジニア」という箱の中にいました。

「俺の仕事はViewModelをキレイに保つこと。バックエンド? あぁ、API仕様書通りにJSON投げて、返ってきたJSONをバインドするだけっす」って。

バックエンドとの境界線は「API」っていう“壁”だと思ってた。

でも、違った。

こっち(海外)の現場で求められる「設計者」は、その“壁”の上に立って、両側を見渡せるヤツでした。

バックエンド(ES)側が、「監査性のために、どうしても“結果整合性”っていう制約が欲しい!」って言う。

ユーザー(トレーダー)側は、「俺のUXのために、“即時性”が絶対必要だ!」って叫んでる。

この二つの、一見「矛盾」する要求。

その“境界線”のど真ん中に立って、「OK、分かった」と。

「バックエンドは、ESで“ビジネス上の真実(監査性)”を守ってくれ。その代わり、コマンド失敗時のエラーコードだけは明確にしてくれ」

「そして、俺たちクライアントサイド(WPF)が、その“コンマ数秒のラグ”を『楽観的更新』っていう“演出”でハックして、ユーザーには『即時性』っていう“最高のUX”を届ける」

この**「アーキテクチャ全体の“制約”を理解した上で、自分の持ち場で(時には“嘘”をついてでも)価値を最大化する」**こと。

これが、俺があの日、シニアエンジニアに「それが君の『設計』じゃないのか?」と問われたことの、本当の意味だったんです。

これから海外を目指すエンジニアへ

もしアナタが今、日本でクライアントサイド(WPFでもWebフロントでも何でもいい)をやってて、「バックエンドのことはよく分からん」と思ってるなら、それは、めちゃくちゃ「損」してるかもしれません。

特に海外(というか、モダンなデカい現場)に出たら、「状態」じゃなく「イベント」で考えるESみたいな設計は、もはや「当たり前」の選択肢の一つ。

そして、「結果整合性」は、そこら中に転がってる「前提条件」です。

その時、「サーバーの応答が遅いから、UIが固まるのは仕方ない」なんて言ってるエンジニアは、残念ながら「コーダー」止まり。

「設計者」とは見なされない。

そうじゃなくて、

「あー、ここのバックエンド、CQRSでリードモデルの更新が非同期なのね。OK、じゃあWPF(クライアント)側は楽観的更新でUX担保しとくから、コマンドの冪等性(※)だけよろしく!」

(※冪等性=同じコマンドを何回実行しても、結果が同じになること。楽観的更新のリトライ処理とかで超重要)

…なんて会話が、サラッと(拙い英語でもいいから)できるエンジニア。

これこそが、「海外で働くITエンジニア」としての、アナタの市場価値をブチ上げてくれる「人生術」だと、俺は思うんすよ。

イベントソーシングを学べ、って言ってるんじゃない。

アナタが今やってる仕事(WPF)の“隣”で、バックエンドの連中が、何を「制約」として戦い、何を「価値」として生み出そうとしてるのか。

その**「アーキテクチャの全体像」**に興味を持て、ってことです。

WPFエンジニア(俺)が、イベントソーシング(バックエンド)を学んだ本当の「得」は、「ESが書けるようになった」ことじゃありません。

**「バックエンドの“痛み”や“制約”を理解し、彼らと“共通言語(アーキテクチャ)”で会話できるようになった」こと。

そして何より、「クライアントサイド(WPF)にしかできない“UXのハック”で、アーキテクチャ全体の価値を最大化できるようになった」**こと。

これに尽きます。

半年前まで、「状態」しか見てなかった俺。

今? 「イベント」の流れと、その“間”にある「時間差(ギャップ)」をどう「演出」するかに、ワクワクしてます。

C#とWPFをこねくり回してるのは相変わらず。

でも、見てる「視座」は、確実に数段上がった。そんな気がしてます。

さーて、今日も薄いコーヒー飲んで、いっちょ“演出”しにいきますか!

コメント

タイトルとURLをコピーしました