「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > TypeScript での Tsyringe と依存関係の注入

TypeScript での Tsyringe と依存関係の注入

2024 年 10 月 31 日公開
ブラウズ:375

TSyringe and Dependency Injection in TypeScript

私は NestJS のような大規模なフレームワークの大ファンではありません。私は、軽量な方法で自分で決めた構造で、自分のソフトウェアを好きなように構築できる自由がずっと好きでした。しかし、NestJS をテストするときに気に入ったのは、Dependency Injection です。

Dependency Injection (DI) は、クラスから依存関係の作成と管理の責任を取り除くことで、疎結合コードの開発を可能にする設計パターンです。このパターンは、保守可能、テスト可能、およびスケーラブルなアプリケーションを作成するために重要です。 TypeScript エコシステムでは、TSyringe は、このプロセスを簡素化する強力で軽量な依存関係注入コンテナーとして際立っています。

TSyringe は、TypeScript/JavaScript アプリケーション用の軽量の依存関係注入コンテナーです。 Microsoft によって GitHub (https://github.com/microsoft/tsyringe) で管理されており、デコレータを使用してコンストラクター インジェクションを実行します。次に、制御の反転コンテナを使用して、インスタンスまたは値と交換できるトークンに基づいて依存関係を保存します。

依存性の注入について理解する

TSyringe について説明する前に、依存関係注入とは何か、そしてそれがなぜ重要なのかを簡単に説明します。

依存関係の挿入は、オブジェクトが依存関係を自分で作成するのではなく、外部ソースから依存関係を受け取る手法です。このアプローチにはいくつかの利点があります:

  1. テスト容易性の向上: 単体テストで依存関係を簡単にモックしたりスタブしたりできます。
  2. モジュール性の向上: コンポーネントの独立性が高まり、簡単に交換または更新できます。
  3. コードの再利用性の向上: 依存関係をアプリケーションのさまざまな部分で共有できます。
  4. 保守性の向上: 依存関係を変更しても、依存コードへの影響は最小限に抑えられます。

Tシリンジのセットアップ

まず、TypeScript プロジェクトで TSyringe を設定しましょう:

npm install tsyringe reflect-metadata

tsconfig.json に次のオプションがあることを確認してください:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

アプリケーションのエントリ ポイントにリフレクト メタデータをインポートします:

import "reflect-metadata";

アプリケーションのエントリ ポイントは、たとえば Next.js 13 のルート レイアウトであるか、小規模な Express アプリケーションのメイン ファイルである可能性があります。

Tsyringe を使用した依存性注入の実装

導入部の例を使用して、TSyringe 砂糖を追加しましょう:

アダプターから始めましょう。

// @/adapters/userAdapter.ts
import { injectable } from "tsyringe"

@injectable()
class UserAdapter {
    constructor(...) {...}

    async fetchByUUID(uuid) {...}
}

@injectable() デコレータに注目してください?これは、このクラスが実行時に注入できることを TSyringe に伝えるためです。

つまり、私のサービスは作成したばかりのアダプターを使用しています。そのアダプターをサービスに挿入しましょう。

// @/core/user/user.service.ts
import { injectable, inject } from "tsyringe"
...

@injectable()
class UserService {
    constructor(@inject('UserAdapter') private readonly userAdapter: UserAdapter) {}

    async fetchByUUID(uuid: string) {
    ...
        const { data, error } = await this.userAdapter.fetchByUUID(uuid);
    ...
    }
}

ここでは、サービスがコマンド クラスに注入されるため、@injectable デコレータも使用しましたが、コンストラクタ パラメータにも @inject デコレータを追加しました。このデコレータは、実行時に userAdapter プロパティのトークン UserAdapter にインスタンスまたは値を与えるように TSyringe に指示します。

そして最後に重要なことですが、私のコアのルートであるコマンド クラス (誤ってユースケースと呼ばれることがよくあります)。

// @/core/user/user.commands.ts
import { inject } from "tsyringe"
...

@injectable()
class UserCommands {
    constructor(@inject('UserService') private readonly userService: UserService) {}

    async fetchByUUID(uuid) {
    ...
        const { data, error } = this.userService.fetchByUUID(uuid);
    ...
    }
}

この時点で、何を注入するのか、コンストラクターに何を注入するのかを TSyringe に伝えました。しかし、依存関係を保存するコンテナーはまだ作成されていません。これは 2 つの方法で行うことができます:

依存関係注入レジストリを使用してファイルを作成できます:

// @/core/user/user.dependencies.ts
import { container } from "tsyringe"
...

container.register("UserService", {useClass: UserService}) // associate the UserService with the token "UserService"
container.register("UserAdapter", {useClass: UserAdapter}) // associate the UserAdapter with the token "UserAdapter"

export { container }

ただし、@registry デコレーターも使用できます。

// @/core/user/user.commands.ts
import { inject, registry, injectable } from "tsyringe"
...

@injectable()
@registry([
    {
        token: 'UserService',
        useClass: UserService
    },
    {
        token: 'UserAdapter',
        useClass: UserAdapter
    },
])
export class UserCommands {
    constructor(@inject('UserService') private readonly userService: UserService) {}

    async fetchByUUID(uuid) {
    ...
        const { data, error } = this.userService.fetchByUUID(uuid);
    ...
    }
}

container.register("UserCommands", { useClass: UserCommands})

export { container }

どちらの方法にも長所と短所がありますが、結局のところ、それは好みの問題です。

コンテナに依存関係が埋め込まれたので、必要に応じてコンテナのresolveメソッドを使用して依存関係をコンテナから取得できます。

import { container, UserCommands } from "@/core/user/user.commands"

...
const userCommands = container.resolve("UserCommands")
await userCommands.fetchByUUID(uuid)
...

各クラスは別のクラスにのみ依存するため、この例は非常に単純ですが、サービスは多くのクラスに依存する可能性があるため、依存関係の注入はすべてを整理するのに非常に役立ちます。

でも待ってください!そんな私を放っておかないで!テストはどうですか?

Tシリンジによるテスト

インジェクションは、モックオブジェクトを依存関係に直接送信することでコードをテストするのにも役立ちます。コード例を見てみましょう:

import { container, UserCommands } from "@/core/user/user.commands"

describe("test ftw", () => {
    let userAdapterMock: UserAdapterMock
    let userCommands: UserCommands

    beforeEach(() => {
        userAdapterMock = new UserAdapter()
        container.registerInstance("UserAdapter", userAdapter)
        userCommands = container.resolve("UserCommands")
    });

    ...
});

UserAdapter トークンには、依存クラスに挿入されるモックが含まれています。

ベストプラクティスとヒント

  1. インターフェイスを使用する: 依存関係のインターフェイスを定義して、依存関係を簡単に交換およびテストできるようにします。この記事ではわかりやすくするためにインターフェイスを使用しませんでしたが、インターフェイスは命です。
  2. 循環依存関係を避ける: TSyringe.
  3. で問題が発生する可能性がある循環依存関係を回避するようにコードを構造化します。
  4. 名前付けにトークンを使用する: 注入トークンに文字列リテラルを使用する代わりに、定数トークンを作成します:

    export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
    
    
  5. スコープ付きコンテナ: Web アプリケーションでリクエスト スコープの依存関係にスコープ付きコンテナを使用します。

  6. DI を使いすぎないでください: すべてを注入する必要はありません。横断的な関心事や構成可能な依存関係には DI を使用します。

ここまで読んでいただいてありがとうございます。この記事が有益であることを願っています。依存関係の挿入とアーキテクチャ パターンを実装するときは、プロジェクトの特定のニーズを常に考慮することを忘れないでください。

いいねとコメントによるフィードバックが改善への最良の方法です。

コーディングを楽しんでください!

リリースステートメント この記事は次の場所に転載されています: https://dev.to/gdsources/tsyringe-and-dependency-injection-in-typescript-3i67?1 侵害がある場合は、[email protected] に連絡して削除してください。
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3