Maison  >  Article  >  interface Web  >  TSyringe et injection de dépendances dans TypeScript

TSyringe et injection de dépendances dans TypeScript

Patricia Arquette
Patricia Arquetteoriginal
2024-09-25 19:21:42592parcourir

TSyringe and Dependency Injection in TypeScript

Je ne suis pas un grand fan des grands frameworks comme NestJS ; J'ai toujours aimé la liberté de créer mes logiciels comme je le souhaite, avec la structure que je décide de manière légère. Mais quelque chose que j'ai aimé lors du test de NestJS, c'est l'injection de dépendances.

Dependency Injection (DI) est un modèle de conception qui nous permet de développer du code faiblement couplé en supprimant la responsabilité de créer et de gérer les dépendances de nos classes. Ce modèle est crucial pour écrire des applications maintenables, testables et évolutives. Dans l'écosystème TypeScript, TSyringe se distingue comme un conteneur d'injection de dépendances puissant et léger qui simplifie ce processus.

TSyringe est un conteneur léger d'injection de dépendances pour les applications TypeScript/JavaScript. Maintenu par Microsoft sur leur GitHub (https://github.com/microsoft/tsyringe), il utilise des décorateurs pour effectuer l'injection de constructeur. Ensuite, il utilise un conteneur d'inversion de contrôle pour stocker les dépendances en fonction d'un jeton que vous pouvez échanger contre une instance ou une valeur.

Comprendre l'injection de dépendances

Avant de plonger dans TSyringe, explorons brièvement ce qu'est l'injection de dépendance et pourquoi elle est importante.

L'injection de dépendances est une technique dans laquelle un objet reçoit ses dépendances de sources externes plutôt que de les créer lui-même. Cette approche offre plusieurs avantages :

  1. Testabilité améliorée : les dépendances peuvent être facilement simulées ou écrasées dans les tests unitaires.
  2. Modularité accrue : les composants sont plus indépendants et peuvent être facilement remplacés ou mis à jour.
  3. Meilleure réutilisabilité du code : les dépendances peuvent être partagées entre différentes parties de l'application.
  4. Maintenabilité améliorée : les modifications apportées aux dépendances ont un impact minimal sur le code dépendant.

Configuration de TSyringe

Tout d'abord, configurons TSyringe dans votre projet TypeScript :

npm install tsyringe reflect-metadata

Dans votre tsconfig.json, assurez-vous d'avoir les options suivantes :

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

Importez les métadonnées réfléchies au point d'entrée de votre application :

import "reflect-metadata";

Le point d'entrée de votre application est, par exemple, la mise en page racine sur Next.js 13+, ou il peut être le fichier principal dans une petite application Express.

Implémentation de l'injection de dépendances avec TSyringe

Reprenons l'exemple de l'introduction et ajoutons le sucre TSeringue :

Commençons par l'adaptateur.

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

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

    async fetchByUUID(uuid) {...}
}

Remarquez le décorateur @injectable() ? C'est pour dire à TSyringe que cette classe peut être injectée au moment de l'exécution.

Mon service utilise donc l'adaptateur que nous venons de créer. Injectons cet adaptateur dans mon service.

// @/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);
    ...
    }
}

Ici, j'ai également utilisé le décorateur @injectable car le Service va être injecté dans ma classe de commande, mais j'ai également ajouté le décorateur @inject dans les paramètres du constructeur. Ce décorateur indique à TSyringe de donner l'instance ou la valeur qu'elle a pour le jeton UserAdapter pour la propriété userAdapter au moment de l'exécution.

Et enfin, la racine de mon Core : la classe de commande (souvent appelée à tort usecase).

// @/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);
    ...
    }
}

À ce stade, nous avons indiqué à TSyringe ce qui va être injecté et quoi injecter dans le constructeur. Mais nous n'avons toujours pas réalisé nos conteneurs pour stocker les dépendances. Nous pouvons le faire de deux manières :

Nous pouvons créer un fichier avec notre registre d'injection de dépendances :

// @/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 }

Mais on peut aussi utiliser le décorateur @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 }

Les deux méthodes ont des avantages et des inconvénients, mais en fin de compte, c'est une question de goût.

Maintenant que notre conteneur est rempli de nos dépendances, nous pouvons les récupérer du conteneur selon nos besoins en utilisant la méthode de résolution du conteneur.

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

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

Cet exemple est assez simple car chaque classe ne dépend que d'une autre, mais nos services pourraient dépendre de plusieurs, et l'injection de dépendances aiderait vraiment à garder tout en ordre.

Mais attendez ! Ne me laisse pas comme ça ! Et les tests ?

Test avec TSyringe

Nos injections peuvent également nous aider à tester notre code en envoyant des objets fictifs directement dans nos dépendances. Voyons un exemple de code :

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

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

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

    ...
});

Maintenant, le jeton UserAdapter contient une simulation qui sera injectée dans les classes dépendantes.

Meilleures pratiques et conseils

  1. Utiliser des interfaces : définissez des interfaces pour vos dépendances afin de les rendre facilement échangeables et testables. Je n'ai pas utilisé d'interfaces par souci de simplicité dans cet article, mais les interfaces c'est la vie.
  2. Évitez les dépendances circulaires : structurez votre code pour éviter les dépendances circulaires, qui peuvent causer des problèmes avec TSyringe.
  3. Utiliser des jetons pour nommer : au lieu d'utiliser des chaînes littérales pour les jetons d'injection, créez des jetons constants :

    export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
    
    
  4. Scoped containers: Use scoped containers for request-scoped dependencies in web applications.

  5. Don't overuse DI: Not everything needs to be injected. Use DI for cross-cutting concerns and configurable dependencies.

If you've come this far, I want to say thank you for reading. I hope you found this article instructive. Remember to always consider the specific needs of your project when implementing dependency injection and architectural patterns.

Likes and comment feedback are the best ways to improve.

Happy coding!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn