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で説明します。
nestjs
非常に有名なnodejsのprogressiveフレームワークとなります。DI機能を内蔵しているフレームワークとなり、DIライブラリとして使うとデカすぎる気がしますが、nestjsのDI設計思想を確認する価値が十分あると思います。
https://github.com/nestjs/nest
実装方法
interfaceと実装classの実装方法はInversifyJSとほぼ同じとなります。(小文字大文字ぐらい)
- まずinterfaceと実装部分を定義し、実装部分に
decorator
を使ってDIできるclassを明記します。
import { Injectable } from '@nestjs/common';
export interface IProjectService {
getProject(id: string): Promise<Project>;
}
@Injectable()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
を定義しましょう。
export const TYPES = {
EmployeeService: Symbol.for('EmployeeService'),
ProjectService: Symbol.for('ProjectService'),
}
- そして、inject先のclassのconstructorにDIします。
簡単にいうと、ここのtoken
(TYPES.EmployeeService → Symbol)はキーとして、containerから実装classを特定し、instance化しています。
import { Injectable, Inject } from '@nestjs/common';
import { IProjectService } from './ProjectService';
export interface IEmployeeService {
getEmployee(id: number): Promise<Employee>;
}
@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,
);
}
}
- interfaceと実装classのbindingを行います。
ステップ3でinjectしたIProjectService
はinterfaceで、どの実装classを指しているか指示する必要があります。
import { Module } from '@nestjs/common';
import ProjectService from './services/ProjectService';
@Module({
providers: [
{
provide: TYPES.ProjectService,
useClass: ProjectService,
},
{
provide: TYPES.EmployeeService,
useClass: EmployeeService,
},
],
})
export class AppModule {}
- トップclassに
typedi.config.js
をimportすれば完了となります。
// metadataとるためのpolyfill
import 'reflect-metadata';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './nestjs.config';
import { IEmployeeService } from './services/EmployeeService';
async function main() {
// standaloneAPPを作成して立ち上げる
const appContext = await NestFactory.createApplicationContext(AppModule); const employeeService = appContext.get<IEmployeeService>(TYPES.EmployeeService);
const employee = await employeeService.getEmployee(12);
console.log(JSON.stringify(employee, null, 2));
await appContext.close();
}
void main();
感想
TypeScriptの各DIライブラリと同じ問題を抱えているフレームワークとなります。ライブラリとしてオーバーヘッドがでかすぎでお勧めでしないが、nestjs
を導入したらぜひ使いたいですね。
サンプルプロジェクト。
https://github.com/thundermiracle/di-typescript/tree/master/projects/nestjs-token