私は NestJS のような大規模なフレームワークの大ファンではありません。私は、軽量な方法で自分で決めた構造で、自分のソフトウェアを好きなように構築できる自由がずっと好きでした。しかし、NestJS をテストするときに気に入ったのは、Dependency Injection です。
Dependency Injection (DI) は、クラスから依存関係の作成と管理の責任を取り除くことで、疎結合コードの開発を可能にする設計パターンです。このパターンは、保守可能、テスト可能、およびスケーラブルなアプリケーションを作成するために重要です。 TypeScript エコシステムでは、TSyringe は、このプロセスを簡素化する強力で軽量な依存関係注入コンテナーとして際立っています。
TSyringe は、TypeScript/JavaScript アプリケーション用の軽量の依存関係注入コンテナーです。 Microsoft によって GitHub (https://github.com/microsoft/tsyringe) で管理されており、デコレータを使用してコンストラクター インジェクションを実行します。次に、制御の反転コンテナを使用して、インスタンスまたは値と交換できるトークンに基づいて依存関係を保存します。
TSyringe について説明する前に、依存関係注入とは何か、そしてそれがなぜ重要なのかを簡単に説明します。
依存関係の挿入は、オブジェクトが依存関係を自分で作成するのではなく、外部ソースから依存関係を受け取る手法です。このアプローチにはいくつかの利点があります:
まず、TypeScript プロジェクトで TSyringe を設定しましょう:
npm install tsyringe reflect-metadata
tsconfig.json に次のオプションがあることを確認してください:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
アプリケーションのエントリ ポイントにリフレクト メタデータをインポートします:
import "reflect-metadata";
アプリケーションのエントリ ポイントは、たとえば Next.js 13 のルート レイアウトであるか、小規模な Express アプリケーションのメイン ファイルである可能性があります。
導入部の例を使用して、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) ...
各クラスは別のクラスにのみ依存するため、この例は非常に単純ですが、サービスは多くのクラスに依存する可能性があるため、依存関係の注入はすべてを整理するのに非常に役立ちます。
でも待ってください!そんな私を放っておかないで!テストはどうですか?
インジェクションは、モックオブジェクトを依存関係に直接送信することでコードをテストするのにも役立ちます。コード例を見てみましょう:
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 トークンには、依存クラスに挿入されるモックが含まれています。
名前付けにトークンを使用する: 注入トークンに文字列リテラルを使用する代わりに、定数トークンを作成します:
export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
スコープ付きコンテナ: Web アプリケーションでリクエスト スコープの依存関係にスコープ付きコンテナを使用します。
DI を使いすぎないでください: すべてを注入する必要はありません。横断的な関心事や構成可能な依存関係には DI を使用します。
ここまで読んでいただいてありがとうございます。この記事が有益であることを願っています。依存関係の挿入とアーキテクチャ パターンを実装するときは、プロジェクトの特定のニーズを常に考慮することを忘れないでください。
いいねとコメントによるフィードバックが改善への最良の方法です。
コーディングを楽しんでください!
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3