
はじめに
こんにちは。nonoyamalfoyです。
弊社ではwebをnext.js, アプリをreact-nativeで開発しています。
フロントエンドアーキテクチャので最近流行りのfeaturesアーキテクチャについての見解を述べようと思います。
featuresとは
超簡単にいうと、機能ごとにUIやロジックを隠蔽していくディレクトリ構造。
features
├── message(メッセージ機能)
│ ├── hooks/
│ ├── types/
│ └── ui
│ ├── origin/
│ └── pages/
├── profile/(プロフィール機能)
└── signin/(サインイン機能)
React公式の見解
React は、ファイルをフォルダーに入れる方法について意見を持っていませんが、以下の2つを例として上げています。
- 機能またはルートによるグループ化
「機能」の定義は普遍的なものではなく、粒度を選択するのはユーザー次第とのこと。
featuresなどはこちらに分類されます。
message/
index.ts
messageApi.ts
message.tsx
message.css
message.story.tsx
message.test.ts
profile/
index.ts
profileApi.ts
profile.tsx
profile.css
profile.story.tsx
profile.test.ts
- 同様のファイルをグループ化
Atomicデザインなどはこちらに分類される。
api/
messageApi.ts
profileApi.ts
components/
profile.tsx
profile.css
profile.story.tsx
profile.test.ts
message.tsx
message.css
message.story.tsx
message.test.ts
複業クラウドの例
初期の頃の構成
リリース初期は同様のファイルをグループ化する形をとっていました。
- componentsやhooksの肥大化が発生。
- componentsディレクトリに切り分けたコンポーネントを入れていくので、抽象的な名前をつけてしまうと名前が被ることがあり後々後悔することになる。
- 該当のコンポーネントがどこにあるのか、どこで使われているのかがわかりにくい
src
├── components(ドメインロジックのない汎用的なコンポーネント)
│ ├── atoms/(最小の粒度、TextAtom, IconAtomなど)
│ ├── molecules/(atomの集合体、基本的に複数箇所で利用される)
│ └── organisms/(atom,moleculesの集合体、複数箇所で利用されない場合もある)
├── windows
│ ├── components/(ドメインロジックのあるコンポーネント、ページの肥大化、コンポーネントの共通化を目的に切り分けたものを格納)
│ ├── member/
│ ├── public/
│ └── route/(ルーティング)
├── lib/
├── models/
├── modules/(サーバーとの接続)
└── hooks/
2023年1月現在
features内でhooksやuiを分けることで機能ごとの依存を防ぐようにしました。
src
├── features
│ ├── message(メッセージ機能)
│ │ ├── hooks/
│ │ ├── types/
│ │ └── ui
│ │ ├── origin/
│ │ └── pages/
│ ├── profile/(プロフィール機能)
│ └── signin/(サインイン機能)
├── ui
│ ├── origin
│ └── homeland
├── adapter/(サーバーとの接続,apiを叩いてデータを返す)
└── lib/(汎用的なロジック)
featuresアーキテクチャの特徴
- 機能単位ごとに疎結合にできる(これが一番大きいメリットだと感じています。)
- 機能単位で改善することが多いので不要なデグレを避けることができる
- 機能削除する際にディレクトリごと消せばいい
- featuresディレクトリを見ると、プロダクトにどんな機能が存在するのかわかる
- 機能を意識した実装や見積もりを行うことができる
- 機能によってはUIから作成したい場合、ロジックから作成したい場合、型から実装したい場合などいろいろなケースがあるかと思います。
- コンポーネントの命名に迷うことが減る。
- ドメインを意識した開発を行える。
下記でもう少し詳しく解説します。
機能単位ごとに疎結合にできる
プロダクト開発は機能単位で改善、削除することが多いです。
そのため、他のfeatures同士で依存しないよう意識すれば編集を入れても他の機能への影響が出にくくなります。
また、機能削除する際にディレクトリごと消せばいいので楽になります。
プロダクトにどんな機能が存在するのかすぐにわかる
以下のようにfeaturesを見ればmessege, profile, signinの機能があることがすぐにわかります
features
├── message(メッセージ機能)
│ ├── hooks/
│ ├── types/
│ └── ui
│ ├── origin/
│ └── pages/
├── profile/(プロフィール機能)
└── signin/(サインイン機能)
ドメインを意識した開発がしやすい
featuresアーキテクチャでは機能ごとにUIとロジックを隠蔽できるため、ロジックから作成する。型から作成する。といった機能に焦点を当てた開発がとてもしやすいメリットがあります。
コンポーネントの命名に迷うことが減る。
機能ごとにファイルを隠蔽できるため、別の機能とコンポーネント名がかぶっても問題がない。
例えば機能やページごとに異なる「Header」が存在した場合、「~Header」みたいにする必要はなく「Header」という名前で実装できる。
Tips
feature同士の依存を強制的に防ぐために、eslintを利用できます。
rules: {
'import/no-restricted-paths': [
'error',
{
zones: [
//profile feature以外でtargetのpathをインポートできないように
{
from: './src/features/!(profile)/**/*', // 対象の依存パス
target: './src/features/profile', // 禁止するパス
message: 'features同士で依存することはできません',// エラーメッセージ
},
//message feature以外でtargetのpathをインポートできないように
{
from: './src/features/!(message)/**/*', // 対象の依存パス
target: './src/features/message', // 禁止するパス
message: 'features同士で依存することはできません', // エラーメッセージ
},
],
},
],
},
最後に
featuresアーキテクチャのメリットをいくつか紹介しましたが、機能の少ないプロダクトやリリース初期のプロダクトは「同様のファイルをグループ化」するほうがいいと思っています。
機能が少ない場合はむしろfeaturesの肥大化が起きたり、どの粒度の機能で切るのか悩む時間が発生します。
機能が増えてディレクトリが肥大化してきたタイミングでfeaturesアーキテクチャの導入を検討するのがいいと思います。
「弊社プロダクトの設計をもっと知りたい!」と思った方は以下を合わせてお読みいただけると幸いです。
複業クラウドのフロントエンド設計
参考文献
Screaming Architecture - Evolution of a React folder structure
Reactベストプラクティスの宝庫!「bulletproof-react」が勉強になりすぎる件
Share this post