Dev Study
← Spring コース

47. NestJS の DI と何が違うか

NestJS も も、に型を書けば依存が渡されますが、読む場所が違います。TypeScript の型注釈は出力に残りません。旧式デコレータ(experimentalDecorators)と emitDecoratorMetadata を有効にすると、コンパイラがに design:paramtypes の1行を足し、NestJS はそれを読みます。Java では型が に残り、直接読めます。

この差はで表に出ます。TypeScript のインターフェースは書き出されると Object になり、どのインターフェースだったかは残りません。そのため NestJS では文字列や Symbol、をトークンにします。Java ではインターフェース型がそのまま読めるので、Spring はそれを鍵に実装を探せます。Spring でインターフェースで受けて実装を差し替える書き方が普通に使えるのは、このためです。

見える範囲も違います。NestJS は単位でプロバイダを囲い、exports で公開します。Spring の の識別子はの中で一意で、実測(Java のモジュール機能を使わない構成)では、別の でない実装クラスが宣言なしで注入されました。そのぶん、同じ型の Bean が2つあると、その型を1つだけ求める注入は絞り込めずに失敗します。

点線の付いた語はタップすると説明が出ます。用語集を開く →

コンストラクタの引数の型は、実行時にどこから読めるか
TypeScript のソースconstructor(repo: CatsRepository)
Java のソースOrderService(PaymentGateway gateway)
コンパイル
出力の JavaScriptconstructor(repo) — 型注釈は無い
設定を有効にすると design:paramtypes の1行が足される
.class引数の型がそのまま入っている
実行時に読む
書き足されたメタデータクラスはそのまま。インターフェースは Object
リフレクションインターフェースも型の名前で読める
コンテナが解決に使う鍵
NestJSクラス、または文字列・Symbol・抽象クラスのトークン
Springクラスまたはインターフェースの型
  • ・左の列は TypeScript 7.0.2 の旧式デコレータ(experimentalDecorators)での実測です。NestJS 自体は起動していません
  • ・NestJS の公式ドキュメント本文に design:paramtypes や emitDecoratorMetadata の語は見当たりませんでした(確認した7ページの範囲)。前者は NestJS のソース、後者は公式スターターの tsconfig.json にあります
  • ・Java でも、List<String> の String のような型引数はオブジェクトには残りません。残るのはクラスそのものと、宣言に書いた型です
  • ・どちらが優れているかではなく、言語が実行時に何を残すかの違いです

サンプルコード(フレームワーク環境が必要なため表示のみ)

// demo.ts — NestJS は入れず、TypeScript のコンパイラが何を書き出すかだけを見る
// tsconfig: "experimentalDecorators": true, "emitDecoratorMetadata": true
import "reflect-metadata";

// 中身が空のデコレータ。付いていることだけが意味を持つ
function Injectable(): ClassDecorator {
  return () => {};
}

class CatsRepository {}

interface Clock {
  now(): number;
}

@Injectable()
class WithClass {
  constructor(private readonly repo: CatsRepository) {} // クラス型の引数
}

@Injectable()
class WithInterface {
  constructor(private readonly clock: Clock) {} // インターフェース型の引数
}

class NoDecorator {
  constructor(private readonly repo: CatsRepository) {} // デコレータなし
}

const show = (c: Function) =>
  console.log(c.name.padEnd(14), Reflect.getMetadata("design:paramtypes", c));

show(WithClass);
show(WithInterface);
show(NoDecorator);

// TypeScript 7.0.2 / reflect-metadata 0.2.2 / Node 24.21.0 での出力
//
// $ node dist-on/demo.js   (emitDecoratorMetadata: true)
// WithClass      [ [class CatsRepository] ]
// WithInterface  [ [Function: Object] ]
// NoDecorator    undefined
//
// $ node dist-off/demo.js  (emitDecoratorMetadata: false)
// WithClass      undefined
// WithInterface  undefined
// NoDecorator    undefined

図やコードに出てくる用語(タップで説明)

サンプルコードで身につける(5本) →

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

公式ドキュメントで詳しく ↗