💻

状態を直接編集する台帳UIをやめたら、Event Sourcing的な発想にたどり着いた

に公開

はじめに

こんにちは。Dress Code 株式会社でプロダクトエンジニアをしている西銘です。

DRESS CODEにはデバイス台帳画面があります。数あるプロダクトの中でも初期から作られ、使われてきただけに業務知識は詰まっている一方で、機能を追加するたびに別の潜在不具合が見つかる状態でした。
直しても別の場所で似た問題が出るので、開発側も利用側も消耗しやすくなっていました。

最初は「使いづらい UI をどう改善するか」という話だと思っていました。ですが掘っていくと、見た目や操作感よりも、現在の状態を直接編集する前提のデータ設計が実際の業務の流れとズレていることが問題でした。

この記事では、デバイス台帳のリプレイスを考える中で、状態中心の設計からライフサイクルイベント中心の設計に発想を切り替えた話を書きます。
実装の詳細というより、どういう課題に対してどんな整理にたどり着いたか、という設計メモです。

この記事のスコープ

本記事で扱っているのは、主に UI/UX とドメイン整理における「イベントを軸にする」発想です。イベントストアへの永続化、リプレイ、スナップショットなど、バックエンドを Event Sourcing としてどう実装するかは別の話として、また別の記事にしようと思っています。


課題

Excel の列をそのまま持ち込んだような編集体験

当時の台帳画面は、ステータスと、そのステータスに関わる情報項目をかなり自由に編集できる構成でした。

たとえば、次のような項目が同じ画面で並んでいます。

  • 現在のステータス
  • 使用者
  • 貸与開始日
  • 貸与終了日
  • 修理開始日
  • 修理完了日

項目は一見そろっているのですが、業務上はそれぞれが独立して存在しているわけではありません。実際には「貸与を開始した」「返却された」「修理に出した」のような出来事が起き、その結果として状態が変わります。

しかし UI は出来事ではなく現在値の集合を直接編集する形だったため、Excel やスプレッドシートに項目を足していく運用とほとんど変わらない状態になっていました。

以下は、状態中心で管理していたときのイメージです。

状態中心の設計イメージ

矛盾した現在状態を簡単に作れてしまう

この構造だと、項目単位では入力できてしまうのに、業務としては意味の通らない状態が作れてしまいます。実際に困っていたのは、次のようなケースです。

  1. 使用者の貸与開始日を 4/1 に設定し、ステータスを「使用中」にする
  2. 修理開始日を 4/8 に設定する
  3. 本来は修理開始日が入力されるとステータスを「修理中」にしたいが、貸与終了日が未入力のため、ステータスは「使用中」のまま残る

このとき何がつらいかというと、単にステータス表示がズレるだけではありません。

  • 意図した状態遷移になっていない
  • 貸与期間と修理期間が重複している
  • 後から見た人が現在の状況を解釈しづらい

つまり、現在状態を直接編集できること自体が、不整合の入口になっていました。

なぜバリデーション強化だけでは足りなかったか

もちろん最初に思いつくのは、バリデーションを増やすことです。たとえば「修理開始日を入れるなら貸与終了日も必須にする」といった制約は追加できます。

ただ、業務を深掘りしていくと、実際に扱いたい状態は「在庫」「使用中」「修理中」だけではありませんでした。「設置中」「故障中」「再整備中」のような中間状態もあり、それぞれに必要な関連情報や整合性チェックの条件が異なります。

そのたびにバリデーションを足していくと、画面や保存処理のロジックはどんどん複雑になります。どの条件がどの業務ルールを守るためのものなのかも追いにくくなり、状態が増えるほど保守しづらい構造になっていました。

しかも、問題は制約の数だけではありませんでした。入力させたい単位が「項目」なのに、実際の業務の単位は「イベント」だったからです。

利用者がやりたいのは、

  • 貸与を開始する
  • 回収する
  • 修理に出す
  • 在庫に戻す

といったアクションです。にもかかわらず UI では、そのアクションを複数項目への編集に分解してしまっていました。すると、どれかの項目だけ更新された中途半端な状態を許してしまいます。

必要だったのは制約を増やすこと以上に、何を 1 回の操作単位として扱うかを見直すことでした。


解決策

状態ではなく、状態遷移を伴うイベントを見る

そこで、デバイス管理で本当に記録したいものは何かを考え直しました。

注目したのは、状態そのものではなく状態遷移を伴うイベントです。

デバイスのライフサイクルを見直すと、実際に起きているのは次のようなイベントです。

イベント 入力する情報の例 期待される結果
入庫 入庫日 ステータスが「在庫」になる
貸与開始 使用者、貸与開始日 ステータスが「使用中」になる
回収 回収日(保管開始日)、回収時メモ ステータスが「在庫」になる
修理開始 修理開始日、修理理由 ステータスが「修理中」になる
修理完了 修理完了日(保管開始日)、回収時メモ ステータスが「在庫」になる
処分 処分日、処分理由 ステータスが「処分済み」になる

以下は、イベント中心に整理したときのイメージです。

イベント中心の設計イメージ

この整理をすると、入力 UI も自然に変わります。たとえば「修理」というイベントを実行するときは、修理開始日だけでなく、必要に応じて貸与・保管の終了に関する情報も同じ操作の中でまとめて扱えます。

これなら、遷移元と遷移先にまたがる情報を 1 アクションで完結できるので、状態の食い違いが起きにくくなります。

イベントの積み重ねから現在状態を導出する

ここで行き着いたのが、イベントの積み重ねから現在状態を投影するという考え方でした。

厳密な用語の定義には幅がありますが、少なくとも自分たちがたどり着いたのは、次のような発想です。

  • まず記録すべきなのは「何が起きたか」
  • 現在のステータスは、その結果として導出されるもの
  • UI も現在値を直接いじるのではなく、イベントを起こす形に寄せる

これによって、以前のように「修理開始日だけ入ったが、ステータスは使用中のまま」といった状態はそもそも作りにくくなります。

もう一つ良かったのは、履歴の意味が読みやすくなることでした。現在値だけを見ていると「いまどうなっているか」は分かっても、「どういう経緯でそこに至ったか」は見えにくくなります。イベントを基準にすると、デバイスのライフサイクルを追いやすくなります。

Event Sourcing 的な考え方は UI/UX にも効いた

世の中のデバイス管理は、Excel やスプレッドシートで運用されているケースがまだまだ多いと思います。その運用をそのままプロダクトに持ち込むと、列の集合として現在状態を管理する発想になりやすいです。

実際、自分たちも最初はその延長線上にいました。ですが、業務の実態に合わせて見直していくと、必要だったのは「列をきれいに並べること」ではなく、「出来事を自然に入力できること」でした。

結果として、Event Sourcing的な考え方はデータ設計だけでなく、UI/UX の設計にも有効でした。

  • 利用者は「何が起きたか」をベースに操作できる
  • システムはその結果として現在状態を導出できる
  • 開発者は不整合を許しにくい構造を持てる

という形で、業務の自然な単位とシステムの単位を揃えやすくなったからです。


向き不向き(所感)

向きにくいケース

シンプルな CRUD で済むユースケースには、イベントを軸にした設計はオーバーになりやすいです。たとえば簡単な CMS なら、下書き作成・公開・アーカイブ程度のイベントしか起きず、複雑な状態管理も不要です。

また、イベントが高頻度に発生し、投影結果を常に最新に保つ必要があるドメインでは、書き込みと読み取りのコストが効いてくる、という話もあります(参考: Why I Quit Event Sourcing After 3 Years of Event-Driven Architecture)。履歴のメリットより、性能や運用の負荷のほうが先に立つケースでは、やりすぎだと感じることもあると思います。

向きにくい 理由の例
状態が少なく、遷移が単純 バリデーション数本で足りる
履歴より最新値だけが重要 イベント蓄積のコストに見合わない
更新が非常に高頻度 投影の鮮度・性能がボトルネックになりやすい

向きそうなケース

一方で、次のようなドメインでは今回の整理が効きやすいと感じています。

  • 直列ではない状態遷移がある(戻り・分岐・並行した中間状態がある)
  • 他ドメインや他画面から「いまの状態」を参照される
  • 「いまどうなっているか」だけでなく「どういう経緯か」が重要

ユーザーのアカウントやプランのライフサイクルも同型です。DRESS CODE では、デバイス台帳に加え、拠点管理・備品管理・車両管理のような「モノのライフサイクル」を扱う領域では、同じ発想が使えそうだと見ています。

向きそう 理由の例
状態遷移に戻りや分岐がある 現在値の直接編集で不整合が起きやすい
他機能から状態を参照する 「いま何の状態か」の定義を揃えたい
監査・説明のために履歴が要る イベント単位の記録が自然

おわりに

Event Sourcing を勉強していると、法律や会計のように出来事を基準に業務が組み立てられている例に触れることがあります。入金や出金、契約締結や改定のように、身近な業務もイベントとして捉えると自然に見えるものが多いと感じました。

今回のデバイス台帳も同じで、最初からEvent Sourcingを目指していたわけではありません。むしろ UI/UX や日々の運用で感じていた違和感を丁寧に掘っていった結果、状態を直接編集するよりも、イベントを記録して現在状態を導出するほうが自然だと分かりました。

向き不向きはドメイン次第ですが、今回のようにイベントを軸に見直したほうが自然なケースは、思ったより多いかもしれません。

DRESS CODE TECH BLOG

Discussion