コードの整理
7分 34秒
shadcn/ui を踏襲し、レイヤード構成でコードを整理しています。
ディレクトリ構成
コロケーションのルール
特定の画面に依存するコードは、その画面のディレクトリに配置します。ただし、少しでも再利用の余地がある場合 /components に配置します。
データ取得(サーバー)
データ取得ロジックは、/data に配置します。あらゆるデータ取得は必ず /data の関数を使用します。これによりセキュリティリスクを低減します。
データ更新(作成、更新、削除)
変更系の処理は Server Actions として /actions に配置します。あらゆるデータ更新は必ず /actions の関数を使用します。これによりセキュリティリスクを低減します。
データ取得(クライアント)
クライアントからデータ取得を行う場合は、/swr に SWR のカスタムフックを配置します。クライアントからデータ取得する際は必ず /swr の関数を使用します。また、SWR用のルートハンドラーも /app/api に配置します。ルートハンドラーは内部的に /data の関数を使用します。
Zod スキーマ
Zod スキーマは、/zod に配置します。Zod スキーマは基本的に drizzle-zod を使用して Drizzle スキーマに基づいて定義します。
型
型は、/types に配置します。型は基本的に Drizzle スキーマに基づいて定義します。
コンポーネントファイルの粒度
単体で export する必要がない構成コンポーネントは、そのファイル内で直接定義します。
use client で区切る場合や、一つのファイルに書くと極端に長くなる場合(概ね400行以上)は、ファイルを分割します。
Features 構成との比較
Features 構成とは、機能ごとにディレクトリを分ける構成です。
レイヤード構成を採用することで、以下のようなメリットがあります。
コードの場所が予測可能
レイヤード構成では、機能ごとにディレクトリを分けるのではなく、責務ごとにディレクトリを分けます。これにより、コードの場所が予測可能になり、新しいメンバーでも素早くコードベースを理解できます。
セキュリティリスクの低減
データ取得は /data、データ更新は /actions に一元化することで、すべてのデータアクセスを制御できます。これにより、意図しないデータアクセスやセキュリティホールを防ぐことができます。
再利用性の向上
共通のコンポーネントやロジックを /components や /lib に配置することで、コードの再利用性が向上します。また、特定の画面に依存するコードは画面のディレクトリに配置することで、適切なスコープでコードを管理できます。
保守性の向上
責務ごとにディレクトリを分けることで、変更の影響範囲が明確になります。例えば、データベーススキーマを変更する場合は /db/schemas のみを確認すればよく、型定義を変更する場合は /types のみを確認すればよいため、保守性が向上します。
一貫性の確保
チーム全体で同じディレクトリ構成を採用することで、コードレビューやペアプログラミングの際に、コードの場所についての認識のずれが発生しにくくなります。
Features 構成の課題
ドメイン知識の必要性
Features 構成では、コードを配置する際に「どの機能に属するか」を判断する必要があります。そのため、プロジェクトのドメイン知識が十分でないメンバーがコードを配置する際に、適切な場所を見つけることが困難になる場合があります。
機能の境界が曖昧な場合の破綻
機能の境界が曖昧な場合、同じようなコードが複数の機能ディレクトリに散在してしまう可能性があります。例えば、「ユーザー管理」と「プロフィール管理」の境界が曖昧な場合、どちらのディレクトリにコードを配置すべきか判断に迷い、コードの重複や不整合が発生しやすくなります。
メンバー間の解像度の差による破綻
チームメンバー間で機能の解像度(機能の粒度や範囲の理解)に差がある場合、同じ機能でも異なるディレクトリにコードが配置されてしまう可能性があります。これにより、コードの場所が予測不可能になり、コードベースの理解が困難になります。
レイヤード構成の優位性
レイヤード構成では、責務ごとにディレクトリを分けるため、ドメイン知識がなくても「データ取得なら /data」「コンポーネントなら /components」というように、明確な判断基準に基づいてコードを配置できます。また、機能の境界が曖昧でも、責務は明確なため、コードの配置に迷うことが少なくなります。