vueの設計ポエム

2025/02/24に公開

vueの設計について考ええることがあったので、ポエムを供養

結論

vueと思想が合わない
コンポーネントを中心においてでなにかを作れるという設計仮定が、自分にとっては難易度が高すぎる
railsとかgoとかをうまく扱えないのと一緒
複雑さをシンプルに落とし込めない

フロントエンドの設計の前提

  • フロントエンドのデータ構造の種類は2種類ある
    • エンティティと状態
      • エンティティ
        • ユーザー情報等意味のある粒度でまとめられたデータの塊
        • fe以外の文脈で語られるデータ
      • 表示状態
        • どういうエンティティのときは何が表示されるべきか
        • ユーザーがアクションした結果何が表示されているべきか
        • ページの状態を表す
    • 更新頻度や契機が異なるこの2つのデータ構造を状態と一括りに扱うと複雑さが増す
  • ユーザーアクションをもとにエンティティ、状態を加工する仕組みと、エンティティ、状態をもとに表示を変更する仕組みが必要
    • 後者をvue、reactなどがメインの機能として提供している
    • 前者をfwの利用者が作り込まないといけず、ここをいかにシンプルに作るのかが設計の肝

目指したい世界

  • 可能な限りvueとロジックを分離し、自動テストで動作の担保できる範囲を最大化したい
    • e2eのような壊れやすテストを自動化したくない
    • デザインと振る舞いを可能な限り分けたい
  • 手続き方のようなフローではなく、イベントドリブンで各処理が走るようにしたい
    • 構造上、非同期処理が発生しやすい
    • 複雑な非同期処理をそれを無理やり手続き的なフローに落とし込むと複雑さが増す
    • 値の更新通知や、ユーザーアクション通知など、イベントフックで扱ったほうが自然

feの構造要素(縦方向の責務分割)

feを構成する要素は大枠以下に分けれられる

  • routeに紐づくvueコンポーネント(メインコンポーネント)
  • メインコンポーネントを構成するvueコンポーネント(サブコンポーネント)
  • デバイス情報等アプリケーション全体に影響する情報保持するpiniaのstore(アプリケーションストア)
  • エンティティを管理するpiniaのstore(データストア)
  • ユーザーの入力情報の管理、バリデーションをするpiniaのstore(フォームストア)
  • コンポーネントの状態を管理するpiniaのstore(ステータスストア)
  • メインコンポーネントから呼ばれ必要なデータサービスを呼び出し等、初期化に関わる処理をするservice(アプリケーションサービス)
  • apiを叩いてデータストアにデータを詰める、フォームストアのデータをデータストア、apiに保存するservice(データストアサービス)
  • データストアの更新やユーザーアクションをもとにステータスストアを変更するservice(コンポーネントサービス)
  • データストアの肥大化を防ぐために意味のある粒度でデータをまとめたmodel(データモデル)
  • apiとの通信でエラーが起こったときの情報を扱うmodel(エラーモデル)

依存関係

依存関係は以下のようになっている

深い構造になることを考慮して基本的にはpropを利用せず、storeを経由して表示を更新する
可能な限りvueの機能を使わないことで、自動テストが機能しやすいようになる

各要素の詳細

メインコンポーネント

urlに紐づくコンポーネント
下記責務を果たす

  • アプリケーションサービスを利用し、各種ストアの初期化や認証認可等グローバルな処理をキック
  • ユーザーのアクションをフックに、コンポーネントサービスをキック
  • ユーザーの入力をフォームストアに反映
  • locationの変更やpiniaの仕組みで監視できないイベントフックなどvueの世界でないとできない処理

サブコンポーネント

メインコンポーネントのパーツとなるコンポーネント

下記責務を果たす

  • ユーザーのアクションをフックに、コンポーネントサービスをキック
  • ユーザーの入力をフォームストアに反映
  • piniaの仕組みで監視できないイベントフックなどvueの世界でないとできない処理

アプリケーションストア

グローバルなエンティティを管理する

例えば

  • デバイス情報
  • 認証認可情報
  • ユーザー情報など全ページで利用するようなデータ

データストア

エンティティを管理する

更新の契機はは以下の2パターン

  • 初期化等ストレージに入ってるエンティティの情報をapi経由で取得したいとき
  • フォームストアに入ってる内容をストレージに反映したいとき

上記を考慮し、データストアは下記データも保持する

  • 更新中
    • api経由での操作は必ず非同期なため、更新中で参照してはいけないのかを参照側が知る必要がある
  • エラーの有無
    • api経由での操作を必ず成功するわけではない
    • 失敗したときにその理由を参照側が知る必要がある

フォームストア

ユーザーが入力した情報を保持する

またユーザーの入力に対するバリデーションのロジックも責務として持つ
バリエーションの結果どういう文言を表示するのかは表示の責務はメインコンポーネント、サブコンポーネントにあるのでメインコンポーネント、サブコンポーネントで持つ
あくまで入力の不正の判断のみ
複合バリデーションなどもあるので、フォームストアストア自体が入れ子になることもある

ステータスストア

データストアやユーザーアクション応じて変化すべき表示の状態を管理する

理想を言えば、データストアの更新をこのストアが監視し、自身の状態をしたい
しかし、piniaでの実装は難易度が高いので、コンポーネントサービスで責務を負う

アプリケーションサービス

そもそもページを初期表示するために必要な処理に対する責務を持つ

例えば

  • データサービスを利用しデータの初期化
  • 認証認可のロジック

データサービス

api経由でデータサービスを更新する責務を持つ

ユーザーデータを表現するためには2つのapiを叩いた結果をマッシュアップしないと表現できない等、apiとデータの構造的なミスマッチの解消に関して責務を持つ

apiのレスポンスだとintだけど、feだとstringで扱い等データの表示的なミスマッチはデータストアやメインコンポーネント、サブコンポーネントが責務を持つ

またすでに更新済みのデータを再度取得しないなどのキャッシュ的な部分の責務も持つ

コンポーネントサービス

ユーザーアクションを契機として、データサービス経由でデータストアの更新やステータスストア更新を行う

ストレージの更新を契機として、データサービス経由でのデータストアとフォームストアの同期の責務も持つ
またデータストアの更新によってなにか表示状態を更新したいときは、ステータスストアを更新する責務を持つ

ただpiniaの仕組み上、このクラスの責務はwatch等vueの機能を利用しないと果たせないので、このクラスに関してはvueの機能を利用する

Discussion