2021年9月11日 • ☕️ 3 min read

TypeScriptのDIライブラリの検証を続けていきたいです。今回はMicroSoft社が作ったTSyringeを使ってみようと思います。

※ DI(dependency inject依存性注入)の説明は割愛します。必要があればwikipediaをご覧ください。

シリーズ一覧

  1. TypeScriptのDI、その壱:InversifyJS
  2. TypeScriptのDI、その貮:TSyringe ← 今はここ
  3. TypeScriptのDI、その弎:TypeDI
  4. TypeScriptのDI、その肆:nestjs
  5. C# .NetFrameworkのDI

目的

C# .netと同じように、interfaceconstructorのDIが望ましいです。具体的なやり方と理由は最後のその5で説明します。

TSyringe

Microsoft社が公開した軽量DIライブラリとなります。InversifyJSと同じように、JavaScriptとTypeScript両方サポートしています。主にconstructor injection用途も言えます。使い方もシンプルで、ドキュメントサイトさえなくて、README.mdに全ての仕様関連がまとめられました。

https://github.com/microsoft/tsyringe

実装方法

interfaceと実装classの実装方法はInversifyJSとほぼ同じとなります。

  1. まずinterfaceと実装部分を定義し、実装部分にdecoratorを使ってDIできるclassを明記します。
ProjectService.ts
Copy
import { injectable } from 'tsyringe';

export interface IProjectService {
  getProject(id: string): Promise<Project>;
}

// singletonにしたい場合、binding時だけではなく、ここを@singleton()に書き換えてもOK
@injectable()export default class ProjectService implements IProjectService {
  async getProject(id: string): Promise<Project> {
    console.log(`getProject: ${id}`);
    return new Project(id, `project_name_${id}`);
  }
}

注意:デフォルトのinjectionはsingletonではなく、transientとなります。

  1. TypeScriptのinterfaceはC#と違って、reflectionでinterfaceからpropertiesなどを取れないため、interfaceでinjectできるように、bindingはtoken形式で行わなければいけません。まずtokenを定義しましょう。
types.ts
Copy
export const TYPES = {
  EmployeeService: Symbol.for('EmployeeService'),
  ProjectService: Symbol.for('ProjectService'),
}
  1. そして、inject先のclassのconstructorにDIします。

簡単にいうと、ここのtoken(TYPES.EmployeeService → Symbol)はキーとして、containerから実装classを特定し、instance化しています。

EmployeeService.ts
Copy
import { inject, injectable } from 'tsyringe';
import { IProjectService } from './ProjectService';

export interface IEmployeeService {
  getEmployee(id: number): Promise<Employee>;
}

// 他のclassにinjectできるようにProjectServiceと同じように`@injectable()`を追加する
@injectable()export default class EmployeeService implements IEmployeeService {
  public constructor(
    // `@inject(TYPES.ProjectService)`はtoken形式で`IProjectService`の実装classをinjectする
    @inject(TYPES.ProjectService) private _projectService: IProjectService,  ) {}

  async getEmployee(id: number): Promise<Employee> {
    console.log(`getEmployee: ${id}`);

    // inject成功!!使える!!
    const project = await this._projectService.getProject(`proj_id_${id + 1}`);

    return new Employee(
      id,
      `dummy_name_${id}`,
      project,
    );
  }
}
  1. interfaceと実装classのbindingを行います。

ステップ3でinjectしたIProjectServiceはinterfaceで、どの実装classを指しているか指示する必要があります。

tsyringe.config.js
Copy
import { container as tsyContainer, Lifecycle } from 'tsyringe';
import EmployeeService, { IEmployeeService } from './services/EmployeeService';
import ProjectService, { IProjectService } from './services/ProjectService';

// containerはclassではない
const container = tsyContainer.createChildContainer();
// `IEmployeeService`と`EmployeeService`のbindingを行い、`token`(キー)は`TYPES.EmployeeService`となる
// classのdecoratorでもsingletonの指定ができるが、bindingで行った方が柔軟性があると
container.register(TYPES.ProjectService, ProjectService, { lifecycle: Lifecycle.Singleton });container.register(TYPES.EmployeeService, EmployeeService, { lifecycle: Lifecycle.Singleton });
export { container };
  1. トップclassにtsyringe.config.jsをimportすれば完了となります。

※結果を確認するためにcontainerからEmployeeServiceととってきています。必須ステップではありません。

app.ts
Copy
// metadataとるためのpolyfill
import 'reflect-metadata';// tsyringeのbindingを行う設定をimportする。※. `container`は結果確認するためimportしている。```import './tsyringe.config';```だけでOK
import { container } from './tsyringe.config';import { IEmployeeService } from './services/EmployeeService';

const employeeService = container.get<IEmployeeService>(TYPES.EmployeeService);

async function main() {
  const employee = await employeeService.getEmployee(12);
  console.log(JSON.stringify(employee, null, 2));
}

void main();

感想

containerのbindingのところ以外、inversifyjsとほぼ同じです。もちろん、inversifyjs同じ問題を抱えています。軽くDIを使いたい場合、どちらでも簡単で素晴らしいライブラリと思われます。

サンプルプロジェクト。

https://github.com/thundermiracle/di-typescript/tree/master/projects/tsyringe


関連投稿

TypeScriptのDI、その肆:nestjs

2021年9月12日

TypeScriptのDI、その壱:InversifyJS

2021年9月9日

ThunderMiracle

Blog part of ThunderMiracle.com