0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

サクッとTypeScriptでDIしてみる

Posted at

はじめに

以前はよくSpringBootを扱っていました。
最近はJavaにふれることがあまりありません。

Java,SpringBootでレイヤードアーキテクチャ、クリーンアーキテクチャを採用した際に、DIはとても便利でした。

そんなこともあり、かなり前ですが、以下のような記事を書いています。

今回は、TypeScriptで便利にDIできそうなライブラリを利用して、その使い勝手を見てみました。

ドキュメントを見ればわかることですが、サクッと実感できることを目指します。

この記事のこと

以下を利用します。

とても簡単に導入でき、DIを実現できました。

前提

DIとはいえ、アプリケーションがないと面白くないので、Honoを用いたAPIを実装し、そこにDIを活用してみます。

準備

Honoアプリケーション

これについては割愛します。

以下を参考に。

サンプルに合わせて以下から始めます。

import { Hono } from 'hono'

const app = new Hono()

app.get('/api/hello', (c) => {
  return c.json({
    ok: true,
    message: 'Hello Hono!',
  })
})

export default app

tsyringeのインストール

以下でインストールしましょう。(npmの場合)

npm install --save tsyringe

また、以下とのことで、サンプルに合わせて「reflect-metadata」も導入します。

Add a polyfill for the Reflect API (examples below use reflect-metadata). You can use:

npm install reflect-metadata

DIを使うまえにimportが必要です。

The Reflect polyfill import should only be added once, and before DI is used:

// main.ts
import "reflect-metadata";

いろいろやってみる

準備

こんな適当なclassから始めます。

lass TestClass {
  constructor() {
    console.log('TestClass のコンストラクタです');
  }
  greet() {
    return 'Hello from TestClass!'
  }
}

DIコンテナに登録

以下の通り、@injectableのアノテーションを付与すると、DIコンテナに登録されます。

import {injectable} from "tsyringe";

@injectable()
class TestClass {
  constructor() {
    console.log('TestClass のコンストラクタです');
  }
  greet() {
    return 'Hello from TestClass!'
  }
}

ちなみに起動時に、コンストラクタのconsole.logが出力されます。インスタンスが生成されて、コンストラクタが実行されるようです。(当然っちゃ当然)

取り出す

以下で取り出せます。

一番やりたい、classへのDIはもうちょっと後ろで。

import {container} from "tsyringe";

const testInstance = container.resolve(TestClass);

シングルトン

そのまんま@singletonです。

@singleton()
class TestClass2 {
  constructor() {
    console.log('TestClass2 のコンストラクタです');
  }

  greet() {
    return 'Hello from TestClass2!'
  }
}

@injectableだとシングルトンにならず、取得の都度インスタンスが生成されますが、@singletonだと、一度のみ流れます。

ClassにDI

この辺からやりたいことですね。

こんなinterfaceがあるとして。


// interface
interface InjectSample {
  greet: () => string;
}

// 実装
@injectable()
class InjectSampleImpl implements InjectSample {
  greet() {
    return 'Hello from InjectSampleImpl!';
  }
}

DIコンテナへは以下のように登録し。

container.register<InjectSample>("InjectSample", {
  useClass: InjectSampleImpl
});

injectする側のclassはこのように実装します。

@injectable()
class Foo {
  constructor(@inject("InjectSample") private injectSample: InjectSample) {
    console.log(injectSample.greet());
  }
}

簡単にDIできますね。便利。

ちょっとだけオブジェクト指向と依存性逆転(実装は適当です)

何も考えないと

こんなリポジトリがあり。

class SampleRepository {
  findAll() {
    return [
      1, 2, 3
    ];
  }
}

usecaseでリポジトリを呼び出し。

class SampleUsecase {
  sampleRepository: SampleRepository;
  constructor(sampleRepository: SampleRepository) {
    this.sampleRepository = sampleRepository;
  }

  execute() {
    return this.sampleRepository.findAll();
  }
}

使う時は2段階でインスタンスを作る必要がでちゃう。

  const sampleRepository = new SampleRepository();
  const sampleUsecase = new SampleUsecase(sampleRepository);

API(など)を実装すると。

こんな感じで、自前で依存関係を準備してあげる必要がある。

app.get("/api/hello2", (c) => {
  const sampleRepository = container.resolve(SampleRepository);
  const useCase = new SampleUsecase(sampleRepository);
  return c.json({
    ok: true,
    data: useCase.execute(),
  });
});

DIを使うと。

interfaceを用意。

interface ISampleUsecase {
  execute: () => number[];
}

interface ISampleRepository {
  findAll: () => number[];
}

リポジトリを実装し。

class SampleRepository2 implements ISampleRepository {
  findAll() {
    return [
      4, 5, 6
    ];
  }
}

DIコンテナの登録し。

container.register<ISampleRepository>("SampleRepository", {
  useClass: SampleRepository2
});

以下のように実装する。

@injectable()
class SampleUsecase2 implements ISampleUsecase {
  sampleRepository: ISampleRepository;
  constructor(@inject("SampleRepository") sampleRepository: ISampleRepository) {
    this.sampleRepository = sampleRepository;
  }

  execute() {
    return this.sampleRepository.findAll();
  }
}

APIが以下のような実装になり、依存関係の解決をDIにお任せすることができる。

app.get("/api/hello3", (c) => {
  const sampleUsecase2 = container.resolve(SampleUsecase2);
  return c.json({
    ok: true,
    data: sampleUsecase2.execute(),
  });
});

SampleUsecase2のインスタンスができた際に、

constructor(@inject("SampleRepository") sampleRepository: ISampleRepository)

でリポジトリが注入されます。

おわりに

やってみた系なので、あまり結論もありませんが。

SpringでできるようなDIも、ある程度便利にTypeScript環境でも行えますね。

TypeScriptにDIを持ち込んで複雑怪奇にするか、という論点はありそうですが。

同じようなことをやってみたい方のご参考になれば幸いです。

以上です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?