„Wenn ein Arbeiter seine Arbeit gut machen will, muss er zuerst seine Werkzeuge schärfen.“ – Konfuzius, „Die Gespräche des Konfuzius. Lu Linggong“
Titelseite > Programmierung > TSyringe und Abhängigkeitsinjektion in TypeScript

TSyringe und Abhängigkeitsinjektion in TypeScript

Veröffentlicht am 31.10.2024
Durchsuche:329

TSyringe and Dependency Injection in TypeScript

Ich bin kein großer Fan von großen Frameworks wie NestJS; Mir gefiel schon immer die Freiheit, meine Software so zu erstellen, wie ich es möchte, mit einer Struktur, die ich auf einfache Weise entscheide. Aber etwas, das mir beim Testen von NestJS gefallen hat, war die Abhängigkeitsinjektion.

Dependency Injection (DI) ist ein Entwurfsmuster, das es uns ermöglicht, lose gekoppelten Code zu entwickeln, indem wir die Verantwortung für die Erstellung und Verwaltung von Abhängigkeiten aus unseren Klassen entfernen. Dieses Muster ist entscheidend für das Schreiben wartbarer, testbarer und skalierbarer Anwendungen. Im TypeScript-Ökosystem zeichnet sich TSyringe als leistungsstarker und leichter Dependency-Injection-Container aus, der diesen Prozess vereinfacht.

TSyringe ist ein leichter Abhängigkeitsinjektionscontainer für TypeScript/JavaScript-Anwendungen. Es wird von Microsoft auf GitHub (https://github.com/microsoft/tsyringe) verwaltet und verwendet Dekoratoren für die Konstruktor-Injection. Anschließend wird ein Inversion of Control-Container verwendet, um die Abhängigkeiten basierend auf einem Token zu speichern, das Sie gegen eine Instanz oder einen Wert austauschen können.

Abhängigkeitsinjektion verstehen

Bevor wir uns mit TSyringe befassen, wollen wir kurz untersuchen, was Abhängigkeitsinjektion ist und warum sie wichtig ist.

Abhängigkeitsinjektion ist eine Technik, bei der ein Objekt seine Abhängigkeiten von externen Quellen erhält, anstatt sie selbst zu erstellen. Dieser Ansatz bietet mehrere Vorteile:

  1. Verbesserte Testbarkeit: Abhängigkeiten können in Unit-Tests leicht verspottet oder gelöscht werden.
  2. Erhöhte Modularität: Komponenten sind unabhängiger und können einfach ausgetauscht oder aktualisiert werden.
  3. Bessere Wiederverwendbarkeit des Codes: Abhängigkeiten können über verschiedene Teile der Anwendung hinweg geteilt werden.
  4. Verbesserte Wartbarkeit: Änderungen an Abhängigkeiten haben minimale Auswirkungen auf abhängigen Code.

TSyringe einrichten

Lassen Sie uns zunächst TSyringe in Ihrem TypeScript-Projekt einrichten:

npm install tsyringe reflect-metadata

Stellen Sie sicher, dass Sie in Ihrer tsconfig.json die folgenden Optionen haben:

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

Reflect-Metadaten am Einstiegspunkt Ihrer Anwendung importieren:

import "reflect-metadata";

Der Einstiegspunkt Ihrer Anwendung ist beispielsweise das Root-Layout auf Next.js 13 oder es kann die Hauptdatei in einer kleinen Express-Anwendung sein.

Implementierung der Abhängigkeitsinjektion mit TSyringe

Nehmen wir das Beispiel aus der Einleitung und fügen den TSyringe-Zucker hinzu:

Beginnen wir mit dem Adapter.

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

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

    async fetchByUUID(uuid) {...}
}

Beachten Sie den @injectable()-Decorator? Es soll TSyringe mitteilen, dass diese Klasse zur Laufzeit injiziert werden kann.

Mein Dienst verwendet also den Adapter, den wir gerade erstellt haben. Lassen Sie uns diesen Adapter in meinen Dienst einbinden.

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

Hier habe ich auch den @injectable-Dekorator verwendet, da der Dienst in meine Befehlsklasse eingefügt werden soll, aber ich habe auch den @inject-Dekorator in den Konstruktorparametern hinzugefügt. Dieser Dekorator weist TSyringe an, die Instanz oder den Wert, den sie hat, für das Token UserAdapter für die userAdapter-Eigenschaft zur Laufzeit anzugeben.

Und zu guter Letzt die Wurzel meines Kerns: die Befehlsklasse (oft fälschlicherweise Usecase genannt).

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

An diesem Punkt haben wir TSyringe mitgeteilt, was injiziert werden soll und was im Konstruktor injiziert werden soll. Aber wir haben unsere Container noch nicht zum Speichern der Abhängigkeiten erstellt. Wir können das auf zwei Arten tun:

Wir können eine Datei mit unserer Abhängigkeitsinjektionsregistrierung erstellen:

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

Wir können aber auch den @registry-Decorator verwenden.

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

Beide Methoden haben Vor- und Nachteile, aber letztendlich ist es Geschmackssache.

Da unser Container nun mit unseren Abhängigkeiten gefüllt ist, können wir sie nach Bedarf aus dem Container abrufen, indem wir die Auflösungsmethode des Containers verwenden.

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

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

Dieses Beispiel ist ziemlich einfach, da jede Klasse nur von einer anderen abhängt, unsere Dienste jedoch von vielen abhängen könnten und die Abhängigkeitsinjektion wirklich dazu beitragen würde, alles aufgeräumt zu halten.

Aber warte! Lass mich nicht so zurück! Wie wäre es mit den Tests?

Testen mit TSyringe

Unsere Injektionen können uns auch beim Testen unseres Codes helfen, indem sie Scheinobjekte direkt in unsere Abhängigkeiten senden. Sehen wir uns ein Codebeispiel an:

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")
    });

    ...
});

Jetzt enthält das UserAdapter-Token einen Schein, der in die abhängigen Klassen eingefügt wird.

Best Practices und Tipps

  1. Schnittstellen verwenden: Definieren Sie Schnittstellen für Ihre Abhängigkeiten, um sie leicht austauschbar und testbar zu machen. Der Einfachheit halber habe ich in diesem Artikel keine Schnittstellen verwendet, aber Schnittstellen sind Leben.
  2. Vermeiden Sie zirkuläre Abhängigkeiten: Strukturieren Sie Ihren Code, um zirkuläre Abhängigkeiten zu vermeiden, die Probleme mit TSyringe verursachen können.
  3. Token zur Benennung verwenden: Anstatt String-Literale für Injektionstoken zu verwenden, erstellen Sie konstante Token:

    export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
    
    
  4. Bereichsbezogene Container: Verwenden Sie Bereichscontainer für anforderungsbezogene Abhängigkeiten in Webanwendungen.

  5. DI nicht überbeanspruchen: Nicht alles muss injiziert werden. Nutzen Sie DI für Querschnittsthemen und konfigurierbare Abhängigkeiten.

Wenn Sie bis hierher gekommen sind, möchte ich mich für das Lesen bedanken. Ich hoffe, Sie fanden diesen Artikel lehrreich. Denken Sie daran, bei der Implementierung von Abhängigkeitsinjektion und Architekturmustern immer die spezifischen Anforderungen Ihres Projekts zu berücksichtigen.

Likes und Kommentar-Feedback sind die besten Möglichkeiten, sich zu verbessern.

Viel Spaß beim Codieren!

Freigabeerklärung Dieser Artikel ist abgedruckt unter: https://dev.to/gdsources/tsyringe-and-dependency-injection-in-typescript-3i67?1 Bei Verstößen wenden Sie sich bitte an [email protected], um ihn zu löschen
Neuestes Tutorial Mehr>

Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.

Copyright© 2022 湘ICP备2022001581号-3