package by feature のススメ
最近、package by feature というディレクトリ構成が様々なところで出てきています[1]。例をあげると、これらで見れます。
- next.js の app router
- bulletproof-react
- 他人がはやく読めるコードを書くために
しかし、package by feature について簡潔にまとまった資料がまだないため、人に紹介するときに不便です。そこで今回は package by feature とは何なのか、何が良いのかについてまとめます。
package by feature とは?
package by feature とは、ディレクトリを feature 単位でまとめる手法のことです。
# package by feature
src/
└ feature/
└ recordList/ (記録を表示するための機能群)
┝ DailyAverage.tsx
┝ Graph.tsx
┝ Table.tsx
┝ Page.tsx
┝ fetchRecord.ts
└ useRecords.ts
このように、特定の機能に関するコンポーネントや API 呼び出し、カスタムフックなどが1つのディレクトリにまとめられているのが特徴です。
next.js の場合は src/feature/
≒ app/
です。
対比のために、よく見かけるレイヤーに基づいた構成(package by layer)を示します。
# package by layer
src/
┝ page/
│ └ recordList/
│ └ Page.tsx
┝ components/
│ └ recordList/
│ ┝ DailyAverage.tsx
│ ┝ Graph.tsx
│ └ Table.tsx
┝ api/
│ └ recordList/
│ └ fetchRecord.ts
└ hook/
└ recordList/
└ useRecords.ts
ちなみに、nuxt.js はこの package by layer になっています。
package by feature の利点
package by feature は機能単位でファイルがまとまっています。これによって 特定の機能についてのコードを簡単に探して読めます。 つまり、コードを読むときに読むべき箇所の見当がつけやすいです。
たとえば、上で示したコードベースのグラフ表示にバグがあるとします。このバグを調べるとき、いくつか知りたいことがあります。
- ユーザーの記録データはどの API から取得しているのか?
- 取得したデータの加工はどこで行っているのか?
- グラフ表示しているのはどこなのか?
これらを調べるとき、 package by feature はほぼ src/feature/recordList だけ眺めれば十分です。 上の3つの疑問に対して、どのファイルを見れば良いか大体見当がつきますよね?
それに対して package by layer では src 配下のファイル全部からいちいちファイルを探す必要があります。 つまり、src/api や src/hook、src/components などからグラフ表示に関するファイルを探して、見つけ終えてから中身を読んでいかなければなりません。これが本当に面倒です。本当に。
このように、 読まなくてもよい箇所、読むべき箇所の見当をつけられるのが package by feature 最大の特長 です。
package by feature でもレイヤードアーキテクチャにできる
package by feature を採用する際、レイヤードアーキテクチャを諦める必要はありません。むしろ2つは併用できます。
そもそも、 レイヤードアーキテクチャを実装する際、ディレクトリの使用は必須ではありません。 実際、ファイル内でレイヤーを構築できます。
以下は実際に単一ファイルが5つのレイヤーで分かれています。経年劣化に耐える ReactComponent の書き方 の例から引用しています。
// (1) import層
import React from 'react'
import styled from 'styled-components'
// (2) Types層
type ContainerProps = {...}
type Props = {...} & ContainerProps
// (3) DOM層
const Component: React.FC<Props> = props => (...)
// (4) Style層
const StyledComponent = styled(Component)`...`
// (5) Container層
const Container: React.FC<ContainerProps> = props => {
return <StyledComponent {...props} />
}
このコードは、import、types、DOM、style、container の5つのレイヤーに分かれています。この考え方を応用すれば、単一のディレクトリ内でもレイヤードアーキテクチャを作成できます。
従来の「package by layer」のアプローチでは、コードベースをレイヤーごとに区切っていましたが、新しい「package by feature」のアプローチでは、コードベースを機能ごとに区切り、さらに機能内でレイヤーを区切ることが可能です。
package by layer
package by feature
つまり、package by feature は 2次元的ディレクトリ構成で、package by layer は1次元的なディレクトリ構成です。
みてわかるとおり、package by feature は package by layer のほぼ上位互換です (若干大げさな表現ですが)。package by feature にできなくて package by layer にできることは殆どないと思います
まとめ
- package by feature は機能単位でファイルをまとめるディレクトリ構成手法
- package by feature の利点は、特定の機能に関連するコードを簡単に見つけられる ことで、コードの読解が容易になる
- 必要に応じて package by feature の中でレイヤードアーキテクチャを実装できる
このように package by feature は特にフロントエンド領域でコードベースを効率よく管理できてオススメです。ぜひ取り入れてみてください。
参考
- そのファイル、本当に hooks/・utils/ に入れるんですか? ―― React プロジェクトを蝕む「見かけ駆動パッケージング」
- 他人がはやく読めるコードを書くために
- 経年劣化に耐える ReactComponent の書き方
-
名前がまだ定まっていない概念のようで、明確にこう呼ばれているのは他人がはやく読めるコードを書くためにのみです ↩︎
Discussion