Primer DI: primer DI en interfaces para aplicaciones mecanografiadas

Comparto una de mis bibliotecas llamada First DI. Me ha ayudado durante muchos a帽os a resolver el problema de la inyecci贸n de dependencia en aplicaciones de navegador para bibliotecas como React, Preact, Mithril y otras. Al escribir First DI, se tom贸 como base la ideolog铆a de las bibliotecas DI de C # y los lenguajes Java, como autofac, java spring autowired, ninject y otros. Y al igual que las bibliotecas de estos lenguajes, First DI funciona en base a interfaces de reflexi贸n y mecanografiado.

驴Para qu茅 sirve la DI?


En resumen, la inyecci贸n de dependencia (en adelante DI) es una de las partes principales para construir una arquitectura limpia. Lo que le permite reemplazar cualquier parte del programa con otra implementaci贸n de esta parte del programa. Y DI es una herramienta para implementar y reemplazar partes del programa.

En particular, le permite:

  • Reemplazar implementaciones l贸gicas individualmente para cada plataforma. Por ejemplo, en el navegador, muestre las notificaciones a trav茅s de la API de notificaciones y, al ensamblar aplicaciones m贸viles, reempl谩celas por notificaciones a trav茅s de la API de la plataforma m贸vil.
  • Reemplazar la implementaci贸n de la l贸gica en varios entornos. Por ejemplo, se puede utilizar una pasarela de pago o un sistema de distribuci贸n de notificaciones inaccesible desde la m谩quina local del desarrollador en el circuito del producto. Por lo tanto, en lugar de pagos reales o correo en la etapa de desarrollo, la l贸gica se puede reemplazar con un tal贸n.
  • Reemplazar fuente de datos. Por ejemplo, en lugar de una solicitud real al servidor, elimine los datos previamente preparados para probar la l贸gica de negocios o el dise帽o para cumplir con los dise帽os.
  • y muchos m谩s usos 煤tiles, pero no se trata de eso ahora ...

Para comenzar, prepare las herramientas para el trabajo.

Preparaci贸n para el uso


Para usar, debe hacer solo 3 cosas simples:

  1. Conecte reflejo-metadatos polyfill .
  2. En el archivo tsconfig, habilite las opciones emitDecoratorMetadata y experimentalDecorators. El primero le permite generar reflejos, el segundo incluye soporte para decoradores
  3. Cree cualquier decorador vac铆o, por ejemplo, const reflection = (..._ params: Object []): void => {} o use uno listo de la biblioteca.

Ahora demostrar茅 el uso en su forma m谩s simple.

Uso simple


Por ejemplo, tome un programa escrito usando Pure Architecture:

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

En Pure Architecture, debe agregar solo 2 l铆neas de c贸digo, con cableado autom谩tico y anulaci贸n para que DI comience a funcionar. Y ahora el programa ya tiene una implementaci贸n para producci贸n y una implementaci贸n para pruebas.

Dado que First DI se escribi贸 principalmente para aplicaciones cliente, las dependencias predeterminadas se implementar谩n como Singleton. Para cambiar globalmente este comportamiento, hay opciones predeterminadas, as铆 como par谩metros para el decorador con cableado autom谩tico y el m茅todo override ().

Puede cambiar de la siguiente manera:

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

Esta es la forma m谩s f谩cil de usar DI. Pero en la empresa, en lugar de implementaciones espec铆ficas, se utilizan interfaces, que son un contrato para implementaciones que se implementar谩n. Hay un segundo modo de operaci贸n para esto.

Uso profesional


Para uso profesional, reemplazamos implementaciones espec铆ficas con interfaces. Pero typecript no genera c贸digo para ejecutar interfaces en tiempo de ejecuci贸n. Afortunadamente, hay una soluci贸n simple, para comprender es necesario recordar la teor铆a ... 驴qu茅 es una interfaz? 隆Esta es una clase completamente abstracta!

Afortunadamente, typecript admite clases completamente abstractas, genera c贸digo de tiempo de ejecuci贸n y permite el uso de clases abstractas para describir tipos.

Utilizaremos esta informaci贸n y escribiremos un programa empresarial:

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

Ahora tenemos un programa profesional listo que no tiene una implementaci贸n predeterminada, y todos los contratos se reemplazan por implementaciones que ya est谩n en la etapa de ensamblaje de la aplicaci贸n.

Es as铆 de simple incrustar DI en su aplicaci贸n. Y aquellos que escribieron en C # y Java podr谩n usar la experiencia existente en desarrollo web.

Otras caracter铆sticas


Usando m煤ltiples copias de 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();
    }

}

Extensibilidad, puede escribir su propia DI:

import { DI } from "first-di";

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

Y muchas m谩s caracter铆sticas interesantes se describen en el repositorio en GitHub .

驴Por qu茅 el tuyo est谩 escrito y no se usa?


La raz贸n es simple: cuando se escribi贸 este DI no hab铆a alternativas. Este fue un momento en que el angular 1 ya no era relevante, y el angular 2 no iba a salir. Las clases acaban de aparecer en Javascript y la reflexi贸n en mecanografiado. Por cierto, la aparici贸n de la reflexi贸n se ha convertido en el 铆mpetu principal para la acci贸n.

InversifyJS: incluso en su forma actual no me conviene. Demasiado repetitivo. Adem谩s, el registro por l铆nea o car谩cter en el otro lado mata la posibilidad de refactorizar dependencias.

驴Gust贸?


Si le gust贸 este DI, ay煤delo a hacerlo m谩s popular. Experimente con 茅l, env铆e solicitudes, ponga estrellas. Repositorio de GitHub .

All Articles