أول DI: أول DI على واجهات تطبيقات الطباعة النصية

أشارك إحدى مكتباتي تسمى First DI. لقد ساعدني لسنوات عديدة على حل مشكلة حقن التبعية في تطبيقات المتصفح للمكتبات مثل React و Preact و Mithril وغيرها. عند كتابة First DI ، تم أخذ أيديولوجية مكتبات DI من لغات C # و Java ، مثل autofac و java spring autowired و ninject وغيرها ، كأساس. تمامًا مثل المكتبات من هذه اللغات ، يعمل First DI استنادًا إلى واجهات الانعكاس والنسخ المطبعي.

ما هو DI؟


باختصار ، يعد Dependecy Injection (المشار إليه فيما يلي باسم DI) أحد الأجزاء الرئيسية لبناء بنية نظيفة. مما يسمح لك باستبدال أي جزء من البرنامج بتنفيذ آخر لهذا الجزء من البرنامج. و DI هي أداة لتنفيذ واستبدال أجزاء من البرنامج.

على وجه الخصوص ، يسمح لك:

  • استبدل تطبيقات المنطق بشكل فردي لكل منصة. على سبيل المثال ، في المتصفح ، اعرض الإشعارات من خلال واجهة برمجة تطبيقات الإشعارات ، وعند تجميع تطبيقات الجوال ، استبدلها بإشعارات من خلال واجهة برمجة تطبيقات النظام الأساسي للجوال.
  • استبدال تنفيذ المنطق في بيئات مختلفة. على سبيل المثال ، يمكن استخدام بوابة الدفع أو نظام توزيع الإشعارات الذي يتعذر الوصول إليه من الجهاز المحلي للمطور في دائرة المنتج. وبالتالي ، بدلاً من المدفوعات الحقيقية أو البريد في مرحلة التطوير ، يمكن استبدال المنطق بكعب.
  • استبدل مصدر البيانات. على سبيل المثال ، بدلاً من طلب حقيقي إلى الخادم ، قم بحذف البيانات المعدة مسبقًا لاختبار منطق الأعمال أو التخطيط للامتثال للتخطيطات.
  • والعديد من الاستخدامات المفيدة ، ولكن ليس عن ذلك الآن ...

للبدء ، قم بإعداد الأدوات للعمل.

التحضير للاستخدام


للاستخدام ، تحتاج فقط إلى 3 أشياء بسيطة:

  1. قم بتوصيل ملف بيانات التعريف العكسي .
  2. في ملف tsconfig ، قم بتمكين emitDecoratorMetadata وخيارات التجريبية التجريبية. الأول يسمح لك بتوليد التفكير ، والثاني يشمل دعم الديكور
  3. قم بإنشاء أي ديكور فارغ ، على سبيل المثال const reflection = (..._ params: Object []): void => {} أو استخدم الديكور الجاهز من المكتبة.

الآن سأوضح الاستخدام في أبسط أشكاله.

استخدام بسيط


على سبيل المثال ، خذ برنامجًا مكتوبًا باستخدام 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"); //  
}

في Pure Architecture ، تحتاج إلى إضافة سطرين فقط من التعليمات البرمجية ، يتم تشغيلهما تلقائيًا وتجاوزهما حتى تبدأ شركة DI العمل. والآن البرنامج لديه بالفعل تنفيذ للإنتاج وتنفيذ للاختبار.

منذ كتابة First DI في المقام الأول لتطبيقات العميل ، بشكل افتراضي سيتم تنفيذ التبعيات كـ Singleton. من أجل تغيير هذا السلوك بشكل عام ، هناك خيارات افتراضية ، بالإضافة إلى معلمات لمزين الديكور التلقائي وطريقة التجاوز ().

يمكنك تغيير ما يلي:

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

هذه هي أسهل طريقة لاستخدام DI. ولكن في المؤسسة ، بدلاً من تطبيقات محددة ، يتم استخدام الواجهات ، وهي عقد للتطبيقات التي سيتم تنفيذها. هناك طريقة ثانية للتشغيل لهذا.

الاستخدام المهني


للاستخدام المهني ، نستبدل تطبيقات محددة بواجهات. ولكن لا تولد الآلة المطبعية رمزًا لتشغيل الواجهات في وقت التشغيل. لحسن الحظ ، هناك حل بسيط ، لفهم أنه من الضروري تذكر النظرية ... ما هي الواجهة؟ هذه فئة مجردة تماما!

لحسن الحظ ، يدعم Typcript فئات مجردة بالكامل ، وينشئ رمز وقت التشغيل ، ويسمح باستخدام فئات مجردة لوصف الأنواع.

سنستخدم هذه المعلومات ونكتب برنامج مؤسسة:

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

الآن لدينا برنامج احترافي جاهز ليس له تنفيذ افتراضي ، ويتم استبدال جميع العقود بتنفيذها بالفعل في مرحلة تجميع التطبيقات.

من السهل جدًا تضمين DI في تطبيقك. وسيتمكن أولئك الذين كتبوا في C # و Java من استخدام التجربة الحالية في تطوير الويب.

ميزات أخرى


باستخدام نسخ متعددة من 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();
    }

}

القابلية للتوسعة ، يمكنك كتابة DI الخاص بك:

import { DI } from "first-di";

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

والعديد من الميزات الأكثر إثارة للاهتمام موصوفة في المستودع على GitHub .

لماذا كتابتك الخاصة ولا تستخدم؟


والسبب بسيط - عندما تم كتابة هذا DI لم تكن هناك بدائل. كان هذا الوقت عندما لم تعد الزاوية 1 ذات صلة ، ولم تكن الزاوية 2 للخروج. ظهرت الفصول الدراسية للتو في Javascript ، والتأمل في الكتابة المطبوعة. بالمناسبة ، أصبح ظهور التفكير الدافع الرئيسي للعمل.

InversifyJS - حتى في شكله الحالي لا يناسبني. الكثير من المراجل. بالإضافة إلى ذلك ، فإن التسجيل عن طريق الخط أو الشخصية على الجانب الآخر يقتل إمكانية إعادة تبعية إعادة التبعية.

احب؟


إذا كنت تحب هذا DI يساعد على جعله أكثر شعبية. جربها ، أرسل الطلبات ، ضع النجوم. مستودع جيثب .

All Articles