ADR-006: NestJS採用(バックエンドフレームワーク)
ステータス
採用済み
意思決定者
- 太田裕貴(CTO)
- バックエンドチーム
決定日
2021年頃(プロジェクト初期)
背景(Context)
Qudenのバックエンドは、動画メタデータ管理、ユーザー認証、動画アップロード、検索機能など、多様なAPIを提供する必要があります。
現状の課題
- Expressだけでは、大規模プロジェクトの構造化が困難
- 依存性注入(DI)の仕組みがなく、テスタビリティが低い
- APIのバリデーション、エラーハンドリングが標準化されていない
- TypeScriptとの統合が不十分
- マイクロサービスへの拡張性が考慮されていない
解決したいこと
- 大規模プロジェクトに耐える構造化されたアーキテクチャ
- 依存性注入(DI)によるテスタビリティ向上
- TypeScriptファーストなフレームワーク
- バリデーション、認証、エラーハンドリングの標準化
- 将来的なマイクロサービス化への対応
要件(Requirements)
機能要件
- RESTful APIの実装
- リクエストバリデーション
- 認証・認可(JWT、AWS Cognito統合)
- エラーハンドリングの統一
- データベース統合(MongoDB)
- ファイルアップロード(S3統合)
非機能要件
- 開発効率: 生産性の高いフレームワーク
- 保守性: 明確なアーキテクチャと構造化
- テスタビリティ: ユニットテスト・統合テストが容易
- スケーラビリティ: 将来的なマイクロサービス化に対応
制約条件
- 技術的制約: TypeScript、MongoDBと統合可能
- ビジネス的制約: オープンソースで無料
- 時間的制約: 学習コストが許容範囲内
検討した選択肢
選択肢1: Express(軽量フレームワーク)
概要
Node.jsの最も一般的なWebフレームワーク。軽量でシンプル。
メリット
- 学習コストが低い(チームメンバーが既に習得)
- 軽量で柔軟性が高い
- エコシステムが成熟している
- ミドルウェアが豊富
デメリット
- アーキテクチャの標準化がない(自由度が高すぎる)
- 依存性注入(DI)の仕組みがない
- TypeScriptとの統合が不十分
- バリデーション、認証等を自前で実装する必要がある
- 大規模プロジェクトでの構造化が困難
実装コスト
- 初期実装: 低
- 学習コスト: 低
- 保守コスト: 高(構造化が困難)
選択肢2: Fastify(高速軽量フレームワーク)
概要
Express代替として注目される、高速で軽量なフレームワーク。
メリット
- Expressより高速(ベンチマーク上)
- スキーマベースのバリデーション(JSON Schema)
- TypeScriptサポート
デメリット
- エコシステムがExpressより小さい
- アーキテクチャの標準化がない
- 依存性注入(DI)の仕組みがない
- 学習リソースが少ない
実装コスト
- 初期実装: 中
- 学習コスト: 中
- 保守コスト: 中
選択肢3: NestJS(エンタープライズフレームワーク)
概要
Angular風のアーキテクチャを採用した、TypeScriptファーストのNode.jsフレームワーク。
メリット
- TypeScriptファースト: デフォルトでTypeScript、型安全性が高い
- 依存性注入(DI): テスタビリティが高い
- モジュラーアーキテクチャ: 大規模プロジェクトに最適
- 標準化: バリデーション(class-validator)、認証(Passport)、エラーハンドリング等が標準化
- Mongoose統合: MongoDBとの統合が容易
- マイクロサービス対応: @nestjs/microservices でマイクロサービス化が容易
- CLI: ボイラープレートを自動生成
- 豊富なドキュメント: 公式ドキュメントが充実
デメリット
- 学習コストが高い(Angular風のアーキテクチャ)
- Expressより複雑
- ボイラープレートが多い
実装コスト
- 初期実装: 中
- 学習コスト: 中〜高
- 保守コスト: 低(構造化されている)
決定(Decision)
選択した技術: NestJS
選択理由
要件との適合性
- TypeScriptファーストで、型安全性が高い
- 依存性注入(DI)により、ユニットテストが容易
- class-validatorによるリクエストバリデーションが標準化
- Passportによる認証・認可が容易(AWS Cognito統合も可能)
- Mongooseとの統合が公式サポートされている
技術的妥当性
- モジュラーアーキテクチャ: Modules、Controllers、Servicesで明確に分離
- スケーラビリティ: @nestjs/microservices でマイクロサービス化が容易
- テスタビリティ: DIにより、モックが容易でテストが書きやすい
- エコシステム: @nestjs/* パッケージが充実(GraphQL、WebSocket、CQRS等)
コスト対効果
- 学習コストは高いが、長期的な保守性が大幅に向上
- 公式ドキュメント・学習リソースが充実
- CLIによるボイラープレート生成で開発効率が高い
将来性
- エンタープライズ向けNode.jsフレームワークとして広く採用
- 積極的な開発とコミュニティサポート
実装方針
モジュラーアーキテクチャの採用
- 機能ごとにモジュールを分離(UsersModule、ItemsModule、AuthModule等)
- Controllers(HTTP)、Services(ビジネスロジック)、Repositories(データアクセス)で責務を分離
依存性注入(DI)の活用
- Services、Repositories等をDIコンテナで管理
- テスト時にモックを注入しやすい設計
バリデーションの標準化
- class-validator + class-transformer でDTOを定義
- ValidationPipe でリクエストバリデーションを自動化
認証・認可
- PassportStrategy でAWS Cognito JWTを検証
- Guards でエンドポイントごとに認証・認可を制御
エラーハンドリング
- Exception Filters でエラーレスポンスを統一
- カスタム例外クラス(NotFoundException、BadRequestException等)
受け入れたトレードオフ
トレードオフ1: 学習コスト
- 影響: Angular風のアーキテクチャに慣れる必要がある
- 受け入れ理由: 公式ドキュメント・学習リソースが充実しており、長期的なメリットが大きい
- 軽減策: チーム内での勉強会、公式ドキュメントの共有、ペアプログラミング
トレードオフ2: ボイラープレートの多さ
- 影響: ExpressよりもコードBuoyancy必要
- 受け入れ理由: NestJS CLIによりボイラープレートを自動生成可能、構造化されたコードは保守性が高い
- 軽減策: Nest CLI の積極的な活用(
nest g module,nest g service等)
トレードオフ3: パフォーマンスオーバーヘッド
- 影響: DIコンテナ等のオーバーヘッドがある
- 受け入れ理由: ほとんどの場合、パフォーマンスボトルネックはDBやネットワークI/Oであり、フレームワークのオーバーヘッドは微小
- 軽減策: 必要に応じてキャッシング、最適化を実施
結果(振り返り)
うまくいったこと(Pros)
- 構造化されたコード: モジュラーアーキテクチャにより、コードが整理され保守性が大幅に向上
- テスタビリティの向上: DIにより、ユニットテストが容易になった
- 開発効率: class-validator、Passport等の標準化により、開発速度が向上
- TypeScript統合: TypeScriptファーストで、型安全性が確保された
- チーム開発: 明確なアーキテクチャにより、チームメンバー間での一貫性が確保された
うまくいかなかったこと(Cons)
- 学習曲線: 新規メンバーがNestJSのアーキテクチャに慣れるのに時間がかかった
- ボイラープレート: 小規模な機能でもModule、Controller、Service等を作成する必要があり、面倒に感じることがあった
学び
- モジュール分離の重要性: 適切なモジュール分離により、コードの見通しが良くなった
- DI の強力さ: テスト時のモック注入が容易で、テスト駆動開発(TDD)がしやすくなった
- class-validatorの活用: DTOでバリデーションを定義することで、APIの型安全性が向上
今後の改善点
- GraphQLの導入検討: @nestjs/graphql を活用したGraphQL APIの検討
- CQRS パターンの導入: 複雑なビジネスロジックにCQRSパターンを適用
- マイクロサービス化: 将来的に@nestjs/microservices を活用したマイクロサービス化