NestJSの設計原則

## はじめに
本記事では、NestJSアプリケーションを設計・実装する際の主要な原則について解説します。

## モジュラーアーキテクチャ

NestJSの核となる設計思想は、アプリケーションを機能的な単位（モジュール）に分割することです。

```ts
@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
    AuthModule,
    ConfigModule,
  ],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}
```

各モジュールは：
- 単一の責任を持つ
- 独立してテスト可能
- 再利用可能な単位として機能

## 依存性注入（DI）

DIはNestJSアプリケーションの設計の中心です：

```typescript
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepository: Repository<User>,
    private readonly configService: ConfigService,
  ) {}
}
```

DIの利点：
- 疎結合な設計
- テスタビリティの向上
- コードの再利用性

## レイヤードアーキテクチャ

アプリケーションは明確に分離されたレイヤーで構成：

```
src/
├── controllers/    # プレゼンテーション層
├── services/       # ビジネスロジック層
├── repositories/   # データアクセス層
└── domain/        # ドメインモデル
```

### 実装例：

```typescript
// Controller層（プレゼンテーション）
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }
}

// Service層（ビジネスロジック）
@Injectable()
export class UsersService {
  constructor(private readonly usersRepository: UsersRepository) {}

  async create(data: CreateUserDto): Promise<User> {
    // ビジネスロジックの実装
    return this.usersRepository.create(data);
  }
}

// Repository層（データアクセス）
@Injectable()
export class UsersRepository {
  constructor(
    @InjectRepository(User)
    private readonly repository: Repository<User>,
  ) {}

  async create(data: CreateUserDto): Promise<User> {
    return this.repository.save(data);
  }
}
```

## 型安全性とバリデーション

DTOとバリデーションデコレータを使用した堅牢な型システム：

```typescript
export class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  readonly name: string;

  @IsEmail()
  readonly email: string;

  @IsString()
  @MinLength(8)
  readonly password: string;
}
```

## エラー処理

集中的なエラー処理メカニズム：

```typescript
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const status = exception.getStatus();

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      message: exception.message,
    });
  }
}
```

## ベストプラクティス

### 1. 設定管理
```typescript
@Injectable()
export class AppConfigService {
  constructor(private configService: ConfigService) {}

  get databaseUrl(): string {
    return this.configService.get<string>('DATABASE_URL');
  }
}
```

### 2. トランザクション管理
```typescript
@Injectable()
export class UsersService {
  constructor(private dataSource: DataSource) {}

  async createUserWithProfile(userData: CreateUserDto) {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      // トランザクション内の処理
      await queryRunner.commitTransaction();
    } catch (err) {
      await queryRunner.rollbackTransaction();
      throw err;
    } finally {
      await queryRunner.release();
    }
  }
}
```

### 3. ガード（認証・認可）
```typescript
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super();
  }

  canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.get<boolean>(
      'isPublic',
      context.getHandler()
    );

    if (isPublic) {
      return true;
    }

    return super.canActivate(context);
  }
}
```

## 実践的な注意点

1. **モジュールの分割粒度**
   - 大きすぎず小さすぎない適切な粒度
   - 機能的な凝集度の高さ
   - 明確な責任範囲

2. **テスタビリティ**
   - モックとスタブの適切な使用
   - テストカバレッジの維持
   - E2Eテストの重要性

3. **パフォーマンス考慮**
   - N+1問題の回避
   - キャッシング戦略
   - 非同期処理の適切な使用

## まとめ

これらの設計原則は、以下を実現するための基盤となります：
- メンテナンス性の高いコードベース
- スケーラブルなアプリケーション
- 堅牢なエラー処理
- 効率的なチーム開発

プロジェクトの要件や規模に応じて、これらの原則を適切に組み合わせることが重要です。

## 参考文献

https://docs.nestjs.com/


https://github.com/Sairyss/domain-driven-hexagon


https://github.com/nestjs/typescript-starter