Development Guidelines
アーキテクチャ
バックエンドのクリーンアーキテクチャ4層構成のルール。
目的
AIが実装時にアーキテクチャに違反したコードを書かないようにする。また、レビュー時にアーキテクチャに違反したコードを検出する。
レイヤー構成
クリーンアーキテクチャに基づく4層構成である。
API → UseCase → Domain ← Infrastructure- Domain層は他のレイヤーに依存しない
- Infrastructureは Domainのインターフェースを実装する(依存性逆転)
各層の責務
Domain層
ビジネスの本質を表現する。フレームワークや外部技術に一切依存しない。
UseCase層
アプリケーション固有の操作を実行する。Domain層のエンティティとRepositoryインターフェースを組み合わせてロジックを実行する。
Infrastructure層
技術的な詳細を実装する。Domain層で定義されたRepositoryインターフェースの具体的な実装を提供する。
API層
外部とのインターフェースを担う。クライアントからのリクエストを受け取り、UseCaseに委譲する。ビジネスロジックを持たない。
ルール
Domain層
エンティティ
- クラスで定義する
- constructorは
privateにする。生成経路をcreate()/reconstruct()に限定する create()は新規作成用。ビジネスルール(初期値の設定など)を強制する。idとcreatedAtはUseCaseから渡すreconstruct()はDBからの復元用。全フィールドをそのまま受け取る- フィールドは
readonlyで公開する - constructor内でValibotスキーマによるランタイム検証を行う
update()メソッドは新しいインスタンスを返す(不変パターン)
ID
domain/id.tsのgenerateId()で生成する(nanoid)- UUIDは使わない
Repository
- インターフェース(
interface)として定義する - メソッド名は操作の意図を明確にする(
create/update/findById/findAll)
UseCase層
- クラスとして定義し、
execute()メソッドで実行する - 1クラス1操作を原則とする
- コンストラクタでRepositoryインターフェースを受け取る(コンストラクタインジェクション)
- 入力スキーマはUseCaseがValibotで定義・exportする。API層はそのスキーマをimportしてバリデーションに使う
- 更新系はLoad-Mutate-Saveパターンで実装する
execute()の戻り値はDTO(plainなtype)で返す。Domain Entityをそのまま返さない- DTOは
usecase/{domain}/{domain}-dto.tsにXxxDto型とtoXxxDto変換関数を定義する
Infrastructure層
di/container.tsでUseCaseとその依存を組み立てる- Repository実装は
infrastructure/配下に置く - DBの行からエンティティへの変換は
toDomainヘルパーでreconstruct()を使う
API層
- tRPCルーターはUseCaseを呼び出すだけ。ビジネスロジックを持たない
- ドメインエラーから
TRPCErrorへの変換はtrpc.tsのmiddlewareが行う - ルーター内でエラーをキャッチしない。エラー変換はmiddlewareに一任する
エラーハンドリング
Domain層
- ビジネスルール違反は
DomainErrorのサブクラスをthrowする - ドメイン固有のエラークラスは作らず、汎用エラー(
NotFoundError,ValidationError,AlreadyExistsErrorなど)を全ドメインで使い回す
UseCase層
- Domain層のエラーはキャッチせず、そのまま上位に伝播させる
- UseCase固有のエラーが必要な場合も
DomainErrorのサブクラスとして定義する
Infrastructure層
- ドメインエラーをthrowしない
- データの不在は戻り値(
null, 空配列など)で表現し、UseCase層に判断を委ねる - 技術的な障害(DB接続エラー等)はそのまま上位に伝播させる
API層
trpc.tsのmiddlewareがDomainErrorのサブクラスを検出し、適切なコードのTRPCErrorに変換する
ディレクトリ構成
server/
├── api/
│ ├── trpc.ts # tRPC初期化・ドメインエラー変換middleware
│ ├── index.ts # appRouterの定義
│ └── routers/ # 各ドメインのルーター
├── usecase/
│ └── {domain}/ # ドメインごとにディレクトリを分ける
├── domain/
│ ├── id.ts # ID生成(nanoid)
│ ├── error/ # ドメインエラー
│ └── {domain}/ # エンティティ + Repositoryインターフェース
└── infrastructure/
├── di/ # DIコンテナ
└── {impl}/ # Repository実装