Erster DI: Erster DI für Schnittstellen für Typoskriptanwendungen

Ich teile eine meiner Bibliotheken namens First DI. Es hilft mir seit vielen Jahren, das Problem der Abhängigkeitsinjektion in Browseranwendungen für Bibliotheken wie React, Preact, Mithril und andere zu lösen. Beim Schreiben von First DI wurde die Ideologie von DI-Bibliotheken in C # - und Java-Sprachen wie Autofac, Java Spring Autowired, Ninject und anderen zugrunde gelegt. Und genau wie die Bibliotheken aus diesen Sprachen arbeitet First DI auf der Basis von Reflection- und Typescript-Schnittstellen.

Wofür ist DI?


Kurz gesagt, Dependecy Injection (im Folgenden DI) ist einer der Hauptteile für den Aufbau einer sauberen Architektur. Dadurch können Sie jeden Teil des Programms durch eine andere Implementierung dieses Teils des Programms ersetzen. Und DI ist ein Werkzeug zum Implementieren und Ersetzen von Teilen des Programms.

Insbesondere können Sie:

  • Ersetzen Sie Logikimplementierungen für jede Plattform einzeln. Zeigen Sie im Browser beispielsweise Benachrichtigungen über die Benachrichtigungs-API an und ersetzen Sie beim Zusammenstellen mobiler Anwendungen diese durch Benachrichtigungen über die API der mobilen Plattform.
  • Ersetzen Sie die Implementierung von Logik in verschiedenen Umgebungen. Beispielsweise kann ein Zahlungsgateway oder ein Benachrichtigungsverteilungssystem, auf das vom lokalen Computer des Entwicklers nicht zugegriffen werden kann, in der Produktschaltung verwendet werden. Anstelle von echten Zahlungen oder Mailing in der Entwicklungsphase kann die Logik daher durch einen Stub ersetzt werden.
  • Datenquelle ersetzen. Anstelle einer echten Anfrage an den Server können Sie beispielsweise vorbereitete Daten zum Testen der Geschäftslogik oder des Layouts auf Übereinstimmung mit den Layouts übertragen.
  • und viele weitere nützliche Anwendungen, aber jetzt nicht mehr ...

Bereiten Sie zunächst die Werkzeuge für die Arbeit vor.

Vorbereitung für den Gebrauch


Um es zu benutzen, müssen Sie nur drei einfache Dinge tun:

  1. Verbinden Sie die Reflect -Metadaten- Polyfüllung .
  2. Aktivieren Sie in der Datei tsconfig die Optionen emitDecoratorMetadata und experimentalDecorators. Mit dem ersten können Sie Reflexionen erzeugen, mit dem zweiten können Sie Dekorateure unterstützen
  3. Erstellen Sie einen leeren Dekorator, z. B. const reflexion = (..._ params: Object []): void => {}, oder verwenden Sie den vorgefertigten aus der Bibliothek.

Jetzt werde ich die Verwendung in ihrer einfachsten Form demonstrieren.

Einfache Verwendung


Nehmen Sie zum Beispiel ein Programm, das mit Pure Architecture geschrieben wurde:

import { autowired, override, reflection } from "first-di";

@reflection // typescript  
class ProdRepository { //   

    public async getData(): Promise<string> {
        return await Promise.resolve("production");
    }

}

@reflection
class MockRepository { //       

    public async getData(): Promise<string> {
        return await Promise.resolve("mock");
    }

}

@reflection
class ProdService {

    constructor(private readonly prodRepository: ProdRepository) { }

    public async getData(): Promise<string> {
        return await this.prodRepository.getData();
    }

}

class ProdController { //   React, Preact, Mithril  .

    @autowired() //  
    private readonly prodService!: ProdService;

    // constructor  ,   React, Preact, Mithril
    //      

    public async getData(): Promise<string> {
        return await this.prodService.getData();
    }

}

if (process.env.NODE_ENV === "test") { //    
    override(ProdRepository, MockRepository);
}

const controllerInstance = new ProdController(); //   
const data = await controllerInstance.getData();

if (process.env.NODE_ENV === "test") {
    assert.strictEqual(data, "mock"); //  
} else {
    assert.strictEqual(data, "production"); //  
}

In Pure Architecture müssen Sie nur 2 Codezeilen hinzufügen, die automatisch verdrahtet und überschrieben werden, damit DI funktioniert. Und jetzt hat das Programm bereits eine Implementierung für die Produktion und eine Implementierung für das Testen.

Da First DI hauptsächlich für Clientanwendungen geschrieben wurde, werden Abhängigkeiten standardmäßig als Singleton implementiert. Um dieses Verhalten global zu ändern, gibt es Standardoptionen sowie Parameter für den automatisch verdrahteten Dekorator und die override () -Methode.

Sie können Folgendes ändern:

//  1 
import { DI, AutowiredLifetimes } from "first-di";
DI.defaultOptions.lifeTime = AutowiredLifetimes.PER_INSTANCE;

//  2  
import { autowired, AutowiredLifetimes } from "first-di";
@autowired({lifeTime: AutowiredLifetimes.PER_INSTANCE})
private readonly prodService!: ProdService;

//  3  
import { override, AutowiredLifetimes } from "first-di";
override(ProdRepository, MockRepository, {lifeTime: AutowiredLifetimes.PER_INSTANCE});

Dies ist der einfachste Weg, DI zu verwenden. Im Unternehmen werden jedoch anstelle spezifischer Implementierungen Schnittstellen verwendet, die einen Vertrag für die zu implementierenden Implementierungen darstellen. Hierfür gibt es eine zweite Betriebsart.

Professionelle Verwendung


Für den professionellen Einsatz ersetzen wir bestimmte Implementierungen durch Schnittstellen. Typoskript generiert jedoch keinen Code zum Ausführen von Schnittstellen zur Laufzeit. Glücklicherweise gibt es eine einfache Lösung, um zu verstehen, dass man sich an die Theorie erinnern muss ... was ist eine Schnittstelle? Dies ist eine völlig abstrakte Klasse!

Glücklicherweise unterstützt Typoskript vollständig abstrakte Klassen, generiert Laufzeitcode und ermöglicht die Verwendung abstrakter Klassen zur Beschreibung von Typen.

Wir werden diese Informationen verwenden und ein Unternehmensprogramm schreiben:

import { autowired, override, reflection } from "first-di";

abstract class AbstractRepository { //    

    abstract getData(): Promise<string>;

}

@reflection
class ProdRepository implements AbstractRepository {

    public async getData(): Promise<string> {
        return await Promise.resolve("production");
    }

}

@reflection
class MockRepository implements AbstractRepository {

    public async getData(): Promise<string> {
        return await Promise.resolve("mock");
    }

}

abstract class AbstractService { //    

    abstract getData(): Promise<string>;

}

@reflection
class ProdService implements AbstractService {

    private readonly prodRepository: AbstractRepository;

    constructor(prodRepository: AbstractRepository) {
        this.prodRepository = prodRepository;
    }

    public async getData(): Promise<string> {
        return await this.prodRepository.getData();
    }

}

class ProdController { //   React, Preact, Mithril  .

    @autowired()
    private readonly prodService!: AbstractService;

    // constructor  ,   React, Preact, Mithril
    //      

    public async getData(): Promise<string> {
        return await this.prodService.getData();
    }

}

override(AbstractService, ProdService);

if (process.env.NODE_ENV === "test") {
    override(AbstractRepository, MockRepository);
} else {
    override(AbstractRepository, ProdRepository);
}

const controllerInstance = new ProdController();
const data = await controllerInstance.getData();

if (process.env.NODE_ENV === "test") {
    assert.strictEqual(data, "mock");
} else {
    assert.strictEqual(data, "production");
}

Jetzt haben wir ein vorgefertigtes Professional-Programm ohne Standardimplementierung, und alle Verträge werden durch Implementierungen ersetzt, die sich bereits in der Phase der Anwendungsmontage befinden.

So einfach ist es, DI in Ihre Anwendung einzubetten. Und diejenigen, die in C # und Java geschrieben haben, können die vorhandenen Erfahrungen in der Webentwicklung nutzen.

Andere Eigenschaften


Verwenden mehrerer Kopien von DI:

import { DI } from "first-di";
import { ProductionService } from "../services/ProductionService";

const scopeA = new DI();
const scopeB = new DI();

export class Controller {

    @scopeA.autowired()
    private readonly serviceScopeA!: ProductionService;

    @scopeB.autowired()
    private readonly serviceScopeB!: ProductionService;

    // constructor  ,   React, Preact, Mithril
    //      

    public async getDataScopeA(): Promise<string> {
        return await this.serviceScopeA.getData();
    }

    public async getDataScopeB(): Promise<string> {
        return await this.serviceScopeB.getData();
    }

}

Erweiterbarkeit, Sie können Ihre eigene DI schreiben:

import { DI } from "first-di";

class MyDI extends DI {
    // extended method
    public getAllSingletons(): IterableIterator<object> {
        return this.singletonsList.values();
    }
}

Viele weitere interessante Funktionen sind im Repository auf GitHub beschrieben .

Warum ist dein eigenes geschrieben und wird nicht verwendet?


Der Grund ist einfach - als dieser DI geschrieben wurde, gab es keine Alternativen. Dies war eine Zeit, in der Winkel 1 nicht mehr relevant war und Winkel 2 nicht austreten würde. Klassen sind gerade in Javascript und Reflexion in Typoskript erschienen. Das Auftreten von Reflexion ist übrigens zum Hauptimpuls zum Handeln geworden.

InversifyJS - auch in seiner jetzigen Form passt mir nicht. Zu viel Boilerplate. Darüber hinaus wird durch die Registrierung nach Zeile oder Zeichen auf der anderen Seite die Möglichkeit der Umgestaltung von Abhängigkeiten aufgehoben.

Gefallen?


Wenn Ihnen diese DI gefallen hat, helfen Sie, sie populärer zu machen. Experimentieren Sie damit, senden Sie Anfragen, setzen Sie Sterne. GitHub-Repository .

All Articles