💡

ぼくのかんがえたフロントエンドアーキテクチャ

2021/03/31に公開
1

はじめに

普段開発しているフロントエンドのアーキテクチャがそこそこ良くできていると得意顔になれる気がしたので、思想を書いてみようと思います
※ その他、プロジェクトの構成については Yota Hada さんの記事をご参照ください

https://zenn.dev/nus3/articles/257d724e24a39b756b5a

記事のターゲット

  • フロントエンドのアーキテクチャ構成に悩んでいる人
  • ちょっとイケイケ・ゴリゴリのディレクトリ構成で開発をしてみたい人
  • バックエンドからフロントエンドに転向してチーム立ち上げをするので困った人(私)

個人的な雑記を含んでいますので、読みにくさはあしからず…

構築背景

バックエンドで何度かプロジェクトの生き死にを経験したエンジニアがフロントエンドチームを立ち上げてイチから構成を考えることになった
バックエンドで馴染みのあったクリーンアーキテクチャの構成をWebアプリケーションのフロントサイドで実現しつつ、コンポーネント開発にAtomicDesignを取り入れたい気持ちが高まり、本構成にたどり着いた

TL;DR

概念図

srcディレクトリ構成

src/
  ├ components/
  │  ├ atoms
  │  ├ molecules
  │  ├ organisms
  │  └ templates 
  ├ containers/
  ├ core/
  │  ├ adapters/
  │  ├ domains/
  │  │  ├ models/
  │  │  └ validators/
  │  ├ infrastructures/
  │  ├ usecases/
  │  │  └ repositories/
  │  └ view-models/
  ├ libs/
  ├ pages/
  ├ states/
  ├ styles/
  ├ types/
  └ utilities/

うまくクリーンアーキテクチャの層をcoreに逃がすようにしてみました

なんだか良くわからんと思うので解説していきます

前提

クリーンアーキテクチャ

問題解決領域であるエンタープライズロジックを中核に据えて、変更の多いUI/DBと言った技術層の交換可能性を上げるアーキテクチャのことです
よく下記のような図を見ると思いますがレイヤー構成をオニオンにしない場合はちょっと参考にしづらいですが、中核に向かって依存しておりフレームワークやドライバーに依存する層をなくすことで交換可能を実現します

クリーンアーキテクチャとは適用するシステムの特性や問題解決領域でレイヤーの考え方は異なる実装を与えるものであり、抽象度の高い設計思想なので、「フロントエンドではどう考えるか」を深堀ります

フロントエンドにおけるシステム特性

UIを中心としたシステム・ライブラリにより実装されていることが多く、バックエンドのシステムに比べて複雑な決済や認証と言った手続き的なユースケースが少ないです
また、インタラクティブなステート管理を必要とするためアトミックな手続きよりオブジェクトを中心としたステート管理を主とする構成になりやすいのも特徴です
また、UIに引きづられて破壊的な変更が行われる層が厚く、コアになるドメイン層が薄いと考えられます

フロントエンドにおける問題解決領域

フロントエンドは直接DBを操作したりトランザクションを意識する手続きが少ないので、関心事の大半が操作対象の適切な状態表示操作の理解補助など直感的・直接的な操作を支援を目的とした問題を扱います
(そのためライブラリに強く依存した仕組みになることが多いが、問題に合わせた小さなライブラリが散見される)

AtomicDesign

言わずと知れたUIデザイン手法で、これがないとコンポーネント開発の沼に足を取られてしまいます

https://design.dena.com/design/atomic-design-を分かったつもりになる/

各社フロントエンドエンジニアの取り組みにより以下の記事のような部分的な採用も多いAtomicDesignですが、今回は全面的な採用を行いました

https://devblog.thebase.in/entry/2019/11/27/110000

本件の考えるクリーンアーキテクチャ

前提を踏まえて考えるべきポイントは以下です

  • 各レイヤーの役割を明確にする
  • AtomicDesignとクリーンアーキテクチャの棲み分けを考える
  • ライブラリ(Frameworks)群とAPI(Drivers)群の依存関係を少なくする

各レイヤーの役割を明確にする

改めてクリーンアーキテクチャの図が登場しますが、注目すべきは項目名です

各層における責務を自分なりに整理すると以下のようになりました

Layer directory desc
Enterprise Business Rules domains 表示・操作に関わるコアドメインのロジック、データを表現する
Application Business Rules usecases ドメインロジックのアプリケーション固有ユースケースを表現する
Interface Adapters adapters バリデーション/リトライ制御/model変換など業務ロジックが介在しない処理を表現する
Frameworks & Drivers infrastructures フレームワークやAPIの薄いラッパーを実装する

一見すると何の話だかわからないですが、上記が実装する上でのガイドラインになります

AtomicDesignとクリーンアーキテクチャの棲み分けを考える

ようやく概念図の解像度が上がっていきます

  • AtomicDesignのPageをエントリーポイントとして、複数のユースケースを利用してそのページのUIを表現する
  • Pageはテンプレートにデータを投げるためのPresenter的な立ち位置であるが、実務はUsecaseが担う形式にする
  • それぞれAtomicDesign層とCoreロジック層を仲介する立ち位置としてcontainerを配置する
    • かなりcontainerが太る構成になっていますが、しょうがない気持ち…
    • DIなどの責務もcontainerが担っています

なかなかに複雑な見た目をしていますが、AtomicDesignの部分をViewと位置づければ話は早いです

ライブラリ(Frameworks)群とAPI(Drivers)群の依存関係を少なくする

ライブラリの依存関係

UIライブラリについては依存からは切っても切り離せない関係にある(React / Next / tailwindcss)
この点はAtomicDesignの責任領域内なので許容する

ライブラリについてはlibsで薄くラップしたモジュール群を作り、簡易的な日付操作などのユーティリティメソッドについてはutilitiesを横断的関心事のために用意しています
ライブラリのインターフェースなどは用意してもよいが、使用ライブラリが変わって入れ替える際にDIをするほどのことでもないのでラップしてライブラリ置換しやすいようにしておく

APIの依存関係

ブラウザのAPIおよび管理画面使用のAPIについてはかならずDriver層であるinfrastructuresを実装として
Interface Adapter層であるadaptersで薄くラップして、使用するApplication Business Rules層のusecasesではinterfaceを参照するようにする
(Mockingやテストの際にはDriver層にソレ用の実装を用意する)

実際にディレクトリを構成する

ここまで決まってきた概念もディレクトリで表現できなければ開発しにくい状態なので整備します
結果的に以下のような構成になりました

src/
  ├ components/
  │  ├ atoms
  │  ├ molecules
  │  ├ organisms
  │  └ templates 
  ├ containers/
  ├ core/
  │  ├ adapters/
  │  ├ domains/
  │  │  ├ models/
  │  │  └ validators/
  │  ├ infrastructures/
  │  ├ usecases/
  │  │  └ repositories/
  │  └ view-models/
  ├ libs/
  ├ pages/
  ├ states/
  ├ styles/
  ├ types/
  └ utilities/

srcディレクトリ直下の責務が分かれた部分だけピックアップします

  • components: AtomicDesignのコンポーネントを表現する
  • containers: AtomicDesignと問題解決領域の仲介層
  • core: クリーンアーキテクチャで表現される層
  • libs: ライブラリのラップモジュール群
  • pages: Next.jsのページ(AtomicDesignのPageにあたる)
  • states: Reducerなどのステート管理層
  • styles: グローバルスタイル
  • utilities: 自前開発のユーティリティモジュール群

ポイントはcoreという名前でクリーンアーキテクチャの層を分離したところです
当初フラットなディレクトリ構成にしようとしたところ、視認性が低くなるという意見が多くなりcoreディレクトリにまとめるという形で落ち着きました

メリット

AtomicDesign×クリーンアーキテクチャの構成で受けられた恩恵は大きいです

  • コンポーネント開発がしやすい
  • APIのモッキングが楽
  • 作業分割がしやすい
  • 粒度の細かいコードにすることで見積もりなどが楽

書ききれないくらいメリットを感じていますが、表面的には上記のような効果が合った気がします

デメリット

ドヤ顔でAtomicDesign×クリーンアーキテクチャの構成を書いていますが、この構成を声高らかに推奨するわけではありません
よく語られているデメリットも含めて以下がリスクだと考えています

  • jsバンドルサイズの肥大化
  • コーディング量の増大
  • 参入時の学習コストの増大

スマホアプリでは致命傷となりえるバンドルサイズがありますが、現状のプロダクトはPCの仕様を前提としているため大きな問題ではないです
コーディング量の増大については基本的にscaffoldingでその手間を解消する方向で進んでいますが、そもそもAtomicDesign×クリーンアーキテクチャを知らないエンジニアに対して辛さのある構成だなぁと感じています

結び

現在プロダクトは絶賛成長中でページ数もコンポーネント数も大きくなっていませんが、コーディングで迷うことも少なくなる上にバックエンドのAPIをモッキングすることが容易になったので開発しやすい印象です
クリーンアーキテクチャにすることでデメリットもありますが、現在の事業ドメインや現在のチーム状況では影響になるほどの問題ではなかったので総合的に見てこの構成にしてよかったと感じています
(とは言え負債化する可能性は大いにあるので、その後日談はおいおい書きたいと思います)

Discussion

k-satok-sato

ありがとうございます。
とても参考になりました。