2024/02/13

「フロントエンドのモデル駆動設計」の見解(場外編)


背景

2023年12月、さくらのテックランチというイベントで「フロントエンドの複雑さに立ち向かう 〜 DDD と Clean Architecture を携えて 〜」という発表をした。ありがたいことに多くの人に見ていただき、(観測範囲では)好意的な意見をいただいた。その影響は大きく「現場から学ぶモデル駆動の設計」という勉強会を主催されている方の目にとまり、座談会メンバーとしてオファーをいただき2024年2月1日に参加してきた。

がっつりカンペを用意したのだが、時間の都合や流れ的に話せなかったことがあるので、4年ぶりにブログを更新しようと思う。

座談会は「バックエンドエンジニアがフロントエンド開発するときの悩み」というテーマで、以下のようなトピックがあった。

  • フロントエンドの学び方
  • バックエンドの知識をフロントエンドに応用できるか
  • UIコンポーネントの設計方法
  • ステートレスとステートフル
  • テストの手法
  • 質疑応答

フロントエンドの学び方

JavaScriptフレームワークを使って小さなアプリケーションを作ってみる

昨今のフロントエンド開発では、JavaScriptのフレームワーク/ライブラリは必須と言っても過言ではない。そのため、React、Vue、Svelte、Angularなど、何でも良いので使って小さなアプリケーションを作って感覚を使うのをオススメしたい。

CodeSandbox のようなオンラインサービスを使うと開発環境構築の手間が省ける。もちろん create-react-app や create-vue などの Scaffolding Tool でも良い。

Web標準を学ぶ

IEサポートが不要になってからブラウザの機能実装が加速した。とくに2023年はCSSが大きく進化した。(CSS Nesting、:has()、Container Query、Subgridなどが主要ブラウザで利用可能になった)

いまでは jQuery や各種ライブラリを使わなくても標準の Web API や HTML/CSS だけでリッチなUIが開発できるようになった。そのため、遠回りをしないためにもWeb標準を学ぶことをオススメしたい。

とくに「◯◯ 実装方法」などでググると、古い情報がいっぱい出てくるので「◯◯というライブラリを使えば簡単に実装できます」系の記事は疑ったほうが良い。

キラキラ記事に惑わされない

テックブログやQiita、Zennなどでは最新ライブラリを詰め込んだキラキラ記事をよく見かける。

たしかに新しい技術はこれまでの課題を解決してくれるので便利なのだが、そういう記事はショーケースと認識し、プロダクトには十分考えてから導入した方が良い。ライブラリ間の依存が強すぎてバージョンアップで詰むのはよくある話なので。

バックエンドの知識をフロントエンドに応用できるか

応用できるが、フレームワークの制約がつきまとう

フロントエンド開発では、JavaScriptフレームワークの制約がついてまわる。そのため、よくあるデザインパターンやソフトウェアアーキテクチャが適用できない場合がある。

大きなフレームワークやライブラリを導入すると、同時に「制約」も大きくなるため、バックエンドの知識をフルに使いたいなら最小限にとどめたほうが良い。バックエンドの知識に拘らず「制約」のメリットを享受したい場合は使ってもよいだろう。

オブジェクト指向プログラミング

「データとふるまいをまとめる」という広義のオブジェクト指向であればフロントエンドでも活かせる。ただし、クラスベースのOOPの場合、前述のとおりJavaScriptフレームワークとの相性が悪い。(メンバ変数が変わっても変更検知できない)

たとえば、Presentation層 / Application層 / Infrastructure層 / Domain層という4Tierアーキテクチャを使う場合、Presentation層以外はクラスベースで実装が可能だ。ただし、フロントエンドにはクラスベースを是とする宗派、関数型やそれ以外を是とする宗派がいるので、チーム内で議論は必要不可欠になる。

レイヤードアーキテクチャ

MVCやMVVMなどはよく目にするが、そのまま実装するとドメインの知識がPresentation層に入り込んで、SmartUI(利口なUI)になりがち。

フロントエンドの複雑さに立ち向かう」では、Presentation / Application / Infrastructure / Domain というレイヤーに加え、Presentation層内をMVCで分けている。そうすることで、UIロジックはPresentationのModel層へ、ビジネスロジックはDomain層へという棲み分けをしている。

ただし、小規模なWebアプリケーションの場合は、レイヤードアーキテクチャはコスパが悪いためSmartUIが良いかもしれない。

DDD(ドメイン駆動設計)

エリック・エヴァンスのドメイン駆動設計」でも「すべての要素を使おうとしなくて良い」と書かれていたとおり、ドメインモデルを効率的に表現できる要素だけを使うのが良い。

戦略的DDD(どう設計するか)の部分は十分使える。

戦術的DDD(どう実装するか)の部分は開発するアプリケーションの規模や環境によって変わるので「場合による」としかいえない。フロントエンドでも正確にドメインモデルを表現しようとするとコード量が増え、ユーザー体験が損なわれる場合があるので注意が必要だ。

フロントエンドの最大の敵はネットワークなので、バンドルファイルの小ささは正義。


UIコンポーネントの設計方法

過去に「加速するコンポーネント設計」や「コンポーネント指向時代のmargin戦略」というスライドを公開しているので参照いただきたい。

Atomic Design

「理想を詰め込んだ分類手法」なので、実際に採用する場合は独自ルールを設ける必要がある。ただ考え方としては有用で、コンポーネント開発時にどのレベルで分割するかなどの参考になる。

UIコンポーネントとビジネスロジック

規模やチーム文化にもよるが、基本的にはUIコンポーネントとビジネスロジックは分離すべき。

フロントエンドにはUIロジックとビジネスロジックがあり、前者はViewModel、後者はDomain層に配置するのだが、その判断は難しい。

フロントエンドでモデルを持つメリット

フロントエンドの最大の敵はネットワーク。なので検証のために毎回APIリクエストを投げていてはユーザー体験を損ねてしまう。その解決策として、フロントエンドにもバックエンドと同様にモデルを持つと良い。

ただし、フロントエンドのビジネスロジックは表示系や検証系に関するモノがメインで、バックエンドと性質が異なるので必ずしも同じモデルを持つ必要はない。

フロントエンドのテスト手法

テストの書き方

コンポーネント設計がうまくできていない場合、ユニットテストに大量のモックが必要になる。できる限り状態を持たないコンポーネントを作成する or 状態を内部に閉じ込めることでテストは書きやすくなる。

ただし、UIのテストの多くはコストが高いため効果的な場所だけやったり、APIをモックして結合テストをするのが無難そう。E2Eテストは、ユーザーの操作をシミュレートできるがしんどいのでよく考えてから導入すること。

フロントエンドにテストが少ない問題

歴史的、文化的な側面が大きいと感じている。

その昔、UIに関するテストは非常に書きづらかった。いまでは新しいツールも登場し、格段に書きやすくなったものの、過去のマインドを引きずってテストを書かない人が多いイメージがある(偏見)。

質疑応答

フロントエンドのドメインモデルにはどういうロジックが入っているのかあまりイメージがつかないので可能な範囲で具体例が聞きたいです

たとえば、さくらのクラウドの場合、スイッチやルーターに紐づくプライベートIPアドレス、グローバルIPアドレスを管理する必要がある。IPアドレスのCIDR表記(192.168.0.0/24)もあり、IPアドレスで有効範囲のチェックをするロジックなどが実装されている


バリデーションを、フロント側で行うべきか、バックエンド側で行うべきか悩みます。(中略)それぞれで実装した場合、共通化する術があるか、二重で実装するか・しないか。良い落とし所があれば知りたいです。

フロントエンド、バックエンドともにバリデーションは必要。

フロントエンドの一番の敵はネットワークなので、なるべくAPI通信を最小限にしたい。そのためにもフロントエンドである程度完結している方が望ましい。


フロントエンドだとどういうフォルダ分けが良さそうでしょうか?Atomic Designは一時期流行ったとは思いますが、現在はあまり使われていないと思っております。

フロントエンドにおけるディレクトリ構成には、UIコンポーネントとレイヤーの2種類がある。最終的には「場合による」としか言えないが、自分が担当しているプロジェクトでは以下のような構成にしている。

  • UIコンポーネント(ライブラリ化している)
    • Components … 機能単位で分割
      • Button … 独立したコンポーネント
        • Button.tsx
      • Menu … 親子関係がある場合はPrefixに親の名前をつける
        • Menu.tsx
        • MenuList.tsx
        • MenuItem.tsx
        • MenuContext.ts
        • useMenuXxxx.ts
    • Hooks … 共通で使えるカスタムフック
      • useXxxx

  • サービスのリポジトリ
    • Presentation
      • Components … 全ページで使える共通コンポーネント
      • Pages
        • {Page Name}
          • Components … ページ専用コンポーネント
          • ViewModel … UIロジック
          • ViewController
          • index.tsx
    • Application
      • UseCases
      • Services … Application用の軽量サービス
    • Infrastructure
      • Repositories
    • Domain
      • Entities
      • ValueObjects
      • Services … Domain用の軽量サービス

REST APIでやりとりするデータ型とvalidation以外に、バックとフロントで共有したいロジックってどんなものがありますか?

基本的には挙げていただいた2つだと思う。それ以外パッと思いつかなかった。

DDDの文脈では、Web APIで境界を引く(境界づけられたコンテキスト)なら、フロントエンドとバックエンドで必ずしも共有する必要はない。しかし、境界を引かないのであれば、同一コンテキストなので「重複した概念」をまとめたほうが良い。

なので「場合による」という曖昧な回答になってしまう。


backend, frontendの言語統一 → 逆にこれのデメリットってどんなものがあるのでしょうか?

バックエンドでも JavaScript/TypeScript を使いたい場合は、おそらくNode.jsで実行することになる。Node.jsはシングルスレッドのため、用途によっては適さないかもしれない。

バックエンド(Node.js)に関する知識や経験がないため、あまり思いつかなかった。



以上

written by @bc_rikko

0 件のコメント :

コメントを投稿