TypeScriptのDIライブラリの検証を続けていきたいです。今回はTypeDIを使ってみようと思います。
※ DI(dependency inject依存性注入)の説明は割愛します。必要があればwikipediaをご覧ください。
シリーズ一覧
- TypeScriptのDI、その壱:InversifyJS
- TypeScriptのDI、その貮:TSyringe
- TypeScriptのDI、その弎:TypeDI ←今はここ
- TypeScriptのDI、その肆:nestjs
- C# .NetFrameworkのDI
目的
C# .netと同じように、interface
でconstructor
のDIが望ましいです。具体的なやり方と理由は最後のその5で説明します。
TypeDI
decorator推進するコミュニティが作ったDIライブラリとなります。InversifyJS並みの古さとなります。他のDIライブラリと同じように、JavaScriptとTypeScript両方サポートしています。classベースのconstructor injectionはtoken
の定義が不要となり、とにかく簡単です。
⚠️⚠️⚠️:ただ、バージョンは1に達していなく、ドキュメントサイトも中身のないページいくつがあり、さらにREADME.mdに載せているサンプル自体が1年以上間違っているそうで、採用する時要注意です。
https://github.com/typestack/typedi
実装方法
interfaceと実装classの実装方法はInversifyJSとほぼ同じとなります。
- まずinterfaceと実装部分を定義し、実装部分に
decorator
を使ってDIできるclassを明記します。
import { Service } from 'typedi';
export interface IProjectService {
getProject(id: string): Promise<Project>;
}
@Service()export default class ProjectService implements IProjectService {
async getProject(id: string): Promise<Project> {
console.log(`getProject: ${id}`);
return new Project(id, `project_name_${id}`);
}
}
- TypeScriptのinterfaceはC#と違って、reflectionでinterfaceからpropertiesなどを取れないため、interfaceでinjectできるように、bindingはtoken形式で行わなければいけません。まず
token
を定義しましょう。
// 残念ながら、TypeDIがSymbolサポートしていないため、文字列を使用する
export const TYPES_STRING = {
EmployeeService: 'EmployeeService',
ProjectService: 'ProjectService',
}
- そして、inject先のclassのconstructorにDIします。
簡単にいうと、ここのtoken
(TYPES_STRING.EmployeeService → String)はキーとして、containerから実装classを特定し、instance化しています。
import { Inject, Service } from 'typedi';
import { IProjectService } from './ProjectService';
export interface IEmployeeService {
getEmployee(id: number): Promise<Employee>;
}
// 他のclassにinjectできるようにProjectServiceと同じように`@Service()`を追加する
@Service()export default class EmployeeService implements IEmployeeService {
public constructor(
// `@Inject(TYPES.ProjectService)`はtoken形式で`IProjectService`の実装classをinjectする
@Inject(TYPES_STRING.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,
);
}
}
- interfaceと実装classのbindingを行います。
ステップ3でinjectしたIProjectService
はinterfaceで、どの実装classを指しているか指示する必要があります。
import { Container } from 'typedi';
import ProjectService from './services/ProjectService';
// @Injectを使う部分のみbindingを設定すればいい、classベースのconstructor injectionは自動で行う
Container.set(TYPES_STRING.ProjectService, new ProjectService());
- トップclassに
typedi.config.js
をimportすれば完了となります。
// metadataとるためのpolyfill
import 'reflect-metadata';
import './typedi.config';
import { Container } from 'typedi';
import EmployeeService from './services/EmployeeService';
// bindingしなくても、@Service()でdecorate済みのclassなら自動bindingしてくれる
const employeeService = Container.get(EmployeeService);
async function main() {
const employee = await employeeService.getEmployee(12);
console.log(JSON.stringify(employee, null, 2));
}
void main();
感想
TypeScriptの各DIライブラリと同じ問題を抱えている軽量なDIライブラリとなります。
明記的にbindingしなくてもcontainerからinstance化できるのが楽だが、自由度が高すぎると思います。ドキュメントの質とコミュニティの活発度を考える(v0.8.0 → v0.9.0 2年ほどかかった)と、上位互換のTSyringe
或いはInversifyJS
を使った方がいいかもしれません。
サンプルプロジェクト。
https://github.com/thundermiracle/di-typescript/tree/master/projects/typedi-token