Nest.jsでモジュール設計を効率化するベストプラクティス💡

## ◾️ はじめに
　Nest.jsは、モジュール中心に設計されているため、モジュール構造がプロジェクトの可読性や拡張性に大きく影響します。本記事では、**モジュールの設計の効率化**をテーマに、設計のベストプラクティスと注意点を解説します。
## ◾️ 1. モジュール設計の基本
　Nest.jsでは、すべての機能をモジュール単位で管理します。
　**モジュール設計**とは、アプリケーションを適切な単位で分割し、それぞれの責務を明確にすることです。

**モジュールの役割**
- 機能のカプセル化：機能ごとに分割し、責務を限定する
- 再利用性の向上：特的の機能を別のプロジェクトや他のモジュールで再利用可能にする
- 依存関係の管理：モジュール間の依存関係を明確にし、循環依存を防ぐ

:::note info
**循環依存**とは、複数のモジュールやクラス、関数が互いに依存しあい、それが循環している状態をさします。この状態になると、コードの理解や保守が難しくなるほか、実行時エラーやコンパイルエラーの原因になり得ます。
:::

**例：UserModule**
```typescript
@Module({
  imports: [PrismaModule],
  controllers: [UserController],
  providers: [UserService, UserRepository],
})
export class UserModule { }
```
## ◾️ 2. Feature-based vs Layer-basedの違いとマイベストプラクティス💡
### **Feature-based**
- **特徴**：1つのモジュールが1つの機能を担当する
- **構造例**：
```typescript
|- src
    |-user
        |-user.controller.ts
        |-user.service.ts
        |-user.repository.ts
        |-user.module.ts
    |-auth
        |-auth.controller.ts
        |-auth.service.ts
        |-auth.module.ts
        |-auth.strategy.ts
```
- **メリット**：
    - 機能ごとにコードがまとまっているため、直感的でわかりやすい
    - 新しい機能の追加や既存機能のリファクタリングが容易
- **デメリット**：
    - 小規模プロジェクトでは過剰な設計になることがある
    - 共通処理が多い場合、`common`や`shared`ディレクトリを適切に設計する必要がある
### **Layer-based**
- **特徴**：役割ごとにモジュールを分割
- **構造例**：
```typescript
|-src
    |-controllers
        |-user.controller.ts
        |-auth.controller.ts
    |-services
        |-user.service.ts
        |-auth.service.ts
    |-repository
        |-user.repository.ts
```
- **メリット**：
    - 同じ役割をもつコードを一箇所に集約できる
- **デメリット**：
    - 機能ごとの依存関係が不明確
    - 拡張性が低く、大規模プロジェクトには不向き

### **マイベストプラクティス💡**
やはり、**Feature-based**をオススメします！
1つのモジュールが1つの機能を担当するため、依存関係が明確になり、機能を追加するときに一連の流れでコードを書きやすくなるからです✨

## ◾️ 3. DI（依存性注入）を活用した設計の効率化
### **依存注入（DI）とは**
　DI（Dependency Injection）とは、クラスやモジュールが必要とする依存関係を自分で作成するのではなく、外部から提供してもらう設計手法です。
Nest.jsでは、この仕組みをフレームワークが自動的に管理してくれるため、モジュール間の依存関係を効率的に構築できます。

### **DIのメリット**
**1. モジュール間の結合度を低減**
DIでは、クラスが必要な依存関係を自分で作成せず、外部から渡してもらう仕組みを使うことで疎結合を実現します。
**2. テストが行いやすい**
依存関係を外部から注入することで、モックを使用したテストが簡単になります。

### **DIの活用例** 
以下の例では、AuthServiceをUserServiceに依存として注入しています。
```typescript
@Injectable()
export class UserService {
  // DIで依存を注入
  constructor(private readonly authService: AuthService) {}

  async createUser(dto: CreateUserDto) {
    const email = this.authService.validate(dto.email);
    // ユーザー作成ロジック
  }
}
```
テスト時には、以下のようにモックを渡すことで依存関係を模倣します。

```typescript
const mockAuthService = { validate: jest.fn(() => true) };
const userService = new UserService(mockAuthService as AuthService);

expect(userService.createUser(dto)).toBeDefined();
```
## ◾️ 4. アンチパターンとその回避方法
**1. 全てをGlobalモジュールにする**
- 問題点：モジュール間の依存関係が不明確になる。
- 解決策：必要最小限のモジュールのみGlobalに設定する。

**2. 無制限な依存関係の追加**
- 問題点：複雑なモジュール間の依存関係が増える。
- 解決策：各モジュールの責務を明確にし、共通部分をSharedモジュールに切り出します。

**3. 巨大なモジュール**
- 問題点：1つのモジュールに多くの責務が集中する。
- 解決策：機能ごとにモジュールを分割する。

## ◾️ 5. 責務の分離とレイヤー設計の重要性
　責務を分離することで、以下のようなメリットが得られます。
- **保守性の向上**
変更に強い設計が可能になります。
- **再利用性の向上**
共通ロジックをレポジトリ層で再利用可能にします。
- **テストの効率化**
層ごとにモックを置き換えることで、テストが容易になります。

### 責務の分離の基本
**サービス層（Service Layer）**
- ビジネスロジックを担当
- データベースへの直接的なアクセスは行わず、レポジトリ層を通じてデータを取得・保存
例：ユーザーの登録処理やバリデーションの実装を行う

**レポジトリ層（Repository Layer）**
- データベースとのやり取りを専任で行う
- ORM（PrismaやTypeORMなど）を利用してCRUD操作を実装
- データアクセスロジックの共通化・再利用を目的とする

### 責務分離の実装例
以下に、責務を分離した具体的なコード例を示します。

**UserRepository**
レポジトリ層でデータベースとのやり取りをカプセル化します。

```typescript
import { Injectable } from '@nestjs/common';
// Prismaを利用した例
import { PrismaService } from '../prisma/prisma.service'; 

@Injectable()
export class UserRepository {
  constructor(private readonly prisma: PrismaService) {}

  async findById(id: string) {
    return this.prisma.user.findUnique({ where: { id } });
  }

  async create(data: { name: string; email: string }) {
    return this.prisma.user.create({ data });
  }
}
```
**UserService**
サービス層では、ビジネスロジックに集中します。データベースの操作はレポジトリ層に委譲します。

```typescript
import { Injectable, NotFoundException } from '@nestjs/common';
import { UserRepository } from './user.repository';

@Injectable()
export class UserService {
  constructor(private readonly userRepository: UserRepository) {}

  async getUserById(id: string) {
    const user = await this.userRepository.findById(id);
    if (!user) {
      throw new NotFoundException('User not found');
    }
    return user;
  }

  async registerUser(name: string, email: string) {
    // ビジネスロジックの追加（例: 重複チェックなど）
    const user = await this.userRepository.create({ name, email });
    return user;
  }
}
```
**UserModule**
モジュール内で、依存性を注入するための設定を行います。

```typescript
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserRepository } from './user.repository';

@Module({
  providers: [UserService, UserRepository],
  exports: [UserService], // 他のモジュールで利用する場合
})
export class UserModule {}
```
## ◾️ 7. 責務分離を踏まえたモジュール設計
最終的には、以下のようなモジュール設計を目指します。

構造例
```typescript
|-src
    |-user
        |-user.controller.ts   // HTTPリクエストを処理
        |-user.service.ts      // ビジネスロジック
        |-user.repository.ts   // データベース操作
        |-user.module.ts       // 依存性の設定
    |-prisma
        |-prisma.service.ts    // データベース接続管理
```
## ◾️ まとめ
効率的なモジュール設計は、Nest.jsアプリケーションの保守性とスケーラビリティを向上させます。以下を意識すると良い設計が実現します。

1. Feature-based設計を採用する
2. DIを活用して疎結合を実現する
3. 責務分離を徹底し、各層の役割を明確にする

これにより、スケーラブルで保守性の高いアプリケーションを構築できます！