Dev Study
← 解説「プロバイダとサービス」に戻る

サンプルコードで身につける: プロバイダとサービス

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1最小のサービス

@Injectable() を付けただけの最小のサービスです。HTTP のことを何も知らないただのロジック置き場であり、どこから呼ばれても同じ仕事をするのがサービスの理想形だと示します。

import { Injectable } from "@nestjs/common";

@Injectable() // 「DIコンテナで管理してよいクラス」という宣言
export class GreetingService {
  greet(userName: string): string {
    return "こんにちは、" + userName + "さん";
  }
}
// HTTP を知らない、純粋なロジックの置き場になる

2コントローラへ注入する

サービスとコントローラをつなぐ最も基本の形です。new と書く代わりにコンストラクタへ「欲しい型」を書くだけで受け取れる、という前レッスンの DI がそのまま NestJS の書き味になっています。

import { Controller, Get, Injectable } from "@nestjs/common";

@Injectable()
export class UsersService {
  findAll() {
    return [{ id: 1, name: "Alice" }];
  }
}

@Controller("users")
export class UsersController {
  // new とは書かず、欲しい型を引数に書くだけ
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll() {
    return this.usersService.findAll();
  }
}

3太ったコントローラをサービスへ逃がす

コントローラに直書きされた業務ロジックをサービスへ移すリファクタの例です。ロジックに findAdults という名前が付いて再利用とテストがしやすくなる、実務レビューで最も頻繁に指摘されるパターンです。

// 悪い例: コントローラのハンドラに業務ロジックが直書き
// @Get("adults")
// findAdults() {
//   return this.users.filter((u) => u.age >= 18);
// }

import { Injectable } from "@nestjs/common";

@Injectable()
export class UsersService {
  private users = [
    { id: 1, name: "Alice", age: 20 },
    { id: 2, name: "Bob", age: 15 },
  ];

  // 良い例: ロジックをサービスに置き、名前を付ける
  findAdults() {
    return this.users.filter((u) => u.age >= 18);
  }
}
// コントローラ側は this.usersService.findAdults() を呼ぶだけになる

41つのインスタンスを共有する意味

プロバイダが既定でシングルトン(アプリ全体で1個)になることの意味をプレーン TypeScript で再現した例です。複数のコントローラが同じサービスを注入すると、内部の状態も共有されることが分かります。

TypeScript

5サービスからサービスへの注入

コントローラだけでなくサービス同士も同じ仕組みで注入できることを示す実務例です。監査ログやメール送信のような横断的な処理を専用サービスに切り出し、各サービスから再利用するのが定番の構成です。

import { Injectable } from "@nestjs/common";

@Injectable()
export class AuditLogService {
  record(action: string): void {
    console.log("[監査ログ] " + action);
  }
}

@Injectable()
export class UsersService {
  // サービス同士も同じ仕組みで注入できる
  constructor(private readonly audit: AuditLogService) {}

  remove(id: number) {
    this.audit.record("ユーザー削除: id=" + id);
    return { removed: id };
  }
}
// 監査ログ・通知・決済など、横断機能の再利用が定番の用途