🤩

Next.jsのディレクトリ構成を考えてみた

2021/05/07に公開3

はじめに

メーカー系企業のコーポレート部門で業務システムを作っているモンゴルと申します。
趣味やPJでNext.jsを使っているのですが、チュートリアルを読んだ後に、とりあえずやってみようの精神で、4つほど動くものを作ってきました。
ただ、この中でディレクトリ構成についてもやもやを感じるようにもなりました。
Next.jsに触れ始めてから半年ほど経過したこともあるので、このタイミングでよりよいディレクトリ構成について整理していきたいと思います。

今までずっとサーバーサイドのエンジニアだったので、間違った認識等あるかと思います。優しくご指摘いただけると幸いです。

実装を初めて何が問題になったか?

使用するデータベースやSaaSが変更になる可能性もあったので、外部へアクセスするコードはlibフォルダにまとめるようにしようと考えて実装をはじめました。
Next.jsのチュートリアルのコードにおいて、外部へのデータ取得をlibフォルダに置いていたことから、それを踏襲していました)

しかし、実際に実装してみると各関数の粒度がまちまちだったり、基準なく関数が大量に作られた結果、再利用性が低いコードが散見されるようおになってしまいました。

具体的には、以下のような問題が発生していました

  • SaaSへのアクセスがgetServersiceProps, getStaticProps, API Route, libフォルダに散乱
  • libフォルダ内でデータを加工するだけの関数、データを取得するだけの関数、データ取得も加工もする関数と、人によって、また同じ人でもタイミングによって、まちまちの粒度で書いてしまって、結果的に関数の数は多い割に、全然再利用されない構造になっていた
  • pages配下のファイルがめちゃくちゃ大きくなったり、小さくなったりしていた
    • 小さくなっているところも、ただファイルが分割されているというだけで、再利用性は低かった

じゃあどうした?

業務ロジックが絡むところは再利用しにくいが、UIやデータベースアクセスの層は共通化できそうな雰囲気はありました。
また、ユースケースに応じてフォルダを分けたほうが見やすそうだという雰囲気もありました。
以上を踏まえて、現在は以下のようなディレクトリ構成にすると、適度に再利用でき、かつ、可読性が高いコードが書けそうだと考えるようになりました。
MVCっぽくもあり、レイヤードアーキテクチャっぽくもある構成だなと思っています。

src/
├ components/
│ ├ common/
│ ├ .../
│ └ .../
├ constants/
├ libs/
│ ├ .../
│ └ .../
├ models/
│ ├ .../
│ └ .../
├ services/
│ ├ .../
│ └ .../
├ pages/
│ ├ .../
│ └ .../
├ repositories/
│ ├ .../
│ └ .../
└ utils/

私は、各ディレクトリは以下のような責務を持たせています。

components/

全ページ共有、ページ固有のコンポーネントを分けます。
こうすることで、再利用するものと再利用しないものを分けます。
ページ固有のコンポーネントは実は複数ページで使える可能性もあるので、ディレクトリはきっちり分けずに、ユーザーのおおまかなアクションの区切りで数個のディレクトリに分けています。

レイヤードアーキテクチャでいうUI層、MVCでいうViewを担っています。

pages/

Next.js固有のディレクトリです。
MVCでいうコントローラ的位置づけで、ユーザーからのリクエストを受け取って、getServerSidePropsやgetStaticPropsを用いながらservicesの関数を呼び出してレスポンスを返すコードを書きます。
jsxを書けるので、ここに直接jsxを書いてもいいのですが、見た目部分は分けたかったので、大部分はcomponentsで定義し、pages配下ではcomponentsで定義されたコンポーネントを呼び出して、jsxを組み立てるという構成にしています。

レイヤードアーキテクチャでいうUI層とApplication層の紐付け、MVCでいうControllerを担っています。

services/

ユーザーのアクションに応じて、データの加工(結合や集計など)を行うコードを置いておきます。
加工元となるデータ取得は、repositories配下の関数を呼び出すことにより行っています。
ユーザーの1アクションに対して、1つのサービス関数を作るイメージでいます。
ここも、componentsと同様に、おおまかなアクションの区切りでディレクトリを分けています。

レイヤードアーキテクチャでいうApplication層を担っています。

repositories/

DBやSaaSにアクセスする層になります。
データを取得して、データマッピングに従い、オブジェクト化する責務を持たせます。
一つのテーブルに対して、一つのファイルにしていけば良さそうだと思っています。
使用しているDBやSaaSに依存したコードはここに配置します。
使用しているDBやSaaSが変更されたときは、ここを変更するだけで対応できるようにしています!と、言いたいところではありますが、使用している基盤技術をごっそり変える時、例えばREST APIからGraphQLに移行するといった場合では、servicesの関数も変更する必要が出てくると思っています。

レイヤードアーキテクチャでいうInfrastructure層を担っています。

models/

ここは人によってはentities、types、interfaces等、様々な名付け方をしそうですが、自分はmodels、entitiesの二択でmodelsにしました。好きな名前にするといいと思います。
modelごとにファイルを分けています。
また、servicesで加工したデータと、repositoriesで取得したデータの区別が付きやすいように、servicesで生成するオブジェクトについては、モデル名にXXXDataのようなサフィックスをつけるようにしています。

レイヤードアーキテクチャでいうDomain層、MVCでいうModelを担っています。

libs/

使用しているライブラリ固有のコードで、初期化や設定のコードなど、データ取得に絡まないコードはここに配置します。
ライブラリ毎にまるっと切り替わる可能性があるので、ライブラリごとにディレクトリを分けています。

utils/

グローバルで使える便利な関数(ex.文字列の加工など)を配置しています。

constants/

グローバルで参照される定数を定義したファイルを配置しています。

コードの依存関係について

依存関係としては
components/

pages/

services/

repositories/
というようにしています。
責務がしっかり分離されるように、たとえ一つの関数を呼び出すだけであったとしても、依存関係はスキップせずにコードを書いています。

まとめ

レイヤードアーキテクチャとMVCの狭間のようなアーキテクチャになっているので、何かもったいないことをしているかもしれません。ただ、ルールを決める前よりは、各ディレクトリの責務をはっきりしたことでコードは書きやすくなりましたし、読みやすくなりました。
とはいえ、まだ課題はあります。例えば、Serviceディレクトリにはサーバーサイドで実行されるコードも、フロントで実行されるコードも混ざってしまっています。
ReactのServer componentのように、拡張子名を変えるなり、ディレクトリを分けるなりしたほうが良さそうだなと感じています。

今後、Next.jsの開発を進めていく中でより良いディレクトリ構成が発見できたらまた共有します。

Discussion

ニツオニツオ

repositories(ほかの事例ではserviceに統合されてることもある)とpages/api(本記事では載ってないけどNextでデフォルトであるやつ)ってどう違うんでしょうか?

apiの方はURLにも対応してるので、外部に公開するAPIのソースを置くイメージで、serviceの方は外部に公開する用ではなくて自分のアプリが外部と通信するためのソースを置くイメージです。

mongolyyさんのご意見伺いたいです

mongolyymongolyy

ニツオさん、ご質問ありがとうございます!!

apiの方はURLにも対応してるので、外部に公開するAPIのソースを置くイメージで、serviceの方は外部に公開する用ではなくて自分のアプリが外部と通信するためのソースを置くイメージです。

私も同じ認識です。
apiの方はHTTPリクエストを受け取り、適切なservice関数を呼び出し、service関数からの返り値に応じて、適切なHTTPレスポンスを返す責務を持っています。(コード量は少ないです)
pagesも同様で、api + レンダリングするコンポーネントを指定する責務を持っているイメージでいます

そうすることによって、複数のpages/apiから、同一のservice関数が使えるようになり、使い回ししやすくなると思っています。