📁

フロントエンドのディレクトリ設計思想

2023/12/04に公開

はじめに

フロントエンドのディレクトリ構成、世の中に色んな「推し」が有って悩みますよね。
例えば、、、
https://zenn.dev/knowledgework/articles/99f8047555f700
https://zenn.dev/sakito/articles/af87061a5016e6

さらに最近は、App Directoryの登場や、それに合わせたNext.js公式の「推し」構成がドキュメント化されたりと、さらに色々なパターンが出てきています。
https://nextjs.org/docs/app/building-your-application/routing/colocation

本記事の趣旨

本記事では、具体的な構成そのものではなく、
様々ある構成を横串で見通して整理できる設計思想を紹介します。

新しい推し構成の紹介ではなく、構成を考えたり決めたりするときに役立つ抽象的・汎用的な指針を提供できればと考えています。

基本となる考え

分割の方向

一般的に、アーキテクチャにおける分割には2つの方向が有ります。

(出典も良書なのでリンクを貼っておきます: https://www.amazon.co.jp/dp/4873119820)

これはディレクトリにおいても同じだと思っていて、筆者は分かりやすさのために

  • 技術によるディレクトリ分割をLayer型
  • ドメインによるディレクトリ分割をFeature型

と呼んでいます。

Layer型の例としては、Atomic DesignやMVC、クリーンアーキテクチャ(の図の構成)などが挙げられます。
また、components、utils、hooksなどの分け方は「レイヤー」状では無いですが技術単位なのでコチラです。

Feature型の例としては、Features DirectoryやApp Directoryなど、異なる技術要素をColocateするようなものが挙げられます。
GraphQLのFragment Colocationなども分類するならコチラです。

最近近しい内容の記事(というかFeature型の推奨記事)がいくつか話題に上がっていました。非常に参考になる内容なので「推し」として紹介します。
https://zenn.dev/pandanoir/articles/d74d317f2b3caf
https://zenn.dev/misuken/articles/bdd33790ed4cd0
(世間的には、package by featureという呼び方が主流になっていきそうな雰囲気を感じています。)

Layer型とFeature型のPros/Cons

さて、2つの分割方向にはPros/Consが有ります。(どんな設計思想にも有りますね)

  • Layer型
    • Pros: 階層構造を作ることで、共通化によって起こる依存の向きを整理できる
    • Cons: (Atomic Designのように)階層の定義が曖昧になったり、必要以上の階層ができて依存が複雑になりやすい
  • Feature型
    • Pros: ドメインごとに分けられると疎結合・高凝集になり、影響範囲が限定的になることで開発しやすくなる
    • Cons: ドメインの境界を明確にできず責務が曖昧になったり、ドメイン間で処理の重複が発生しやすい
      • (あるいは設計を間違てドメイン間で依存を発生させてしまうとメリットが失われてしまう)
      • ※これは(ディレクトリではないですが)マイクロサービスの世間での実例を見てもらうとイメージしやすいかと

どのように設計するか

二者択一ではなく組み合わせ

巷ではLayer型よりもFeature型が良い、という主旨の話を見聞きしますが、
先に述べたように、Pros/Consが有るので、これらは組み合わせるべきものだと筆者は考えています。

ここで一度、世の中の組織構造について見てみると、コンウェイの法則から、システムのあるべき姿が見えるかもしれません。

世の中の組織構造の多くは以下のいずれかの形になっています。

アーキテクチャの分割方向の話とかなり類似した構造に見えますね。

そして、おそらく一番多いのは以下のように組み合わせた形です。

そして

共通の事業目的を持つすべての大組織は、最後にはハイブリッド組織形態に落ち着くことになる
ー アンドリュー・S・グローブ. 『ハイアウトプットマネジメント』.日経BP.2017

という元インテルCEOの言葉を是とするなら、コンウェイの法則に従って、システムもハイブリッドに落ち着くはずです。

フロントエンドディレクトリにおける組み合わせ方

ここまでで、2つの分割方向があり、それらを組み合わせるのが良さそうだという話を書きました。
では、ディレクトリの構成を決めるにあたっては、どう考えれば良いでしょうか。

まず、基本的にはFeature型で考える、というのが凝集度高くできて開発しやすいと思います。
一方で、Feature型では共通化を扱いづらいので、共通化したい部分はLayer型で整理するのが良さそうです。

共通化したい箇所をLayer型にする理由は、共通化をすると同じ処理を複数箇所から参照することになるので依存関係が発生するためです。
依存関係で問題になるのは依存の方向であり、方向を整えるために階層状、あるいは技術単位で整理するのが有効ということです。

さらに、各FeatureやLayerのディレクトリの中で、処理を整理する必要がある場合はさらにFeatureとLayerの組み合わせを作って入れ子構造にするとより扱いやすくなると思います。
例えば以下のようなパターンです。

  • componentsの中でもCSSやtestをColocateする
  • ルーティングやfeaturesの中にもcomponentsやutilsディレクトリを切る

ディレクトリ構成の例

実はこの思想を自分でまとめた後に気づいたのですが、ディレクトリの話をする時に避けては通れないbulletproof-reactは、まさにこの思想に則ったファイル構成でした。
bulletproof-reactはfeaturesディレクトリが有名なので、ついFeature型のみで構成されているかと思っていましたが、よく見るとLayer型のディレクトリも組み合わせた設計になっています。
https://github.com/alan2207/bulletproof-react/blob/master/docs/project-structure.md

また、ChatGPTにも、この思想に沿ってサンプルを出力してもらいましたので参考までに置いておきます。

src/
│
├── components/          # 共通コンポーネント (Presentation Layer)
│   ├── UI/              # 純粋なUIコンポーネント (Button, Modal)
│   └── Layout/          # レイアウトコンポーネント (Header, Footer)
│
├── pages/               # 各ページ (Feature Element)
│   ├── Home/            # ホームページ (Feature Element)
│   │   ├── components/  # ホームページ固有のコンポーネント (Presentation Layer)
│   │   ├── hooks/       # ホームページのカスタムフック (Business Logic Layer)
│   │   └── api/         # ホームページ関連API呼び出し (Data Access Layer)
│   │
│   └── Profile/         # プロフィールページ (Feature Element)
│       ├── components/  # プロフィールページ固有のコンポーネント (Presentation Layer)
│       ├── hooks/       # プロフィールのカスタムフック (Business Logic Layer)
│       └── api/         # プロフィール関連API呼び出し (Data Access Layer)
│
├── hooks/               # グローバルなカスタムフック (Business Logic Layer)
│
├── utils/               # ユーティリティ関数 (Business Logic Layer)
│
├── api/                 # グローバルなAPI通信ロジック (Data Access Layer)
│
├── store/               # 状態管理 (Business Logic Layer)
│
└── styles/              # 共通スタイル (Presentation Layer)

(おまけ)ファイルベースルーティングはFeature型と相性が良い

冒頭で紹介した記事では以下のように書かれていて、筆者も賛同するところです。

機能の粒度や分割点をエンジニアだけで考えてしまうと、このディレクトリ構成をただ踏襲しても破綻してしまうと思います。

https://zenn.dev/sakito/articles/af87061a5016e6#気をつけたいポイント

その点、Routingに合わせた機能分割であれば、URLや画面単位での分割になるので、エンジニア以外とも共通認識を持つことが容易です。
これまた冒頭で紹介したApp Directoryの思想は、Routingに合わせて色んなファイルをColocationしましょうというものですが、そういった観点から見ると非常に理に適っていると筆者は思います。
https://nextjs.org/docs/app/building-your-application/routing/colocation

おわりに

今回は抽象的な設計思想の話でしたが、今後、具体的なディレクトリの話やデザインシステムの話なども書きたいと思っています。
ご興味ある方や話をしてみたい方は、XのフォローやDMお待ちしております!

https://twitter.com/_teppeita

※なお、本記事は少し前に弊社イベントのLTで発表した以下の内容の増強版です。

Discussion