NestJSにおけるクリーンアーキテクチャなディレクトリ構造
追記: こちらの記事も書きました!
ディレクトリ構造いろいろ
ディレクトリ構造って結構悩みますよね。NestJSに限らずいくつかパターンを上げてみたいと思います。
Rails型
僕が最初に学んだディレクトリ構造です。
├── controllers
│ ├── userController.ts
│ └── chatController.ts
├── services
│ ├── userService.ts
│ └── chatService.ts
├── models
│ ├── user.ts
│ └── chat.ts
└── configs
Rails型クリーンアーキテクチャ
僕がクリーンアーキテクチャを学んで作ったディレクトリ構造です。
├── presentations
│ ├── userController.ts
│ └── chatController.ts
├── usecases
│ ├── userService.ts
│ └── chatService.ts
├── domains
│ ├── user
│ │ ├── userRepositoryInterface.ts
│ │ └── userEntity.ts
│ └── chat
│ ├── chatRepositoryInterface.ts
│ └── chatEntity.ts
├── infrastructures
│ ├── sequelize
│ │ └── userRepository.ts
│ └── dynamodb
│ └── chatRepository.ts
└── configs
主要ドメイン分割クリーンアーキテクチャ
Rails型のクリーンアーキテクチャを継承しつつ、主要ドメインで最初のディレクトリを分けた構造です。どっかの会社が採用しているのをどこかの記事で見ました。主要ドメインディレクトリ配下に関連ドメインが収まります。例えば、チャットドメイン配下に参加者リストドメインが入ってくるみたいな感じです。だから主要ドメイン本体をcore
として分離させています。
├── chat
│ ├── presentations
│ │ └── chatController.ts
│ ├── usecases
│ │ └── chatService.ts
│ ├── domains
│ │ └── core
│ │ ├── chatRepositoryInterface.ts
│ │ └── chatEntity.ts
│ ├── infrastructures
│ │ └── dynamodb
│ │ └── chatRepository.ts
│ └── configs
└── account
├── presentations
│ └── userController.ts
├── usecases
│ └── userService.ts
├── domains
│ └── core
│ ├── userRepositoryInterface.ts
│ └── userEntity.ts
├── infrastructures
│ └── sequelize
│ └── userRepository.ts
└── configs
モジュール型クリーンアーキテクチャ
nestjsでクリーンアーキテクチャやるならこの構造になると思います。フォルダ名はなんとなくで命名しているので、entity
やinfra
などは好きな名前でもいいと思います。
├── chat
│ ├── core
│ │ ├── entity
│ │ │ ├── chat.entity.ts
│ │ │ └── chat.repository.interface.ts
│ │ ├── infra
│ │ │ └── chat.repository.dynamodb.ts
│ │ ├── chat.controller.ts
│ │ ├── chat.service.ts
│ │ └── chat.module.ts
│ └── chat.index.module.ts
└── account
├── core
│ ├── entity
│ │ ├── user.entity.ts
│ │ └── user.repository.interface.ts
│ ├── infra
│ │ └── user.repository.sequelize.ts
│ ├── user.controller.ts
│ ├── user.service.ts
│ └── user.module.ts
└── account.index.module.ts
クエリサービスを導入したい
モジュールディレクトリに直接user.query.service.interface.ts
とインターフェースを起きます。次にinfra
ディレクリに実際の実装user.repository.sequelize.ts
を起きます。
└── account
├── core
│ ├── entity
│ │ ├── user.entity.ts
│ │ └── user.repository.interface.ts
│ ├── infra
│ │ ├── user.query.service.sequelize.ts
+ │ │ └── user.repository.sequelize.ts
│ ├── user.controller.ts
│ ├── user.service.ts
+ │ ├── user.query.service.interface.ts
│ └── user.module.ts
└── account.index.module.ts
外部ライブラリをモジュールとして導入したい
nestjsのやり方の従うと、sequelizeやprismaなど外部ライブラリは直接import
せずに、モジュール管理して、使いたいサービスにDIすることになります。作成の仕方に2パターンあります。
パターン1 ライブラリの固有名詞を隠す。
├── account
│ ├── core
│ │ ├── entity
│ │ │ ├── user.entity.ts
│ │ │ └── user.repository.interface.ts
│ │ ├── infra
│ │ │ └── user.query.service.sequelize.ts
│ │ ├── user.controller.ts
│ │ ├── user.service.ts
│ │ └── user.module.ts
│ └── account.index.module.ts
└── cache
├── infra
│ └── cache.service.redis.ts
├── cache.service.interface.ts
└── cache.module.ts
パターン2 ライブラリの固有名詞を使う。
├── account
│ ├── core
│ │ ├── entity
│ │ │ ├── user.entity.ts
│ │ │ └── user.repository.interface.ts
│ │ ├── infra
│ │ │ └── user.query.service.sequelize.ts
│ │ ├── user.controller.ts
│ │ ├── user.service.ts
│ │ └── user.module.ts
│ └── account.index.module.ts
└── sequelize
├── sequelize.service.ts
└── sequelize.module.ts
パターン1は、redisのもつキャッシュという役割の前面にだし、抽象化しています。そのため、cache.service.ts
ではなくてcache.service.interface.ts
になっています。実装はinfra
ディレクトリで行っています。
パターン2は、sequelizeそのものをサービス化しています。
抽象化の観点からはパターン2よりパターン1のほうがおすすめです。パターン2は、主に他のディレクトリのinfra
ディレクトリ内で共通で使うライブラリを注入するために利用します。
抽象化に関してはこちらの記事で詳しく解説しています。
ドメインイベント関連のディレクトリが入ると
├── account
│ ├── core
│ │ ├── entity
│ │ │ ├── user.entity.ts
│ │ │ └── user.repository.interface.ts
│ │ ├── event
│ │ │ └── account.created.event.ts
│ │ ├── infra
│ │ │ └── user.query.service.sequelize.ts
│ │ ├── user.controller.ts
│ │ ├── user.service.ts
│ │ └── user.module.ts
│ └── account.index.module.ts
├── notification
│ ├── listener
│ │ └── account.created.listener.ts
│ ├── notification.service.ts
│ └── notification.module.ts
└── share
├── baseEvent.ts
└── util.ts
実装はこちらを参照してください。
Discussion