その設計、「なぜ」に答えられる?
海外の現場に放り込まれて、最初にぶち当たった壁。それは「英語」でも「コーディング規約」でもなく、**「『なぜ』の深掘りレベル」**でした。
俺がアサインされたのは、とある金融系のトレーディングシステム。もちろんクライアントは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」に変わってないと、トレーダーはパニックになる。
「あれ? キャンセルできてない!?」ってもう一回ボタンを連打するかもしれない。
でも、裏側(バックエンド)で起きてることはこうです。
- (0.0秒)ユーザーがWPFのボタンをクリック。
- (0.1秒)ViewModelから「キャンセルコマンド」が飛ぶ。
- (0.3秒)バックエンドが「OrderCanceled」イベントをイベントストアに書き込む。(書き込み完了)
- (0.3秒)WPF(UI)側は「コマンド成功したっぽいな」と思う。
- (0.4秒)WPFのViewModelが「よし、最新の状態をDB(リードモデル)から再取得するぞ」とクエリを投げる。
- (0.5秒)バックエンドのクエリ側は、まだイベントストアの更新を検知できてない(非同期処理が追いついてない)。
- (0.6秒)クエリ側は、古い「Pending(処理中)」っていう**「状態」**をWPFに返す。
- (0.7秒)WPFのグリッドには「Pending」のまま表示される。
- (0.8秒)ユーザー:「ファッ!?(連打)」
- (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変わらない問題」のフローを思い出してください。
<今までの俺(失敗フロー)>
- ユーザーが「キャンセル」ボタンをクリック。
- ViewModelがバックエンドに「コマンド」を送信。
- ViewModelは、バックエンドからの「成功応答」をひたすら待つ。
- (その間、バックエンド側ではESの非同期処理が走ってる)
- ViewModelは「よし、コマンドが通ったっぽいから、最新の『状態』をリードモデル(DB)に問い合わせ(クエリ)るぞ」
- リードモデルは、まだ非同期処理が追いついてないので、古い「Pending(処理中)」を返す。
- UIのグリッドは「Pending」のまま。
- ユーザー、ブチギレ。
このフローの最大の問題点は、**「ViewModelが、バックエンド(リードモデル)の“お許し”が出るまで、UIを変えようとしないこと」**でした。
じゃあ、「楽観的更新」だと、どうなるか。
<ヤツら(海外のWPFエンジニア)のフロー>
- (0.0秒)ユーザーが「キャンセル」ボタンをクリック。
- (0.1秒)ViewModelが、ローカルで保持している
Orderオブジェクトのステータスを、問答無用でCanceledに変更する。 - (0.15秒)ViewModelが
INotifyPropertyChangedをブッ放す。 - (0.2秒)WPFのUIグリッドが、バインディングの力で即座に「Canceled」に変わる。
- (0.21秒)ユーザーは「お、キャンセルできたな」と、次の作業に移る。(← UX大勝利!)
- (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をこねくり回してるのは相変わらず。
でも、見てる「視座」は、確実に数段上がった。そんな気がしてます。
さーて、今日も薄いコーヒー飲んで、いっちょ“演出”しにいきますか!

コメント