DI Pertama: DI Pertama di Antarmuka untuk Aplikasi Script

Saya membagikan salah satu perpustakaan saya yang disebut First DI. Ini telah membantu saya selama bertahun-tahun untuk menyelesaikan masalah injeksi ketergantungan pada aplikasi browser untuk perpustakaan seperti React, Preact, Mithril dan lainnya. Ketika menulis DI Pertama, ideologi perpustakaan DI dari bahasa C # dan Java, seperti autofac, java spring autowired, ninject, dan lainnya, diambil sebagai dasar. Dan sama seperti perpustakaan dari bahasa-bahasa ini, First DI bekerja berdasarkan pada refleksi dan antarmuka naskah.

Untuk apa DI?


Singkatnya, Dependecy Injection (selanjutnya disebut DI) adalah salah satu bagian utama untuk membangun Arsitektur Bersih. Yang memungkinkan Anda untuk mengganti bagian mana pun dari program dengan implementasi lain dari bagian program ini. Dan DI adalah alat untuk mengimplementasikan dan mengganti bagian-bagian dari program.

Secara khusus, ini memungkinkan Anda untuk:

  • Ganti implementasi logika satu per satu untuk setiap platform. Misalnya, di browser, tampilkan pemberitahuan melalui API Pemberitahuan, dan saat merakit aplikasi seluler, ganti dengan pemberitahuan melalui API platform seluler.
  • Ganti implementasi logika di berbagai lingkungan. Misalnya, gateway pembayaran atau sistem distribusi notifikasi yang tidak dapat diakses dari mesin lokal pengembang dapat digunakan dalam sirkuit produk. Dengan demikian, alih-alih pembayaran nyata atau pengiriman pada tahap pengembangan, logika dapat diganti dengan rintisan.
  • Ganti sumber data. Misalnya, alih-alih permintaan nyata ke server, buka data yang sudah disiapkan sebelumnya untuk menguji logika bisnis atau tata letak untuk kepatuhan dengan tata letak.
  • dan masih banyak lagi kegunaan yang bermanfaat, tetapi bukan tentang itu sekarang ...

Untuk memulai, siapkan alat untuk bekerja.

Persiapan untuk digunakan


Untuk menggunakannya, Anda hanya perlu melakukan 3 hal sederhana:

  1. Hubungkan polyfill mencerminkan-metadata .
  2. Dalam file tsconfig, aktifkan opsi emitDecoratorMetadata dan experimentalDecorators. Yang pertama memungkinkan Anda untuk menghasilkan refleksi, yang kedua termasuk dukungan untuk dekorator
  3. Buat dekorator kosong, misalnya const reflection = (..._ params: Object []): void => {} atau gunakan yang sudah jadi dari perpustakaan.

Sekarang saya akan menunjukkan penggunaannya dalam bentuk yang paling sederhana.

Penggunaan sederhana


Misalnya, ambil program yang ditulis menggunakan Arsitektur Murni:

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

Dalam Arsitektur Murni, Anda hanya perlu menambahkan 2 baris kode, diotomatiskan, dan diganti agar DI mulai bekerja. Dan sekarang program sudah memiliki implementasi untuk produksi dan implementasi untuk pengujian.

Karena First DI ditulis terutama untuk aplikasi klien, dependensi default akan diimplementasikan sebagai Singleton. Untuk mengubah perilaku ini secara global, ada opsi default, serta parameter untuk dekorator autowired dan metode override ().

Anda dapat mengubah sebagai berikut:

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

Ini adalah cara termudah untuk menggunakan DI. Tetapi di perusahaan, alih-alih implementasi spesifik, antarmuka digunakan, yang merupakan kontrak untuk implementasi yang akan diimplementasikan. Ada mode operasi kedua untuk ini.

Penggunaan profesional


Untuk penggunaan profesional, kami mengganti implementasi spesifik dengan antarmuka. Tetapi naskah tidak menghasilkan kode untuk menjalankan antarmuka dalam runtime. Untungnya, ada solusi sederhana, untuk memahami perlu mengingat teori ... apa itu antarmuka? Ini adalah kelas yang sepenuhnya abstrak!

Untungnya, naskah mendukung kelas yang sepenuhnya abstrak, menghasilkan kode runtime, dan memungkinkan penggunaan kelas abstrak untuk menggambarkan jenis.

Kami akan menggunakan informasi ini dan menulis program perusahaan:

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

Sekarang kami memiliki program Profesional siap pakai yang tidak memiliki implementasi standar, dan semua kontrak diganti dengan implementasi yang sudah pada tahap perakitan aplikasi.

Hanya sesederhana itu untuk menanamkan DI di aplikasi Anda Dan mereka yang menulis dalam C # dan Java akan dapat menggunakan pengalaman yang ada dalam pengembangan web.

Fitur lainnya


Menggunakan beberapa salinan 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();
    }

}

Ekstensibilitas, Anda dapat menulis DI Anda sendiri:

import { DI } from "first-di";

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

Dan banyak lagi fitur menarik yang dijelaskan dalam repositori di GitHub .

Mengapa tulisan Anda sendiri dan tidak digunakan?


Alasannya sederhana - ketika DI ini ditulis tidak ada alternatif. Ini adalah waktu ketika sudut 1 tidak lagi relevan, dan sudut 2 tidak akan keluar. Kelas baru saja muncul di Javascript, dan refleksi dalam naskah. Kebetulan, penampilan refleksi telah menjadi pendorong utama untuk bertindak.

InversifyJS - bahkan dalam bentuk saat ini tidak cocok untuk saya. Terlalu banyak boilerplate. Selain itu, mendaftar dengan garis atau karakter di sisi lain membunuh kemungkinan ketergantungan refactoring.

Menyukai?


Jika Anda suka, bantu DI ini membuatnya lebih populer. Eksperimen dengannya, kirim permintaan, beri bintang. Repositori GitHub .

All Articles