/note/tech

フロントエンドのモデルに境界を引く

要約:

■ 1. 開発環境と日々の課題

  • 対象システムの特徴:
    • BEから受け取った状態に応じて、UIの表示や操作が変わる
    • BEは状態を返し、表示や操作の可否はFE側で判定することも多い
    • 稼働中のシステムの保守開発
  • 組織体制:
    • FEとBEでロールが分かれ、それぞれ開発している
    • 複数人のエンジニアが同時並行でシステムに変更を入れる
    • システムを使って業務を遂行する担当者が別途存在する
    • 業務に詳しい人とは日常的には話さないが、要件定義などの場で質問や確認はする
  • 問題提起:
    • 仕様を変える時、どこを変えればよいか、誰と合意が必要かにすぐ答えられるか
  • 仕様変更コストの重さ:
    • 変更すべき箇所の洗い出しに時間がかかる
    • 認識齟齬から対応漏れが起きる
    • 画面要件がビジネス要求に整合しているかの確認が難しい
    • BEとFEの実装が整合していない
    • 誰がどの部分の仕様を決めているのかわかりにくい
  • 課題の根本:
    • 判定が画面ごとに散らばり、どこに何があるか追いきれない
    • その判定がデザインの都合か業務の決まりかがわからない
    • エンジニア内外のコミュニケーションコストが高い
  • 生成AIによる課題の深刻化:
    • 実装のアウトプットが加速し、既存の挙動や要件を深く理解する余裕なく実装が出てくる
    • 特にコードレビューで顕著になる
    • システムの挙動と業務仕様の共通理解をよりスムーズに作りたい

■ 2. 共通理解が作りにくい原因

  • 仕様の関心のレイヤー混在:
    • 業務: ユーザーに何をさせてよいか
    • 体験: 何をどう認知させるか
    • 表現: どこでどう見せるか
  • 層の混在による認知負荷:
    • 関係者との会話でもコードレビューでも、何を判断すべきかがわからなくなる
  • 紐解きの起点:
    • 混ざった関心を紐解くため、まず土台にある「業務」の層からドメインモデルを把握する

■ 3. ドメインモデルとは

  • 定義:
    • 特定の文脈における業務知識を、意味のある構造として表したもの
    • 業務概念、規則、振る舞いを問題解決に使える形で表したもの
    • 実装そのものではないが、コードで表現することは強く推奨される
  • 育てるもの:
    • 完全な業務知識やモデルがいきなり出てくるわけではない
    • 対話やドメインモデルのテストを書くことで育てていく
  • 位置づけ:
    • システムが表現する対象
    • システムの正しさを判断する基準
    • システムをつくることを通じて更新されうるもの

■ 4. ドメインモデリングの壁と現実的な入口

  • ドメインモデリングの価値:
    • 業務を理解し、システム内の整合も取れる取り組み
  • 必要な体力:
    • 業務担当者、PdM、BE、デザイナー、FE、QAなど関係者を広く巻き込む必要がある
    • ヒアリング、用語の統一、実装とテスト、矛盾や不足の確認など、時間を取って会話を重ねる必要がある
    • 手ぶらではいきなり動きにくい
  • 現実的な入口の模索:
    • フロントエンドで業務知識の理解と整理を深められないか
    • フロントエンドからドメインモデリングのきっかけを作れないか

■ 5. フロントエンドのモデル

  • 定義:
    • 画面の表示制御に混ざっている判断や知識を、概念、規則、振る舞いとして表したモデル
  • 含むもの(何を決めているか):
    • 見せ方のための決め事
    • 業務ルールの適用
  • 含まないもの(どう描くか):
    • 表示/非表示、活性/非活性、色や文言の出し分けといった表示制御
  • 例: ECサイトの注文確認画面:
    • 購入者向けの配送段階を6段階から4段階に丸める処理は見せ方の決め事
    • 注文のキャンセル可否の判定は業務ルール
  • 単体での価値:
    • 画面に依存しない判断なので、画面やUI部品を横断して利用できる

■ 6. フロントエンドモデルからドメインモデルの仮説へ

  • 仮説の立て方:
    • フロントエンドモデルから規則を抽出し、システム全体のドメインモデルの仮説を立てる
    • 仕様レビューや問い合わせなど既存の場で確認する
    • 複数画面に出現する規則や食い違いはドメインエキスパートと会話する
  • 一から掘り起こす方式との比較:
    • 場を設けて一から掘り起こせば未知の概念や例外まで広く拾えるが、関係者を集めて時間を多く取る必要がある
    • エンジニアが仮説を持ち込めば、要件定義や開発中の仕様確認の場で簡潔にモデルについて会話できる
  • コンパクトな開始:
    • 仮説を持ち込むことで、コンパクトにモデリングを始められる
    • 確認の中で見えた食い違いが、最終的に本格的なモデリングの場につながる

■ 7. 実装フロー1: 判断の洗い出し

  • 対象と範囲:
    • 既存の表示制御の中にある判断を、1画面または1機能ずつ洗い出す
  • 注目する箇所:
    • 条件付き描画やラベル変換などの表示の分岐
    • 検証、送信可否、ステップ進行などの入力の制御
  • 推奨手法:
    • if文など判定系の処理をすべて関数に切り出し、関数名をつける

■ 8. 実装フロー2: 判断が答える問いの言語化

  • 各分岐が何を決めているかを言語化する
  • 主語と述語がモデルの設計図になる
  • 言語化の手順例:
    • 元の関数名: キャンセルボタンを活性にするか
    • UI部品の言葉を外す: キャンセルできるか
    • 主語を判断の対象にする: この注文はキャンセルできるか

■ 9. 実装フロー3: モデルの実装

  • APIレスポンス型:
    • 通信回数の削減や他システムとの共通化など、ドメインモデルと無関係の都合で構造が決まることが多い
    • 例では注文状態(6種)と、注文状態とは別に進む支払い状態を返す
    • 支払い状態はキャンセル後も返金中がありうる
  • 概念型への変換(腐敗防止層):
    • APIレスポンス型を、ルールが必要とする概念の型(OrderStatus、Order)に変換する
    • ルールが使わない支払い状態、お届け先、商品、金額は持ち込まない
    • APIのenumが増えたら変換箇所でコンパイルエラーになる
    • 状態の分け方がAPIと食い違ったら、変換箇所で対応づけを書く
  • 規則と振る舞いの定義:
    • 判定結果と理由を概念の型(CancelDecision、CancelReason)として表す
    • 規則は「出荷作業(ピッキング)の開始前ならキャンセルできる」
    • decideCancelが状態ごとに可否と理由(出荷作業開始済み、キャンセル済み)を返す
    • satisfies neverにより、statusが増えたらコンパイルエラーで気付ける
  • 責務の分離:
    • ContainerはAPI呼び出し、toOrderによる概念への変換、decideCancelやtoDisplayPhaseによる判定を担う
    • Containerは概念の型(CancelDecision、DisplayPhase)だけをPresentationalに渡す
    • Presentationalはstatusの値を知らず、受け取った判定を表示や操作の制御に変換する
  • 判定結果によるUI制御:
    • 判定結果に応じてキャンセルボタンを非活性にする
    • キャンセル不可の理由ごとに文言を出し分ける
    • 理由と文言の対応はsatisfies Recordで網羅性を保証する

■ 10. 実装フロー4: ルールの所有者による分類

  • 分類の目的:
    • 仮説を立てるため、このルールは誰が決めているかでフロントエンドモデルを分類する
  • 所有するルール:
    • 画面の見せ方の都合で決められるもの
    • 例: 配送段階を6段階から4段階に丸める
  • 所有しないルール:
    • 普遍的な業務ルールに従っているもの
    • 例: 注文をキャンセルできるか
  • 所有者を確認する問い:
    • なぜそうなっているのかの答えは画面の外にあるか
    • 変えるとき誰の合意が必要か
    • 無視したら何が壊れるか
  • キャンセル可否への適用:
    • ピッキング以降は棚に戻すのに労力がかかるという理由がある
    • 変更には物流や配送コストの管理者の合意が必要
    • 無視すると出荷作業が壊れるため、FEは所有しない
  • 配送状態の段階表示への適用:
    • 購入者がわかりやすいという理由がある
    • 変更にはデザイナーの合意が必要
    • 無視するとユーザーのわかりやすさが壊れるため、FEが所有する
  • ドメインモデル仮説との関係:
    • FEが所有しないルールを持つフロントエンドモデルが、ドメインモデルの仮説になる
    • 所有しないルールを仮説として、既存の場やドメインエキスパートとの会話で確認する

■ 11. 現在地

  • 段階的な切り出し:
    • システム全体への導入はまだで、段階的にモデルを抽出し実装している
  • 仕様変更時のリファクタから着手:
    • 既存画面の挙動や業務ルールを理解する見返りが大きい
    • ドメインエキスパートとコミュニケーションを取れる場が多い
  • 画面単位の切り出しと重複の一時的許容:
    • 画面横断の概念をいきなり見つけるのは難しく、浸透もしにくい
    • 共通化を急がず、モデルを横断比較して重複を発見し、共通化するプロセスにする
  • 所有の区別は開発フロー未組み込み:
    • 所有の仕分けは明確な開発手順として定めていない
    • 開発フローを重くしすぎず、まずはモデル化を浸透させたい
  • それでも区別はつけやすくなった:
    • ドメインエキスパート、デザイナー、BEなどFE以外の関係者とシステムの規則について会話し確認するきっかけが増えている
  • 人によるレビューの改善:
    • 関心事が絞られ、所有の有無を問わずどの層の変更かがすぐわかる
    • 特にテストコードが読みやすくなる
    • 業務のどういう状態のケースを実装しているかが見えやすくなる
  • AIによる実装とレビューの改善:
    • コンテキストが限定され、提案や指摘の精度が体感で上がった

■ 12. 最終的に目指す状態

  • 業務の決まりや仕組みを変える時、直す場所と確認相手がわかる
  • 画面ごとのモデルを徐々に共通化して重複を排除し、規則の変更が1箇所で済む
  • フロントエンドのモデル実装から、関心を持つべきドメインモデルを読み取れる

■ 13. 課題1: モデル化対象の抽出自動化

  • モデル化したいのは表示の形式ではなく意味:
    • 文言、ロケール、装飾だけの変換は表示制御の近くに実装する(例: PENDING_REVIEWを「審査待ち」に変換)
    • 画面固有の見せ方は表示制御の近くに実装する(例: バッジ色、説明、ボタン状態)
    • クライアントの概念への意味変換はフロントエンドモデルに実装する(例: PENDING_REVIEWとQUEUEDを「処理待ち」に統合)
    • サーバーの都合を隠すクライアント独自の状態体系はフロントエンドモデルに実装する(例: Editable、AwaitingResult、Completed)
  • 自動化の難しさ:
    • 意味の抽出は自動判定しにくく、開発者のスキルに依存している
    • 表示の形式と表示に依存しない意味の境界を機械的に判定できない
  • AIへの期待:
    • 分類にはシステム外の合意が不要なので、AIで自動化したい
    • モデルの賢さに任せても自動化できる水準に到達するかもしれない

■ 14. 課題2: AIによるドメインモデルのテスト生成

  • 期待:
    • モデルのテストを書く中で、業務との不整合や検討不足に気付ける
  • 現実:
    • テストはAIに生成させ、人間が評価するのが主流になっている
    • AIは業務上ありえないケースも、説明付きでもっともらしく書く
    • 他システムやドメインエキスパートへの確認が省かれやすい
  • 対策:
    • もっともらしいテストを鵜呑みにせず、知らない知識は確認に回す

■ 15. 課題3: ドメインモデル全容の記録場所

  • FE実装の限界:
    • FEは規則を使うが、状態変更はデータ永続化を担うBEに任せることが多い
    • そのためFEの実装だけでは不完全なドメインモデルしか表現できない
    • FEのコードでは仮説は立てられても、確認済みの全体像は残せない
  • 残したい情報:
    • FEがどの規則をどの程度の厳密さで使っているかまで残したい
  • 記録先候補と欠点:
    • ドキュメントは陳腐化しやすい
    • 結合テストはドメインモデルの構造を把握しにくい
    • BEのコードは本来ベストだが、FEエンジニアから参照しにくく実装に手が出せない

■ 16. まとめ

  • UIに混ざった判断を、フロントエンドのモデルとして切り出す
  • FEが所有しないルールを、仮説としてBEやドメインエキスパートと確認する
  • 始めるなら、確認や会話の機会が多い仕様変更のタイミングがよい
  • 関心事が絞られ、人とAIのレビュー負荷が下がる
  • 課題は抽出の自動化、AI生成テストの検証、ドメインモデルの記録先

■ 17. 境界を引く理由

  • AI時代の変更ループの高速化:
    • AIでコードを変えるのは容易になり、変えて反応を見てまた変えるループが高速化している
  • 守るべきもの:
    • 業務の決まりのように合意形成が必要な部分を、無自覚に変えないようにしたい
    • 業務の決まりを変える場合は、安全に素早く変えたい
  • 結論:
    • フロントエンドのモデルに境界を引くのは、システムを高速に変えながら守るべきルールを見失わないため

■ 18. FAQ

  • BEにドメインモデルがあれば不要か:
    • UXのためにユーザーができることを表現する都合上、FEにもドメインモデルの規則を利用し実装する部分が生じる
    • 全体像はBEのドメインモデルだが、FEがどこを完全または部分的に利用しているかを明確にできる
    • それによりBEのドメインモデルの変化をFEに反映しやすくなる
    • FEやデザインの判断だけでドメインモデルを変えてはいけないことにも気付ける
    • FEが所有しないルールであることにFEが気づける状態を作ることに価値がある
  • 実行可能な操作をAPIから受け取りBEに判断させればよいか:
    • 返し方次第であり、フラグの羅列は避けたい
    • OpenAPIのoneOfなどで、ありえない状態を表現できない形で返せるならありうる
    • 実態はis系フラグの羅列が多く、業務上ありえない組み合わせも表現できてしまうため、結局FEが判断を負うケースが多い
    • 状態ごとに実行可能な操作一覧を返す設計もあるが、一般的ではない
  • どのプロダクトでも取り入れるべきか:
    • FEから手軽に始められるとはいえ、コストがかからないわけではない
    • 0→1のスタートアップやPoCなどドメインが急激に変わる領域では、想定外の変更を防ぐ恩恵が得られず不要かもしれない
  • 業務判定の共通関数化との違い:
    • 関数に切り出す点は同じ
    • 概念型から別の概念型への変換として実装することで、ロジックがどの業務概念に属し、どんな値しか取りえず、どんな操作だけが許されるかを表現できる

■ 19. 参考文献

  • DDD on the frontend(Talysson Oliveira Cassiano、DDD Europe)
  • オブジェクト指向UIデザイン──使いやすいソフトウェアの原理(ソシオメディア株式会社、上野学、藤井幸多)
  • ドメイン駆動設計をはじめよう(Vlad Khononov著、増田亨、綿引琢磨訳)
  • 関数型ドメインモデリング(Scott Wlaschin著、猪股健太郎訳)