TypeScriptのDIライブラリとしてtsyringeを使ってみました

TypeScriptDIDependency InjectiontsyringeSoftware ArchitectureDomain Driven Design

はじめに

現在、色々な個人開発を行っています。その中のひとつにSwitchbotとNature Remoを連携させたワークロードがあります(この話はまたどこかで)。 この連携ワークロードの開発はTypeScriptを利用しているのですが、ここでDIライブラリとしてMicrosoftが提供しているtsyringeを使っています。 今回は基本的な利用方法を記載していきたいと思います。

サンプルユースケース

基本的な利用方法は、上で軽くお伝えした連携ワークロードに出てくる簡単なユースケースをもとに記載できればと思います。 Switchbotのデバイスを表すDeviceエンティティを用意しています。 このデバイスの状態に応じて、Nature Remoを動作させるためのメッセージを送信するので、notifyというメソッドが備わっています。

デバイス

このエンティティを表すクラスが以下のコードです。 このクラスメソッドのnotifyはデータベースへデータ登録も行います。 そのため、このクラス自体にはデータベースを操作するためのオブジェクトをメンバーとして保持しますが、ここに依存性の注入Dependency Injection / DI)があります。 これを実施するためのライブラリとしてはtsyringeを使用します。

import { autoInjectable, inject } from 'tsyringe'
import { IDeviceDatabase } from './deviceDatabaseInterface'
import { FinishState } from '../../type/switchbot/finishState'

@autoInjectable()
export default class Device {
	readonly id: string
	readonly status: string
	readonly battery: number
	private readonly database?: IDeviceDatabase

	constructor(
		id: string,
		status: string,
		battery: number,
		@inject('IDeviceDatabase') database?: IDeviceDatabase,
	) {
		this.id = id
		this.status = status
		this.battery = battery
		this.database = database
	}

	public notify = async (): Promise<FinishState> => {
		// databaseに対してデータを投入する部分のみ抜粋
		await this.database.register()
	}
}

依存性を注入するクラスに対しては、@autoInjectableアノテーションを記述します。 一方、依存先に対しては、@injectアノテーションを利用します。 @inject('IDeviceDatabase')のように引数にはトークンと呼ばれる、依存性を示す識別子を指定します。

データベースとインターフェース

依存先であるIdeviceDatabaseインターフェースとその実装であるDeviceDynamoDBは以下のようなコードになります。 今回のデータベースはDynamoDBを利用しているので、実装としてはDynamoDBテーブルへのPutItemを記述しています。

import Device from './device'

export interface IDeviceDatabase {
	register: (device: Device, messageId: string) => Promise<void>
}
export default class DeviceDynamoDB implements IDeviceDatabase {
	private readonly client: DynamoDBDocumentClient

	constructor() {
		this.client = DynamoDBDocumentClient.from(new DynamoDBClient({}))
	}

	public register = async (device: Device, messageId: string): Promise<void> => {
		const command = new PutCommand(
			{
			TableName: `<dynamodb_table_name>`,
			Item: {
				Id: device.id,
				Status: device.status,
				Battery: device.battery,
				MessageId: messageId
			}
		})

		await this.client.send(command)
	}
}

インスタンス生成

デバイスのインスタンス生成に際しては、まずIDeviceDatabaseとしてDeviceDynamoDBの依存性を注入するためにcontainer.register()を利用します。 これの第一引数には、先程の@injectアノテーションで指定したトークンと同じ値を記述します。

import { container } from 'tsyringe'
import DeviceDynamoDB from './repogitory/dynamodb/device'
import Device from './entity/switchbot/device'

const main = async () => {
	container.register('IDeviceDatabase', {
		useClass: DeviceDynamoDB
	})

	const device = new Device('sampleDeviceId', 'sampleDeviceStatus', 100)

	await device.notify()
}

main()

さいごに

いかがでしたでしょうか。tsyringeを利用することで比較的簡単にDIを実現することができました。 今回は備忘のための記事なので、サンプルコードを主体にしてブログを記載しましたが、誰かの役に立てれば幸いです。