fontcolor_theme
Deepkit Injector

Provider

Es gibt mehrere Möglichkeiten, Abhängigkeiten im Dependency Injection Container bereitzustellen. Die einfachste Variante ist einfach die Angabe einer Class. Dies ist auch als kurzer ClassProvider bekannt.

new App({
    providers: [UserRepository]
});

Dies stellt einen speziellen Provider dar, da nur die Class angegeben wird. Alle anderen Provider müssen als Objektliteral angegeben werden.

Standardmäßig sind alle Provider als Singletons markiert, sodass zu jedem Zeitpunkt nur eine Instanz existiert. Um bei jeder Auflösung eines Providers eine neue Instanz zu erstellen, kann die Option transient verwendet werden. Dadurch werden Classes jedes Mal neu instanziert oder Factories jedes Mal ausgeführt.

new App({
    providers: [{ provide: UserRepository, transient: true }]
});

ClassProvider

Neben dem kurzen ClassProvider gibt es auch den regulären ClassProvider, der als Objektliteral statt als Class angegeben wird.

new App({
    providers: [{ provide: UserRepository, useClass: UserRepository }]
});

Dies entspricht diesen beiden:

new App({
    providers: [{ provide: UserRepository }]
});

new App({
    providers: [UserRepository]
});

Er kann verwendet werden, um einen Provider durch eine andere Class auszutauschen.

new App({
    providers: [{ provide: UserRepository, useClass: OtherUserRepository }]
});

In diesem Beispiel wird die Class OtherUserRepository nun ebenfalls im DI-Container verwaltet und all ihre Abhängigkeiten werden automatisch aufgelöst.

ValueProvider

Statische Werte können mit diesem Provider bereitgestellt werden.

new App({
    providers: [{ provide: OtherUserRepository, useValue: new OtherUserRepository() }]
});

Da nicht nur Class-Instanzen als Abhängigkeiten bereitgestellt werden können, kann jeder Wert als useValue angegeben werden. Ein symbol oder ein Primitive (string, number, boolean) könnte ebenfalls als Provider-Token verwendet werden.

new App({
    providers: [{ provide: 'domain', useValue: 'localhost' }]
});

Primitive Provider-Token müssen mit dem Inject Type als Abhängigkeit deklariert werden.

import { Inject } from '@deepkit/core';

class EmailService {
    constructor(public domain: Inject<string, 'domain'>) {}
}

Die Kombination aus einem Inject-Alias und primitiven Provider-Token kann auch verwendet werden, um Abhängigkeiten aus Packages bereitzustellen, die keine Runtime-Type-Informationen enthalten.

import { Inject } from '@deepkit/core';
import { Stripe } from 'stripe';

export type StripeService = Inject<Stripe, '_stripe'>;

new App({
    providers: [{ provide: '_stripe', useValue: new Stripe }]
});

Und dann auf der Nutzerseite wie folgt deklariert:

class PaymentService {
    constructor(public stripe: StripeService) {}
}

ExistingProvider

Eine Weiterleitung auf einen bereits definierten Provider kann definiert werden.

new App({
    providers: [
        {provide: OtherUserRepository, useValue: new OtherUserRepository()},
        {provide: UserRepository, useExisting: OtherUserRepository}
    ]
});

FactoryProvider

Eine Function kann verwendet werden, um einen Wert für den Provider bereitzustellen. Diese Function kann auch Parameters enthalten, die wiederum vom DI-Container bereitgestellt werden. So werden andere Abhängigkeiten oder Konfigurationsoptionen zugänglich.

new App({
    providers: [
        {provide: OtherUserRepository, useFactory: () => {
            return new OtherUserRepository()
        }},
    ]
});

new App({
    providers: [
        {provide: OtherUserRepository, useFactory: (domain: RootConfiguration['domain']) => {
            return new OtherUserRepository(domain);
        }},
    ]
});

new App({
    providers: [
        Database,
        {provide: OtherUserRepository, useFactory: (database: Database) => {
            return new OtherUserRepository(database);
        }},
    ]
});

InterfaceProvider

Neben Classes und Primitives können auch Abstraktionen (Interfaces) bereitgestellt werden. Dies geschieht über die Function provide und ist besonders nützlich, wenn der bereitzustellende Wert keine Type-Informationen enthält.

import { provide } from '@deepkit/injector';

interface Connection {
    write(data: Uint16Array): void;
}

class Server {
   constructor (public connection: Connection) {}
}

class MyConnection {
    write(data: Uint16Array): void {}
}

new App({
    providers: [
        Server,
        provide<Connection>(MyConnection)
    ]
});

Wenn mehrere Provider das Connection Interface implementiert haben, wird der letzte Provider verwendet.

Als Argument für provide() sind alle anderen Provider möglich.

const myConnection = {write: (data: any) => undefined};

new App({
    providers: [
        provide<Connection>({ useValue: myConnection })
    ]
});

new App({
    providers: [
        provide<Connection>({ useFactory: () => myConnection })
    ]
});

Asynchrone Provider

Das Design von @deepkit/injector schließt die Verwendung asynchroner Provider mit einem asynchronen Dependency Injection Container aus. Dies liegt daran, dass auch das Anfordern von Providern asynchron sein müsste, wodurch die gesamte Anwendung auf höchster Ebene asynchron arbeiten müsste.

Um etwas asynchron zu initialisieren, sollte diese Initialisierung in den Application-Server-Bootstrap verlegt werden, da dort die Events asynchron sein können. Alternativ kann eine Initialisierung manuell ausgelöst werden.

Provider konfigurieren

Konfigurations-Callbacks erlauben es, das Ergebnis eines Providers zu manipulieren. Das ist z. B. nützlich, um eine andere Dependency Injection-Variante zu nutzen, die Method Injection.

Sie können nur mit der Module API oder der App API verwendet werden und werden oberhalb des Moduls registriert.

class UserRepository  {
    private db?: Database;
    setDatabase(db: Database) {
       this.db = db;
    }
}

const rootModule = new InjectorModule([UserRepository])
     .addImport(lowLevelModule);

rootModule.configureProvider<UserRepository>(v => {
  v.setDatabase(db);
});

configureProvider erhält im Callback als ersten Parameter v die UserRepository-Instanz, auf der ihre Methods aufgerufen werden können.

Neben Method-Calls können auch Properties gesetzt werden.

class UserRepository  {
    db?: Database;
}

const rootModule = new InjectorModule([UserRepository])
     .addImport(lowLevelModule);

rootModule.configureProvider<UserRepository>(v => {
  v.db = new Database();
});

Alle Callbacks werden in eine Queue gestellt und in der Reihenfolge ihrer Definition ausgeführt.

Die Aufrufe in der Queue werden dann auf das tatsächliche Ergebnis des Providers ausgeführt, sobald der Provider erstellt wird. Das heißt: Bei einem ClassProvider werden sie auf die Class-Instanz angewendet, sobald die Instanz erstellt wurde; bei einem FactoryProvider auf das Ergebnis der Factory; und bei einem ValueProvider auf den Wert.

Um nicht nur statische Werte, sondern auch andere Provider zu referenzieren, können beliebige Abhängigkeiten in den Callback injiziert werden, indem sie einfach als Arguments definiert werden. Stellen Sie sicher, dass diese Abhängigkeiten im Provider-Scope bekannt sind.

class Database {}

class UserRepository  {
    db?: Database;
}

const rootModule = new InjectorModule([UserRepository, Database])
rootModule.configureProvider<UserRepository>((v, db: Database) => {
  v.db = db;
});

Nominale Typen

Beachten Sie, dass der an configureProvider übergebene Typ, wie im letzten Beispiel UserRepository, nicht mittels struktureller Typprüfung, sondern anhand nominaler Typen aufgelöst wird. Das bedeutet zum Beispiel, dass zwei Classes/Interfaces mit gleicher Struktur, aber unterschiedlicher Identität nicht kompatibel sind. Gleiches gilt für get<T>-Aufrufe oder wenn eine Abhängigkeit aufgelöst wird.

Dies unterscheidet sich von der Art, wie die TypeScript-Typprüfung funktioniert, die auf struktureller Typprüfung basiert. Diese Designentscheidung wurde getroffen, um versehentliche Fehlkonfigurationen zu vermeiden (z. B. das Anfordern einer leeren Class, die strukturell mit jeder Class kompatibel ist) und um den Code robuster zu machen.

Im folgenden Beispiel sind die Classes User1 und User2 strukturell kompatibel, aber nicht nominal. Das bedeutet, dass das Anfordern von User1 nicht User2 auflöst und umgekehrt.

class User1 {
    name: string = '';
}

class User2 {
    name: string = '';
}

new App({
    providers: [User1, User2]
});

Das Erweitern von Classes und das Implementieren von Interfaces stellt eine nominale Beziehung her.

class UserBase {
    name: string = '';
}

class User extends UserBase {
}

const app = new App({
    providers: [User2]
});

app.get(UserBase); //gibt User zurück
interface UserInterface {
    name: string;
}

class User implements UserInterface {
    name: string = '';
}

const app = new App({
    providers: [User]
});

app.get<UserInterface>(); //gibt User zurück
English中文 (Chinese)한국어 (Korean)日本語 (Japanese)Deutsch (German)