Skip to content

Services mit Factories erstellen

Nutze registerClass, wenn die Konstruktion genau new Ctor(...deps) entspricht. registerFactory eignet sich für Konfiguration, Drittanbieter-APIs, Interface-Bindungen oder andere explizite Initialisierungslogik.

ts
const container = new Container()
  .registerValue('config', {
    dsn: 'postgres://localhost/app',
    poolSize: 10
  })
  .registerFactory('pool', (c) => {
    const { dsn, poolSize } = c.get('config')
    return new Pool({ connectionString: dsn, max: poolSize })
  })
  .registerClass('users', UserRepository, ['pool'])

Der Rückgabetyp wird zum registrierten Servicetyp.

Factories mit Containerzugriff

Der einfache Callback erhält einen nach Lebensdauer gefilterten Container. Eine Singleton-Factory kann nur Abhängigkeiten auflösen, die für Singletons zulässig sind; scoped und transiente Schlüssel weist TypeScript zurück.

ts
const root = new Container()
  .registerValue('prefix', 'app')
  .registerFactory('logger', (c) => new Logger(c.get('prefix')))

Diese Form eignet sich für bedingte oder mehrstufige Auflösung. Führe alle .get()-Aufrufe synchron aus. Soll eine asynchrone Initialisierung durch den Graphen weitergegeben werden, nutze registerAsyncFactory.

Deklarierte Factory-Abhängigkeiten

Die Überladung mit deps macht Anforderungen im Graphen sichtbar und beschränkt den Resolver des Callbacks auf diese Schlüssel:

ts
const root = new Container()
  .declareScopeInputs<{ request: RequestContext }>()
  .registerClass('logger', Logger, [])
  .registerFactory(
    'requestLog',
    (deps) => new RequestLog(
      deps.get('request'),
      deps.get('logger')
    ),
    ['request', 'logger'],
    'scoped'
  )

Deklarierte Abhängigkeiten geben Scope-Eingaben und Lebensdaueranforderungen durch Module und weitere Registrierungen weiter. Anders als bei registerAsyncFactory erhält der Callback einen Resolver, keine positionsgebundenen Werte.

Lebensdauern von Factories

Factories verwenden dieselben Lebensdauern wie Klassen:

ts
const root = new Container()
  .registerFactory('cache', () => new Cache(), 'singleton')
  .registerFactory('requestState', () => new RequestState(), 'scoped')
  .registerFactory('operation', () => new Operation(), 'transient')

Singleton- und scoped Ergebnisse werden gecacht und gehören ihrem Container. Transiente Ergebnisse werden von InferDI weder gecacht noch freigegeben.

Mit einem vierten Argument lazyKey entsteht eine Lazy-Begleitregistrierung mit derselben Lebensdauer:

ts
const root = new Container()
  .registerFactory('cache', () => new Cache(), 'singleton', 'cacheLazy')

root.get('cacheLazy').get()

Eine Begleitregistrierung erfordert eine explizite Lebensdauer, auch bei 'singleton'. Die Regeln stehen unter Lazy Injection.

Interfaces binden

Interfaces besitzen zur Laufzeit keinen Konstruktor. Gib der Factory einen expliziten Servicetyp, wenn Verbraucher von einer Abstraktion abhängen sollen:

ts
interface Mailer {
  send(message: string): void
}

class SendGridMailer implements Mailer {
  send(message: string) {}
}

const container = new Container()
  .registerFactory<'mailer', Mailer>(
    'mailer',
    () => new SendGridMailer()
  )

Verbraucher von mailer sehen nun Mailer statt SendGridMailer.

Den Promise-Vertrag wählen

Ein von registerFactory zurückgegebenes Promise ist selbst der Servicewert: .get() liefert Promise<T>, und abhängige Factories erhalten dasselbe Promise. Nutze dies nur, wenn das Promise zum synchronen Graphen gehören soll.

Verwende registerAsyncFactory, wenn T der Service ist und das Promise dessen Initialisierung darstellt. Diese Form propagiert den Async-Status und wird mit .getAsync() aufgelöst. Asynchrone Abhängigkeiten beschreibt beide Verträge, Caching, Lazy-Begleiter, Fehler und Freigabe.