Dependency Injection Container für JavaScript (Node.js) — Awilix
In der modernen Node.js-Entwicklung rücken saubere Architektur und Testbarkeit des Codes in den Vordergrund. Eines der wichtigsten Werkzeuge zur Erreichung dieser Ziele ist die Abhängigkeitsinjektion (Dependency Injection, DI). Die Bibliothek Awilix bietet einen leistungsstarken, performanten und flexiblen DI-Container für JavaScript und TypeScript, der sich ideal für serverseitige Anwendungen unter Node.js eignet. In diesem Artikel werden wir im Detail besprechen, wie Sie Awilix zur Verwaltung von Abhängigkeiten, Verbesserung der Modularität und Vereinfachung des Testens einsetzen können.
Was ist Dependency Injection und wozu dient ein Container?
Dependency Injection ist ein Entwurfsmuster, bei dem ein Objekt seine Abhängigkeiten von außen erhält, anstatt sie selbst zu erstellen. Dies realisiert das Prinzip der Inversion of Control (IoC). Ein Container ist ein Werkzeug, das den Prozess der Erstellung und Bereitstellung dieser Abhängigkeiten automatisiert. Anstatt manuell Instanzen von Klassen zu erzeugen und aneinander zu übergeben, beschreiben Sie die Bauregeln im Container, und dieser löst selbstständig alle Abhängigkeitsketten auf.
Die wichtigsten Vorteile der Verwendung eines DI-Containers:
- Reduzierte Kopplung des Codes: Module hängen von Abstraktionen ab, nicht von konkreten Implementierungen.
- Vereinfachtes Testen: Reale Dienste können während Unit-Tests leicht durch Mocks ersetzt werden.
- Zentrale Verwaltung: Die gesamte Abhängigkeitskonfiguration befindet sich an einem Ort, was Refactoring vereinfacht.
- Lebenszyklusverwaltung: Der Container kann steuern, wie lange ein Objekt lebt – eine Instanz für die gesamte Anwendung (Singleton) oder eine neue bei jeder Anfrage.
Installation von Awilix
Installieren Sie die Bibliothek zunächst über npm:
npm install awilixAwilix hat keine externen Abhängigkeiten und funktioniert sowohl mit CommonJS- als auch mit ESM-Modulen.
Grundlegende Konzepte von Awilix
Awilix basiert auf mehreren Schlüsselkonzepten:
- Container: Das Hauptobjekt, das Abhängigkeiten speichert und bereitstellt.
- Registration: Der Prozess der Verknüpfung eines Namens (Tokens) mit einer Factory, einer Klasse oder einem Wert.
- Resolver: Ein Objekt, das definiert, wie eine Abhängigkeit genau erstellt oder bezogen wird. Zum Beispiel
asClass(),asFunction(),asValue(). - Lifetime (Lebensdauer): Gibt an, wie oft ein Objekt erstellt wird:
ScopedLifeTime(einmal pro Scope),SingletonLifeTime(einmal insgesamt) oderTransientLifeTime(jedes Mal neu).
Anwendungsbeispiel: Dienst für die Benutzerverwaltung
Betrachten wir ein einfaches Beispiel. Angenommen, wir haben eine Datenbank, ein Benutzer-Repository und einen Dienst für die Geschäftslogik.
1. Klassen definieren
// database.jsclass Database { constructor() { this.connection = 'Connected to DB'; } query(sql) { return `Result for: ${sql}`; }}
// userRepository.jsclass UserRepository { constructor({ database }) { this.db = database; } findById(id) { return this.db.query(`SELECT * FROM users WHERE id = ${id}`); }}
// userService.jsclass UserService { constructor({ userRepository }) { this.repo = userRepository; } getUser(id) { return this.repo.findById(id); }}Beachten Sie die Konstruktoren: Sie nehmen ein Objekt mit Abhängigkeiten entgegen. Dies ist eine gängige Praxis bei Awilix.
2. Container konfigurieren
const { createContainer, asClass, asValue, Lifetime } = require('awilix');
const container = createContainer();
// Registrieren wir die Auch in der Bibliothek
Arbeit mit Daten in C# (.NET): Überblick über Bibliotheken und integrierte Mittel
Arbeiten mit CSV in JavaScript (Node.js): Bibliotheken, Parsing und Datengenerierung
E-Mail-Versand mit C# (.NET): Bibliothek System.Net.Mail
Arbeiten mit YAML in Rust: Die Bibliothek serde_yaml