ฉันต้องการการฉีดพึ่งพาใน NodeJS หรือจะจัดการกับ ...


220

ฉันกำลังสร้างโครงการทดลองบางอย่างที่มี nodejs ฉันได้ตั้งโปรแกรม Java EE เว็บแอพพลิเคชั่นไว้มากมายกับ Spring และชื่นชมความสะดวกในการใช้งานที่นั่น

ตอนนี้ฉันอยากรู้อยากเห็น: ฉันจะฉีดพึ่งพาโหนดได้อย่างไร หรือ: ฉันต้องการมันด้วยเหรอ? มีแนวคิดการแทนที่เนื่องจากรูปแบบการเขียนโปรแกรมแตกต่างกันหรือไม่?

ฉันกำลังพูดถึงสิ่งที่เรียบง่ายเช่นการแชร์วัตถุการเชื่อมต่อฐานข้อมูลจนถึงตอนนี้ แต่ฉันยังไม่พบวิธีแก้ปัญหาที่ทำให้ฉันพอใจ


1
หากคุณตัดสินใจที่จะใช้ DI เมื่อเร็ว ๆ นี้ OpenTable เปิดแหล่งที่มาของไลบรารี: github.com/opentable/spur-iocฉันใช้มัน (ฉันทำงานที่นั่น) และสามารถพูดได้ว่ามันง่ายและดีสำหรับการทดสอบ
tybro0103

คำตอบ:


108

ในระยะสั้นคุณไม่จำเป็นต้องมีตู้ฉีดที่ต้องพึ่งพาหรือ locater ให้บริการเหมือนที่คุณทำใน C # / Java เนื่องจาก Node.js ใช้ประโยชน์จากmodule patternจึงไม่จำเป็นต้องทำการสร้างคอนสตรัคเตอร์หรือคุณสมบัติการฉีด แม้ว่าคุณจะยังสามารถ

สิ่งที่ยอดเยี่ยมเกี่ยวกับ JS คือคุณสามารถปรับเปลี่ยนอะไรก็ได้เพื่อให้ได้สิ่งที่คุณต้องการ สิ่งนี้มีประโยชน์เมื่อพูดถึงการทดสอบ

ดูตัวอย่างที่อ่อนแอของฉัน

MyClass.js:

var fs = require('fs');

MyClass.prototype.errorFileExists = function(dir) {
    var dirsOrFiles = fs.readdirSync(dir);
    for (var d in dirsOrFiles) {
        if (d === 'error.txt') return true;
    }
    return false;
};

MyClass.test.js:

describe('MyClass', function(){
    it('should return an error if error.txt is found in the directory', function(done){
        var mc = new MyClass();
        assert(mc.errorFileExists('/tmp/mydir')); //true
    });
});

สังเกตว่าMyClassขึ้นอยู่กับfsโมดูลอย่างไร ตามที่ @ ShatyemShekhar พูดถึงคุณสามารถสร้างคอนสตรัคเตอร์หรือฉีดคุณสมบัติเหมือนกับภาษาอื่น ๆ ได้ แต่ไม่จำเป็นใน Javascript

ในกรณีนี้คุณสามารถทำสองสิ่ง

คุณสามารถต้นขั้ววิธีการหรือคุณสามารถกลับโมดูลที่แตกต่างกันอย่างสิ้นเชิงเมื่อคุณเรียกfs.readdirSyncrequire

วิธีที่ 1:

var oldmethod = fs.readdirSync;
fs.readdirSync = function(dir) { 
    return ['somefile.txt', 'error.txt', 'anotherfile.txt']; 
};

*** PERFORM TEST ***
*** RESTORE METHOD AFTER TEST ****
fs.readddirSync = oldmethod;

วิธีที่ 2:

var oldrequire = require
require = function(module) {
    if (module === 'fs') {
        return {
            readdirSync: function(dir) { 
                return ['somefile.txt', 'error.txt', 'anotherfile.txt']; 
            };
        };
    } else
        return oldrequire(module);

}

กุญแจสำคัญคือการใช้ประโยชน์จากพลังของ Node.js และ Javascript หมายเหตุฉันเป็นคน CoffeeScript ดังนั้นไวยากรณ์ JS ของฉันอาจไม่ถูกต้องที่ไหนสักแห่ง นอกจากนี้ฉันไม่ได้บอกว่านี่เป็นวิธีที่ดีที่สุด แต่ก็เป็นวิธี ผู้เชี่ยวชาญด้าน Javascript อาจสามารถสอดส่องด้วยโซลูชันอื่น ๆ

ปรับปรุง:

สิ่งนี้ควรตอบคำถามเฉพาะของคุณเกี่ยวกับการเชื่อมต่อฐานข้อมูล ฉันจะสร้างโมดูลแยกต่างหากสำหรับคุณเพื่อสรุปเหตุผลการเชื่อมต่อฐานข้อมูลของคุณ บางสิ่งเช่นนี้

MyDbConnection.js: (อย่าลืมเลือกชื่อที่ดีกว่า)

var db = require('whichever_db_vendor_i_use');

module.exports.fetchConnection() = function() {
    //logic to test connection

    //do I want to connection pool?

    //do I need only one connection throughout the lifecyle of my application?

    return db.createConnection(port, host, databasename); //<--- values typically from a config file    
}

จากนั้นโมดูลใด ๆ ที่ต้องการการเชื่อมต่อฐานข้อมูลก็จะรวมMyDbConnectionโมดูลของคุณ

SuperCoolWebApp.js:

var dbCon = require('./lib/mydbconnection'); //wherever the file is stored

//now do something with the connection
var connection = dbCon.fetchConnection(); //mydbconnection.js is responsible for pooling, reusing, whatever your app use case is

//come TEST time of SuperCoolWebApp, you can set the require or return whatever you want, or, like I said, use an actual connection to a TEST database. 

อย่าทำตามคำต่อคำตัวอย่างนี้ เป็นตัวอย่างที่พยายามสื่อสารว่าคุณใช้ประโยชน์จากmoduleรูปแบบในการจัดการการพึ่งพาของคุณ หวังว่านี่จะช่วยได้มากกว่านี้อีกเล็กน้อย


42
นี่เป็นเรื่องจริงสำหรับการทดสอบ แต่ DI มีประโยชน์อื่น ๆ โดยใช้ DI คุณสามารถตั้งโปรแกรมไปยังส่วนต่อประสานไม่ใช่การใช้งาน
moteutsch

3
@moteutsch ไม่แน่ใจว่าทำไมคุณถึงต้องตั้งแต่ JS ไม่มีความคิดของอินเทอร์เฟซเหมือนภาษาคงที่มากที่สุด สิ่งที่คุณมีทั้งหมดคือการนำไปใช้งานแม้ว่าคุณต้องการใช้บางส่วนที่ได้รับการตกลงล่วงหน้าไว้ใน "ส่วนต่อประสาน" ที่ทำเป็นเอกสาร
JP Richardson

16
@JPRichardson ฉันจะเขียนส่วนประกอบที่ใช้ตัวบันทึกโดยไม่ขึ้นกับไลบรารีใดไลบรารีหนึ่งได้อย่างไร ถ้าฉันrequire('my_logger_library')คนที่ใช้องค์ประกอบของฉันจะต้องยกเลิกการใช้ห้องสมุดของตัวเอง แต่ฉันสามารถอนุญาตให้คนผ่านการเรียกกลับที่ล้อมรอบการนำ logger ไปใช้ในส่วนประกอบ "constructor" หรือ "init" นั่นคือจุดประสงค์ของ DI
moteutsch

4
ณ กลางปี ​​2014 - npmjs.org/package/proxyquire ทำให้การเยาะเย้ย "ต้อง" เป็นเรื่องเล็กน้อย
arcseldon

4
ฉันไม่ได้รับมันแทนที่ต้องการในโมดูลหนึ่งไม่ได้แทนที่ในโมดูลอื่น ถ้าฉันตั้งต้องการฟังก์ชั่นในการทดสอบของฉันแล้วต้องการให้โมดูลได้รับการทดสอบจำเป็นต้องมีคำสั่งในวัตถุที่จะทดสอบไม่ได้ใช้ฟังก์ชั่นการตั้งค่าในโมดูลทดสอบ สิ่งนี้ทำให้การพึ่งพาอาศัยการฉีดอย่างไร?
HMR

73

requireเป็นวิธีการในการจัดการการอ้างอิงใน Node.js และแน่นอนมันเป็นที่ใช้งานง่ายและมีประสิทธิภาพ แต่ก็ยังมีข้อ จำกัด ของมัน

คำแนะนำของฉันคือการดูที่บางส่วนของการพึ่งพาการฉีดภาชนะที่มีอยู่ในวันนี้สำหรับ Node.js ที่จะมีความคิดเกี่ยวกับสิ่งที่เป็นข้อดี / ข้อเสียของพวกเขา บางส่วนของพวกเขาคือ:

เพียงเพื่อชื่อไม่กี่

ตอนนี้คำถามจริงคือสิ่งที่คุณสามารถประสบความสำเร็จกับภาชนะ DI Node.js เมื่อเทียบกับที่เรียบง่ายrequire?

ข้อดี:

  • ทดสอบได้ดีขึ้น: โมดูลยอมรับการพึ่งพาเป็นอินพุต
  • การผกผันของการควบคุม: ตัดสินใจว่าจะวางสายโมดูลของคุณอย่างไรโดยไม่ต้องสัมผัสรหัสหลักของแอปพลิเคชันของคุณ
  • อัลกอริทึมที่ปรับแต่งได้สำหรับการแก้ไขโมดูล: การพึ่งพามีตัวระบุ "เสมือน" โดยปกติแล้วพวกเขาจะไม่ถูกผูกไว้กับพา ธ บนระบบไฟล์
  • ความสามารถในการขยายที่ดีขึ้น: เปิดใช้งานโดยตัวระบุ IoC และ "เสมือน"
  • สิ่งแฟนซีอื่น ๆ ที่เป็นไปได้:
    • การเริ่มต้น Async
    • การจัดการวงจรชีวิตของโมดูล
    • ความสามารถในการขยายของคอนเทนเนอร์ DI เอง
    • สามารถใช้ abstractions ระดับที่สูงขึ้นได้อย่างง่ายดาย (เช่น AOP)

จุดด้อย:

  • แตกต่างจาก "ประสบการณ์" ของ Node.js: ไม่ได้ใช้requireแน่นอนรู้สึกเหมือนคุณเบี่ยงเบนไปจากวิธีคิดของโหนด
  • ความสัมพันธ์ระหว่างการพึ่งพาและการใช้งานนั้นไม่ชัดเจนเสมอไป การพึ่งพาอาจได้รับการแก้ไขที่รันไทม์และได้รับอิทธิพลจากพารามิเตอร์ต่างๆ รหัสกลายเป็นเรื่องยากที่จะเข้าใจและตรวจแก้จุดบกพร่อง
  • เวลาเริ่มต้นช้าลง
  • ครบกําหนด (ในขณะนี้): ไม่มีการแก้ปัญหาในปัจจุบันที่เป็นที่นิยมจริงๆในขณะนี้ดังนั้นบทเรียนไม่มากไม่มีระบบนิเวศไม่ต่อสู้ทดสอบ
  • คอนเทนเนอร์ DI บางอันจะเล่นได้ไม่ดีกับโมดูลรวมเช่น Browserify และ Webpack

เช่นเดียวกับสิ่งที่เกี่ยวข้องกับการพัฒนาซอฟต์แวร์ให้เลือกระหว่าง DI หรือrequireขึ้นอยู่กับความต้องการของคุณความซับซ้อนของระบบและสไตล์การเขียนโปรแกรมของคุณ


3
คุณคิดว่าสถานการณ์มีการเปลี่ยนแปลงอย่างมากตั้งแต่ '09
Juho Vepsäläinen

13
คุณหมายถึงตั้งแต่ 10 วันที่แล้ว? :)
Mario

2
nooo 9 ธ.ค. ... น่าจะรู้
Juho Vepsäläinen

4
ฉันใช้ "DI" โดยใช้ module.exports = function (deps) {} ชนิดของรูปแบบ ใช่มันใช้งานได้ แต่มันก็ไม่ค่อยเหมาะนัก
Juho Vepsäläinen

3
โมดูลยอมรับการพึ่งพาของพวกเขาเป็นอินพุทและการอ้างอิงไม่ชัดเจนเสียงให้ฉันเหมือนความขัดแย้ง
Anton Rudeshko

53

ฉันรู้ว่ากระทู้นี้ค่อนข้างเก่าในตอนนี้ แต่ฉันคิดว่าฉันจะพูดสอดกับความคิดของฉันในเรื่องนี้ TL; DR นั้นเป็นเพราะธรรมชาติของ JavaScript ที่ไม่มีการพิมพ์คุณสามารถทำได้ค่อนข้างมากโดยไม่ต้องพึ่งพารูปแบบการพึ่งพา (DI) หรือใช้เฟรมเวิร์ก DI อย่างไรก็ตามเนื่องจากแอปพลิเคชันมีขนาดใหญ่ขึ้นและมีความซับซ้อนมากขึ้น DI สามารถช่วยบำรุงรักษาโค้ดของคุณได้อย่างแน่นอน

DI ใน C #

เพื่อให้เข้าใจว่าทำไม DI จึงไม่ใช่ความต้องการที่ยิ่งใหญ่ใน JavaScript มันมีประโยชน์ที่จะดูภาษาที่พิมพ์ได้ดีเช่น C # (ขออภัยสำหรับผู้ที่ไม่รู้จัก C # แต่ควรจะง่ายพอที่จะติดตาม) สมมติว่าเรามีแอพที่อธิบายรถยนต์และแตรของมัน คุณจะกำหนดสองคลาส:

class Horn
{
    public void Honk()
    {
        Console.WriteLine("beep!");
    }
}

class Car
{
    private Horn horn;

    public Car()
    {
        this.horn = new Horn();
    }

    public void HonkHorn()
    {
        this.horn.Honk();
    }
}

class Program
{
    static void Main()
    {
        var car = new Car();
        car.HonkHorn();
    }
}

มีปัญหาเล็กน้อยในการเขียนโค้ดด้วยวิธีนี้

  1. Carชั้นคู่แน่นในการดำเนินงานโดยเฉพาะอย่างยิ่งของฮอร์นในHornชั้นเรียน หากเราต้องการเปลี่ยนประเภทของฮอร์นที่ใช้โดยรถยนต์เราต้องแก้ไขCarคลาสแม้ว่าการใช้ฮอร์นจะไม่เปลี่ยนแปลง สิ่งนี้ทำให้การทดสอบทำได้ยากเพราะเราไม่สามารถทดสอบCarชั้นเรียนแยกออกจากHornชั้นเรียนได้
  2. Carระดับเป็นผู้รับผิดชอบสำหรับการดำเนินงานของHornระดับ ในตัวอย่างง่ายๆเช่นนี้มันไม่ใช่ปัญหาใหญ่ แต่ในการพึ่งพาแอปพลิเคชันจริงจะมีการพึ่งพาซึ่งจะมีการพึ่งพา ฯลฯCarชั้นจะต้องรับผิดชอบในการสร้างต้นไม้ทั้งหมดของการพึ่งพา สิ่งนี้ไม่เพียง แต่ซับซ้อนและซ้ำซาก แต่เป็นการละเมิด "ความรับผิดชอบเดี่ยว" ของชั้นเรียน ควรเน้นที่การเป็นรถยนต์ไม่ใช่การสร้างอินสแตนซ์
  3. ไม่มีวิธีที่จะใช้อินสแตนซ์การพึ่งพาเดียวกันซ้ำได้ สิ่งนี้ไม่สำคัญในแอปพลิเคชันของเล่นนี้ แต่ให้พิจารณาการเชื่อมต่อฐานข้อมูล โดยทั่วไปแล้วคุณจะมีอินสแตนซ์เดียวที่แชร์ผ่านแอปพลิเคชันของคุณ

ทีนี้มาลองปรับเปลี่ยนสิ่งนี้เพื่อใช้รูปแบบการฉีดพึ่งพา

interface IHorn
{
    void Honk();
}

class Horn : IHorn
{
    public void Honk()
    {
        Console.WriteLine("beep!");
    }
}

class Car
{
    private IHorn horn;

    public Car(IHorn horn)
    {
        this.horn = horn;
    }

    public void HonkHorn()
    {
        this.horn.Honk();
    }
}

class Program
{
    static void Main()
    {
        var horn = new Horn();
        var car = new Car(horn);
        car.HonkHorn();
    }
}

เราได้ทำสองสิ่งสำคัญที่นี่ อันดับแรกเราได้แนะนำอินเทอร์เฟซที่Hornคลาสของเราใช้ สิ่งนี้ช่วยให้เราเขียนรหัสCarคลาสไปยังอินเทอร์เฟซแทนการใช้งานเฉพาะ IHornตอนนี้รหัสอาจจะใช้สิ่งที่ดำเนินการ ประการที่สองเราได้ดึงอินสแตนซ์ของฮอร์นออกCarแล้วผ่านเข้าไปแทน วิธีนี้จะช่วยแก้ไขปัญหาข้างต้นและนำไปไว้ที่ฟังก์ชั่นหลักของแอปพลิเคชันเพื่อจัดการอินสแตนซ์และวงจรชีวิตของพวกเขา

สิ่งนี้หมายความว่าอะไรที่จะสามารถแนะนำฮอร์นชนิดใหม่สำหรับให้รถใช้โดยไม่ต้องสัมผัสCarชั้นเรียน:

class FrenchHorn : IHorn
{
    public void Honk()
    {
        Console.WriteLine("le beep!");
    }
}

หลักสามารถแทรกตัวอย่างของFrenchHornคลาสแทน นอกจากนี้ยังช่วยลดความยุ่งยากในการทดสอบ คุณสามารถสร้างMockHornชั้นเรียนเพื่อแทรกเข้าไปในตัวCarสร้างเพื่อให้แน่ใจว่าคุณกำลังทดสอบเพียงCarชั้นแยก

ตัวอย่างข้างต้นแสดงการฉีดขึ้นต่อกันแบบแมนนวล โดยทั่วไปแล้ว DI จะทำกับเฟรมเวิร์ก (เช่นUnityหรือNinjectใน C # world) เฟรมเวิร์กเหล่านี้จะทำการเดินสายอ้างอิงทั้งหมดให้คุณโดยการเดินกราฟอ้างอิงและสร้างอินสแตนซ์ตามต้องการ

วิธีมาตรฐาน Node.js

ตอนนี้เรามาดูตัวอย่างเดียวกันใน Node.js เราอาจแบ่งรหัสของเราออกเป็น 3 โมดูล:

// horn.js
module.exports = {
    honk: function () {
        console.log("beep!");
    }
};

// car.js
var horn = require("./horn");
module.exports = {
    honkHorn: function () {
        horn.honk();
    }
};

// index.js
var car = require("./car");
car.honkHorn();

เนื่องจาก JavaScript ถูกพิมพ์ออกมาเราจึงไม่มีข้อต่อแน่นเหมือนที่เราเคยทำมาก่อน ไม่จำเป็นต้องมีอินเตอร์เฟส (หรือไม่มีอยู่) เนื่องจากcarโมดูลจะพยายามเรียกhonkเมธอดบนสิ่งที่hornโมดูลส่งออก

นอกจากนี้เนื่องจากโหนดrequireเก็บทุกอย่างโมดูลจะถูกเก็บไว้ในคอนเทนเนอร์เป็นหลัก โมดูลอื่น ๆ ที่ทำงานrequireบนhornโมดูลจะได้รับอินสแตนซ์เดียวกันแน่นอน ทำให้การแชร์วัตถุแบบซิงเกิลเช่นการเชื่อมต่อฐานข้อมูลทำได้ง่ายมาก

ตอนนี้ยังมีปัญหาที่ว่าโมดูลเป็นผู้รับผิดชอบสำหรับการเรียกพึ่งพาของตัวเองcar hornหากคุณต้องการให้รถใช้โมดูลที่แตกต่างกันสำหรับแตรของมันคุณจะต้องเปลี่ยนrequireคำสั่งในcarโมดูล สิ่งนี้ไม่ใช่สิ่งที่ทำกันมากนัก แต่มันทำให้เกิดปัญหากับการทดสอบ

วิธีที่ผู้คนปกติจัดการกับปัญหาที่เกิดขึ้นกับการทดสอบเป็นproxyquire เนื่องจากลักษณะของ JavaScript แบบไดนามิก proxyquire จะสกัดกั้นการโทรที่ต้องการและส่งคืน stubs / mocks ใด ๆ ที่คุณให้ไว้แทน

var proxyquire = require('proxyquire');
var hornStub = {
    honk: function () {
        console.log("test beep!");
    }
};

var car = proxyquire('./car', { './horn': hornStub });

// Now make test assertions on car...

นี่เกินพอสำหรับแอปพลิเคชั่นส่วนใหญ่ ถ้ามันใช้ได้กับแอปของคุณให้ไปกับมัน อย่างไรก็ตามในประสบการณ์ของฉันเมื่อแอปพลิเคชันขยายใหญ่ขึ้นและมีความซับซ้อนมากขึ้นการบำรุงรักษารหัสเช่นนี้จะกลายเป็นเรื่องยาก

DI ใน JavaScript

Node.js ยืดหยุ่นได้มาก หากคุณไม่พอใจกับวิธีการข้างต้นคุณสามารถเขียนโมดูลของคุณโดยใช้รูปแบบการฉีดพึ่งพา ในรูปแบบนี้ทุกโมดูลส่งออกฟังก์ชั่นโรงงาน (หรือตัวสร้างคลาส)

// horn.js
module.exports = function () {
    return {
        honk: function () {
            console.log("beep!");
        }
    };
};

// car.js
module.exports = function (horn) {
    return {
        honkHorn: function () {
            horn.honk();
        }
    };
};

// index.js
var horn = require("./horn")();
var car = require("./car")(horn);
car.honkHorn();

นี่คล้ายกับวิธี C # มากก่อนหน้านี้ในindex.jsโมดูลที่รับผิดชอบวงจรชีวิตและการเดินสายเช่น การทดสอบหน่วยนั้นค่อนข้างง่ายเนื่องจากคุณสามารถส่งผ่าน mocks / stubs ไปยังฟังก์ชั่นได้ หากนี่เป็นสิ่งที่ดีพอสำหรับแอปพลิเคชันของคุณ

Bolus DI Framework

แตกต่างจาก C # ไม่มีกรอบ DI มาตรฐานที่จัดตั้งขึ้นเพื่อช่วยในการจัดการการพึ่งพาของคุณ มีเฟรมเวิร์กจำนวนมากในรีจิสทรี npm แต่ไม่มีการยอมรับอย่างกว้างขวาง ตัวเลือกเหล่านี้จำนวนมากได้ถูกอ้างถึงแล้วในคำตอบอื่น ๆ

ผมไม่ได้มีความสุขโดยเฉพาะอย่างยิ่งกับใด ๆ ของตัวเลือกที่ใช้ได้ดังนั้นฉันเขียนของตัวเองที่เรียกว่ายาลูกกลอน ยาลูกกลอนถูกออกแบบมาเพื่อทำงานกับรหัสที่เขียนในรูปแบบ DI ข้างต้นและพยายามที่จะแห้งมากและง่ายมาก ด้วยการใช้โมดูลcar.jsและhorn.jsโมดูลข้างต้นที่แน่นอนคุณสามารถเขียนindex.jsโมดูลใหม่ได้ด้วย bolus ดังนี้

// index.js
var Injector = require("bolus");
var injector = new Injector();
injector.registerPath("**/*.js");

var car = injector.resolve("car");
car.honkHorn();

แนวคิดพื้นฐานคือคุณสร้างหัวฉีด คุณลงทะเบียนโมดูลทั้งหมดของคุณในหัวฉีด จากนั้นคุณก็แก้ไขสิ่งที่คุณต้องการ ยาลูกกลอนจะเดินกราฟการพึ่งพาและสร้างและฉีดการพึ่งพาตามความจำเป็น คุณไม่ได้ประหยัดมากนักในตัวอย่างของเล่นเช่นนี้ แต่ในแอปพลิเคชันขนาดใหญ่ที่มีต้นไม้ที่พึ่งพาได้ซับซ้อนการออมมีขนาดใหญ่

Bolus รองรับฟีเจอร์มากมายเช่นการอ้างอิงเสริมและการทดสอบรอบตัว แต่มีประโยชน์หลักสองประการที่ฉันได้เห็นเมื่อเทียบกับวิธี Node.js มาตรฐาน ก่อนอื่นถ้าคุณมีแอปพลิเคชั่นที่คล้ายกันจำนวนมากคุณสามารถสร้างโมดูล npm ส่วนตัวสำหรับฐานของคุณที่สร้างหัวฉีดและลงทะเบียนวัตถุที่มีประโยชน์บนมัน จากนั้นแอปเฉพาะของคุณสามารถเพิ่มแทนที่และแก้ไขได้ตามต้องการเช่นเดียวกับวิธีการใช้AngularJSหัวฉีดทำงาน ประการที่สองคุณสามารถใช้ยาลูกกลอนเพื่อจัดการบริบทต่างๆของการพึ่งพา ตัวอย่างเช่นคุณสามารถใช้มิดเดิลแวร์เพื่อสร้าง child injector ต่อการร้องขอลงทะเบียน user id, session id, logger และอื่น ๆ บน injector พร้อมกับโมดูลใด ๆ จากนั้นแก้ไขสิ่งที่คุณต้องการเพื่อให้บริการตามคำขอ สิ่งนี้จะให้อินสแตนซ์ของโมดูลของคุณต่อการร้องขอและป้องกันไม่ให้ส่งผ่านตัวบันทึก ฯลฯ พร้อมกับการเรียกฟังก์ชันโมดูลทุกครั้ง


1
ก็ยังเป็นความจริงว่ามีทางเลือกในการproxyquireเช่นsinonที่ช่วยให้คุณทำ mocks รัดกุมมากเช่นlet readFileStub = sinon.stub(fs, 'readFile').yields(new Error('something went wrong'));และบริการโทรแล้วภายหลังจะกลับข้อผิดพลาดจนกว่าคุณจะเปลี่ยนกลับต้นขั้วผ่านfs.readFile readFileStub.restore()โดยส่วนตัวแล้วฉันพบว่า DI ใช้งานได้อย่างน่าสงสัยเพราะฉันรู้สึกว่ามันเกือบจะต้องการการใช้คลาส / วัตถุซึ่งเป็นข้อสันนิษฐานที่น่าสงสัยเนื่องจากมีความยืดหยุ่นในการใช้งานของจาวาสคริปต์
เควิน

ขอบคุณสำหรับคำตอบดี ๆ ฉันเกือบจะพลาดมันเป็นครั้งแรกที่ผมอ่านพาดหัวDI ใน C #
Konstantin A. Magg

1
คำตอบที่ดี ฉันสงสัยว่าสิ่งที่คิดของคุณอยู่ใน 2019 สำหรับโครงการขนาดใหญ่เป็นเรื่องของการตั้งค่าส่วนตัวที่คุณชอบ - DI / IoC ในโหนดหรือเพียงแค่ stubbing / เยาะเย้ยด้วยjest, rewire, proxyquireฯลฯ ? ขอบคุณ
Jamie Corkhill

คำตอบที่ยอดเยี่ยม! ขอบคุณ.
Johnny Oshika

36

ผมเคยเขียนยังโมดูลที่จะบรรลุนี้ก็เรียกว่าReWire เพียงใช้npm install rewireแล้ว:

var rewire = require("rewire"),
    myModule = rewire("./path/to/myModule.js"); // exactly like require()

// Your module will now export a special setter and getter for private variables.
myModule.__set__("myPrivateVar", 123);
myModule.__get__("myPrivateVar"); // = 123


// This allows you to mock almost everything within the module e.g. the fs-module.
// Just pass the variable name as first parameter and your mock as second.
myModule.__set__("fs", {
    readFile: function (path, encoding, cb) {
        cb(null, "Success!");
    }
});
myModule.readSomethingFromFileSystem(function (err, data) {
    console.log(data); // = Success!
});

ฉันได้รับแรงบันดาลใจจากการฉีดของนาธานแมคอินเนสแต่ใช้วิธีการอื่น ฉันไม่ได้ใช้vmเพื่อประเมินผลการทดสอบโมดูลอันที่จริงฉันใช้ความต้องการของโหนดเอง วิธีนี้โมดูลของคุณมีลักษณะเหมือนกับที่ใช้require()(ยกเว้นการแก้ไขของคุณ) ยังรองรับการแก้ไขข้อบกพร่องอย่างสมบูรณ์


7
ณ กลางปี ​​2014 - npmjs.org/package/proxyquire ทำให้การเยาะเย้ย "ต้อง" เป็นเรื่องเล็กน้อย
arcseldon

proxyquire ก็เท่เช่นกัน! ตอนนี้พวกเขากำลังใช้โมดูล "ภายใน" โมดูลซึ่งเป็นวิธีที่ดีกว่าการใช้ vm ของโหนด แต่หลังจากทั้งหมดเป็นเพียงเรื่องของสไตล์ ฉันชอบโมดูลที่จะใช้ความต้องการดั้งเดิมและเพื่อสลับการอ้างอิงในภายหลัง นอกจากนี้ rewire ยังช่วยให้แทนที่ globals
Johannes Ewald

น่าสนใจมากที่มองหาสิ่งนี้เพื่อใช้ในที่ทำงานโมดูลนี้มีผลต่อโมดูลดาวน์สตรีมด้วยหรือไม่?
Akst

สำหรับproxyquireมีการกล่าวไว้ในคำอธิบายที่ใช้สำหรับการทดสอบ 'nodex ของพร็อกซีจำเป็นต้องมีเพื่อให้สามารถเอาชนะการอ้างอิงระหว่างการทดสอบ ' ไม่ใช่สำหรับ DI ใช่มั้ย
Marwen Trabelsi

17

ฉันสร้างอิเล็กโทรไลต์เพื่อจุดประสงค์นี้ อีกวิธีการแก้ปัญหาการฉีดพึ่งพาอื่น ๆ ที่มีการรุกรานเกินไปสำหรับรสนิยมของฉันและการยุ่งกับโลกrequireคือความไม่พอใจของฉันโดยเฉพาะ

อิเล็กโทรไลต์รวบรวมโมดูลเฉพาะที่ส่งออกฟังก์ชั่น "ตั้งค่า" ตามที่คุณเห็นในมิดเดิลแวร์ Connect / Express โดยพื้นฐานแล้วโมดูลประเภทนี้เป็นเพียงโรงงานสำหรับบางออบเจ็กต์ที่ส่งคืน

ตัวอย่างเช่นโมดูลที่สร้างการเชื่อมต่อฐานข้อมูล:

var mysql = require('mysql');

exports = module.exports = function(settings) {
  var connection = mysql.createConnection({
    host: settings.dbHost,
    port: settings.dbPort
  });

  connection.connect(function(err) {
    if (err) { throw err; }
  });

  return connection;
}

exports['@singleton'] = true;
exports['@require'] = [ 'settings' ];

สิ่งที่คุณเห็นที่ด้านล่างคือคำอธิบายประกอบซึ่งเป็นข้อมูลเมตาเพิ่มเติมอีกเล็กน้อยที่อิเล็กโทรไลต์ใช้ในการสร้างอินสแตนซ์และฉีดการพึ่งพาเชื่อมโยงส่วนประกอบแอปพลิเคชันของคุณเข้าด้วยกันโดยอัตโนมัติ

วิธีสร้างการเชื่อมต่อฐานข้อมูล:

var db = electrolyte.create('database');

อิเล็กโทรไลต์ผ่านการเคลื่อนที่@require'd dependencies และแทรกอินสแตนซ์เป็นอาร์กิวเมนต์ของฟังก์ชันที่ส่งออก

กุญแจสำคัญคือสิ่งนี้มีการบุกรุกน้อยที่สุด โมดูลนี้ใช้งานได้อย่างสมบูรณ์เป็นอิสระจากอิเล็กโทรไลต์เอง นั่นหมายความว่าการทดสอบหน่วยของคุณสามารถทดสอบโมดูลที่อยู่ภายใต้การทดสอบส่งผ่านวัตถุจำลองโดยไม่จำเป็นต้องพึ่งพาการเพิ่มเติมเพื่อ rewire internals

เมื่อเรียกใช้แอปพลิเคชันแบบเต็มขั้นตอนอิเล็กโทรไลต์จะอยู่ที่ระดับระหว่างโมดูลการเดินสายไฟเข้าด้วยกันโดยไม่จำเป็นต้องใช้รูปทรงกลมเดี่ยวหรือท่อประปามากเกินไป


1
คุณจะชี้แจงสิ่งที่เกิดขึ้นในรหัสที่คุณได้โพสต์เมื่อเรียกร้องให้connect()พ่น? แม้ว่าฉันจะไม่คุ้นเคยกับ MySql API สำหรับ Node แต่ฉันคาดว่าการโทรนี้จะไม่ตรงกันดังนั้นภาพประกอบจึงค่อนข้างชัดเจน
Andrey Agibalov

กำลังใช้อิเล็กโทรไลต์ คุณอ้างว่าเป็นโมดูล INJECT ที่ง่ายผ่านการส่งออก ['@ ต้อง'] แต่ถ้าฉันต้องสตับหนึ่งในโมดูลที่ต้องการจะทำได้อย่างไรในอิเล็กโทรไลต์ ในปัจจุบันหากเราต้องการโมดูลนี้สามารถทำได้อย่างง่ายดาย แต่สำหรับอิเล็กโทรไลต์นี้เป็นค่าลบอย่างมาก .... คุณมีตัวอย่างที่เราสามารถใช้โมดูลที่ได้รับการขนานนามและส่งผ่านแบบไดนามิกในขณะที่อินสแตนซ์ / ioc.use จากกรณีทดสอบ ดังนั้นโดยทั่วไปในการทดสอบหน่วยถ้าเราสามารถทำ ioc.create ('modulename') แล้วทำการฉีดของโมดูลที่ขึ้นอยู่กับ (แต่คนที่ถูก stubbed) จะเหมาะ ...
user1102171

1
คุณจะไม่โทรioc.createจากการทดสอบหน่วย การทดสอบหน่วยควรทดสอบเฉพาะโมดูลที่อยู่ในระหว่างการทดสอบและไม่นำการพึ่งพาอื่น ๆ มารวมถึงอิเล็กโทรไลต์ ทำตามคำแนะนำนี้คุณจะทำobjToTest = require('modulename')(mockObj1, mockObj2);
เจเร็ดแฮนสัน

8

ฉันดูมันด้วยตัวเอง ฉันไม่ชอบที่จะแนะนำการพึ่งพาเวทย์มนตร์ utils ห้องสมุดที่มีกลไกในการจี้นำเข้าโมดูลของฉัน แต่ฉันกลับมาพร้อมกับ "แนวทางการออกแบบ" สำหรับทีมของฉันที่จะระบุอย่างชัดเจนว่าการอ้างอิงใดบ้างที่สามารถล้อเลียนได้โดยการแนะนำการส่งออกฟังก์ชั่นจากโรงงานภายในโมดูลของฉัน

ฉันใช้คุณสมบัติ ES6 อย่างกว้างขวางสำหรับพารามิเตอร์และการทำลายล้างเพื่อหลีกเลี่ยงการสร้างแผ่นบางและจัดเตรียมกลไกการแทนที่การอ้างอิงที่มีชื่อ

นี่คือตัวอย่าง:

import foo from './utils/foo';
import bob from './utils/bob';

// We export a factory which accepts our dependencies.
export const factory = (dependencies = {}) => {
  const {
    // The 'bob' dependency.  We default to the standard 'bob' imp if not provided.
    $bob = bob, 
    // Instead of exposing the whole 'foo' api, we only provide a mechanism
    // with which to override the specific part of foo we care about.
    $doSomething = foo.doSomething // defaults to standard imp if none provided.
  } = dependencies;  

  return function bar() {
    return $bob($doSomething());
  }
}

// The default implementation, which would end up using default deps.
export default factory();

และนี่คือตัวอย่างของการใช้งาน

import { factory } from './bar';

const underTest = factory({ $bob: () => 'BOB!' }); // only override bob!
const result = underTest();

ขอโทษไวยากรณ์ ES6 สำหรับผู้ที่ไม่คุ้นเคย


แยบยลมากจริงๆ!
arnold

4

ฉันเพิ่งตรวจสอบกระทู้นี้ด้วยเหตุผลเดียวกับ OP - libs ส่วนใหญ่ที่ฉันพบเขียนคำสั่ง require ชั่วคราว ฉันประสบความสำเร็จหลายอย่างด้วยวิธีนี้และฉันก็เลยใช้วิธีต่อไปนี้

ในบริบทของแอปพลิเคชันแบบด่วน - ฉันใส่ app.js ในไฟล์ bootstrap.js:

var path = require('path');
var myapp = require('./app.js');

var loader = require('./server/services/loader.js');

// give the loader the root directory
// and an object mapping module names 
// to paths relative to that root
loader.init(path.normalize(__dirname), require('./server/config/loader.js')); 

myapp.start();

แผนผังวัตถุที่ส่งผ่านไปยังตัวโหลดมีลักษณะดังนี้:

// live loader config
module.exports = {
    'dataBaseService': '/lib/dataBaseService.js'
}

// test loader config
module.exports = {
    'dataBaseService': '/mocks/dataBaseService.js'
    'otherService' : {other: 'service'} // takes objects too...
};

จากนั้นแทนที่จะต้องโทรโดยตรง ...

var myDatabaseService = loader.load('dataBaseService');

หากไม่มีนามแฝงอยู่ในตัวโหลด - มันจะเป็นค่าเริ่มต้นสำหรับความต้องการปกติ สิ่งนี้มีประโยชน์สองประการ: ฉันสามารถสลับในคลาสรุ่นใดก็ได้และไม่จำเป็นต้องใช้ชื่อพา ธ สัมพัทธ์ตลอดทั้งแอปพลิเคชัน (ดังนั้นถ้าฉันต้องการ lib ที่กำหนดเองด้านล่างหรือด้านบนของไฟล์ปัจจุบันฉันไม่จำเป็นต้องข้าม และต้องการจะแคชโมดูลกับคีย์เดียวกัน) นอกจากนี้ยังช่วยให้ฉันสามารถระบุ mocks ที่จุดใดก็ได้ในแอปแทนในชุดทดสอบทันที

ฉันเพิ่งเผยแพร่โมดูล npm เล็กน้อยเพื่อความสะดวก:

https://npmjs.org/package/nodejs-simple-loader


3

ความจริงก็คือคุณสามารถทดสอบ node.js โดยไม่ต้องใช้คอนเทนเนอร์ IoC เพราะ JavaScript เป็นภาษาการเขียนโปรแกรมแบบไดนามิกและคุณสามารถแก้ไขเกือบทุกอย่างในเวลาทำงาน

พิจารณาสิ่งต่อไปนี้:

import UserRepository from "./dal/user_repository";

class UserController {
    constructor() {
        this._repository = new UserRepository();
    }
    getUsers() {
        this._repository.getAll();
    }
}

export default UserController;

ดังนั้นคุณสามารถแทนที่การเชื่อมต่อระหว่างส่วนประกอบในเวลาทำงาน ฉันชอบที่จะคิดว่าเราควรมุ่งมั่นที่จะแยกโมดูล JavaScript ของเราออก

วิธีเดียวที่จะบรรลุการแยกส่วนที่แท้จริงคือการลบการอ้างอิงไปที่UserRepository:

class UserController {
    constructor(userRepository) {
        this._repository = userRepository;
    }
    getUsers() {
        this._repository.getAll();
    }
}

export default UserController;

ซึ่งหมายความว่าคุณจะต้องทำการจัดวางวัตถุ:

import UserRepository from "./dal/user_repository";
import UserController from "./dal/user_controller";

export default new UserController(new UserRepository());

ฉันชอบแนวคิดของการมอบหมายองค์ประกอบของวัตถุไปยังคอนเทนเนอร์ IoC คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับความคิดนี้ในบทความสถานะปัจจุบันของการพึ่งพาผกผันใน JavaScript บทความพยายาม debunk "JavaScript IoC container myths":

ความเชื่อที่ 1: ไม่มีที่สำหรับคอนเทนเนอร์ IoC ใน JavaScript

ความเชื่อที่ 2: เราไม่ต้องการตู้คอนเทนเนอร์ IoC เรามีโมดูลตักแล้ว!

ความเชื่อที่ 3: การพึ่งพาการพึ่งพา === การพึ่งพาการฉีด

หากคุณชอบแนวคิดของการใช้คอนเทนเนอร์ IoC คุณสามารถดู InversifyJS รีลีสล่าสุด (2.0.0) รองรับการใช้งานหลายกรณี:

  • โมดูลเคอร์เนล
  • มิดเดิ้ลเคอร์เนล
  • ใช้คลาสตัวอักษรสตริงหรือสัญลักษณ์เป็นตัวบ่งชี้การพึ่งพา
  • การฉีดค่าคงที่
  • การฉีดของคลาสก่อสร้าง
  • ฉีดของโรงงาน
  • โรงงานอัตโนมัติ
  • การฉีดของผู้ให้บริการ (โรงงาน async)
  • ตัวจัดการการเปิดใช้งาน (ใช้ในการฉีดพร็อกซี)
  • การฉีดหลายครั้ง
  • ติดแท็กผูก
  • ตัวตกแต่งแท็กที่กำหนดเอง
  • การผูกชื่อ
  • การโยงบริบท
  • ข้อยกเว้นที่เป็นมิตร (เช่นการอ้างอิงแบบวงกลม)

คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับเรื่องที่InversifyJS


2

สำหรับ ES6 ฉันพัฒนาคอนเทนเนอร์นี้ https://github.com/zazoomauro/node-dependency-inject

import {ContainerBuilder} from 'node-dependency-injection'

let container = new ContainerBuilder()
container.register('mailer', 'Mailer')

จากนั้นคุณสามารถตั้งค่าตัวอย่างเช่นตัวเลือกของการขนส่งในภาชนะบรรจุ:

import {ContainerBuilder} from 'node-dependency-injection'

let container = new ContainerBuilder()
container
  .register('mailer', 'Mailer')
  .addArgument('sendmail')

คลาสนี้มีความยืดหยุ่นมากขึ้นในขณะที่คุณแยกตัวเลือกของการขนส่งออกจากการนำไปใช้และลงในคอนเทนเนอร์

ตอนนี้บริการจดหมายนั้นอยู่ในคอนเทนเนอร์แล้วคุณสามารถใช้งานมันเป็นการพึ่งพาของคลาสอื่น ๆ หากคุณมีคลาส NewsletterManager เช่นนี้:

class NewsletterManager {
    construct (mailer, fs) {
        this._mailer = mailer
        this._fs = fs
    }
}

export default NewsletterManager

เมื่อกำหนดบริการ newsletter_manager บริการจดหมายยังไม่มีอยู่ ใช้คลาส Reference เพื่อบอกให้คอนเทนเนอร์ทำการฉีดบริการเมลเมื่อเริ่มต้นตัวจัดการจดหมายข่าว:

import {ContainerBuilder, Reference, PackageReference} from 'node-dependency-injection'
import Mailer from './Mailer'
import NewsletterManager from './NewsletterManager'

let container = new ContainerBuilder()

container
  .register('mailer', Mailer)
  .addArgument('sendmail')

container
  .register('newsletter_manager', NewsletterManager)
  .addArgument(new Reference('mailer'))
  .addArgument(new PackageReference('fs-extra'))

คุณยังสามารถตั้งค่าคอนเทนเนอร์ด้วยไฟล์กำหนดค่าเช่นไฟล์ Yaml, Json หรือ JS

คอนเทนเนอร์เซอร์วิสสามารถรวบรวมได้จากหลายสาเหตุ เหตุผลเหล่านี้รวมถึงการตรวจสอบปัญหาที่อาจเกิดขึ้นเช่นการอ้างอิงแบบวงกลมและทำให้คอนเทนเนอร์มีประสิทธิภาพมากขึ้น

container.compile()

1

ขึ้นอยู่กับการออกแบบแอปพลิเคชันของคุณ เห็นได้ชัดว่าคุณสามารถทำจาวาเช่นการฉีดที่คุณสร้างวัตถุของคลาสที่มีการพึ่งพาผ่านในตัวสร้างเช่นนี้

function Cache(store) {
   this._store = store;
}

var cache = new Cache(mysqlStore);

หากคุณไม่ได้ทำ OOP ในรูปแบบจาวาสคริปต์คุณสามารถสร้างฟังก์ชั่น init ที่ตั้งค่าทุกอย่างได้

อย่างไรก็ตามมีวิธีการอีกวิธีหนึ่งที่คุณสามารถทำได้ซึ่งเป็นเรื่องธรรมดาในระบบที่ใช้เหตุการณ์เช่น node.js หากคุณสามารถสร้างแบบจำลองแอปพลิเคชันของคุณให้ดำเนินการกับเหตุการณ์ (ส่วนใหญ่แล้ว) สิ่งที่คุณต้องทำคือการตั้งค่าทุกอย่าง (ซึ่งฉันมักจะทำโดยการเรียกฟังก์ชั่น init) และปล่อยเหตุการณ์จากต้นขั้ว ทำให้การทดสอบค่อนข้างง่ายและอ่านง่าย


ขอบคุณสำหรับคำตอบของคุณ แต่ฉันไม่เข้าใจส่วนที่สองของคำตอบของคุณ
Erik

1

ฉันชอบความเรียบง่ายของแนวคิด IoC เสมอ - "คุณไม่ต้องรู้อะไรเกี่ยวกับสภาพแวดล้อมคุณจะถูกเรียกโดยใครบางคนเมื่อจำเป็น"

แต่การใช้งาน IoC ทั้งหมดที่ฉันเห็นนั้นเป็นสิ่งที่ตรงกันข้าม - พวกเขายุ่งเหยิงโค้ดด้วยสิ่งต่าง ๆ มากกว่าที่จะไม่มีมัน ดังนั้นฉันสร้าง IoC ของตัวเองที่ทำงานเป็นผมอยากให้มันเป็น - มันอยู่ที่ซ่อนอยู่และมองไม่เห็น 90% ของเวลา

มันถูกใช้ในเว็บเฟรมเวิร์ก MonoJS http://monojs.org

ฉันกำลังพูดถึงสิ่งที่เรียบง่ายเช่นการแชร์วัตถุการเชื่อมต่อฐานข้อมูลจนถึงตอนนี้ แต่ฉันยังไม่พบวิธีแก้ปัญหาที่ทำให้ฉันพอใจ

มันทำเช่นนี้ - ลงทะเบียนองค์ประกอบหนึ่งครั้งในการกำหนดค่า

app.register 'db', -> 
  require('mongodb').connect config.dbPath

และใช้งานได้ทุกที่

app.db.findSomething()

คุณสามารถดูรหัสการกำหนดองค์ประกอบแบบเต็ม (ด้วยการเชื่อมต่อฐานข้อมูลและส่วนประกอบอื่น ๆ ) ที่นี่https://github.com/sinizinairina/mono/blob/master/mono.coffee

นี่เป็นสถานที่เดียวที่คุณต้องบอก IoC ว่าจะทำอย่างไรหลังจากนั้นส่วนประกอบทั้งหมดจะถูกสร้างและต่อสายโดยอัตโนมัติและคุณไม่ต้องเห็นรหัสเฉพาะของ IoC ในแอปพลิเคชันของคุณอีกต่อไป

IoC เองhttps://github.com/alexeypetrushin/miconjs


6
แม้ว่าโฆษณาเป็น DI แต่ดูเหมือนว่าจะเป็นบริการค้นหา
KyorCode

2
ดูดีน่าละอายที่มีเฉพาะในกาแฟเท่านั้น
Rafael P. Miranda

1

ฉันคิดว่าเรายังคงต้องพึ่งพาการฉีดใน Nodejs เพราะมันเป็นการลดการพึ่งพาระหว่างบริการและทำให้แอปพลิเคชันชัดเจนขึ้น

แรงบันดาลใจจากSpring Frameworkฉันยังใช้โมดูลของตัวเองเพื่อรองรับการฉีดใน Nodejs ด้วย โมดูลของฉันยังสามารถตรวจจับcode changesและauto reloadบริการโดยไม่ต้องรีสตาร์ทแอปพลิเคชันของคุณ

เยี่ยมชมโครงการของฉันได้ที่: Buncha - IoC container

ขอบคุณ!



0

ฉันทำงานกับ. Net, PHP และ Java เป็นเวลานานดังนั้นฉันจึงต้องการที่จะใช้ Dependency Injection ใน NodeJS ด้วยเช่นกัน ผู้คนกล่าวว่า DI ในตัวใน NodeJS ก็เพียงพอแล้วที่เราจะได้มาพร้อมกับ Module แต่มันก็ไม่ทำให้ฉันพอใจ ฉันต้องการเก็บโมดูลไว้ไม่เกินคลาส นอกจากนี้ฉันต้องการ DI ที่จะได้รับการสนับสนุนอย่างเต็มที่สำหรับการจัดการวงจรชีวิตของโมดูล (โมดูลเดี่ยว, โมดูล transient ฯลฯ ) แต่ด้วยโมดูล Node ฉันต้องเขียนรหัสคู่มือบ่อยครั้งมาก สุดท้ายฉันต้องการทำให้การทดสอบหน่วยง่ายขึ้น นั่นเป็นเหตุผลที่ฉันสร้างการฉีดพึ่งพาสำหรับตัวเอง

หากคุณกำลังมองหา DI ลองทำดู มันสามารถพบได้ที่นี่: https://github.com/robo-creative/nodejs-robo-container เอกสารครบถ้วน นอกจากนี้ยังแก้ไขปัญหาที่พบโดยทั่วไปเกี่ยวกับ DI และวิธีแก้ปัญหาด้วยวิธี OOP หวังว่ามันจะช่วย


ใช่คุณมีสิทธิห้องสมุด DI ในตัวคุณโครงการเป็นสิ่งสำคัญสำหรับสถาปัตยกรรมที่ดีถ้าคุณอยากเห็นกรณีการใช้งานสำหรับ DI ดู README สำหรับการเก็บข้อมูลนี้ยังมีห้องสมุด DI สำหรับโหนดJems DI
Francisco Mercedes

-1

ฉันเพิ่งสร้างห้องสมุดที่เรียกว่าcircuitboxซึ่งช่วยให้คุณใช้การพึ่งพาการฉีดกับ node.js มันเป็นความจริงการพึ่งพาการฉีดกับห้องสมุดที่ขึ้นอยู่กับการค้นหาจำนวนมากที่ฉันได้เห็น Circuitbox ยังสนับสนุนการสร้างแบบอะซิงโครนัสและรูทีนการเริ่มต้น ด้านล่างเป็นตัวอย่าง:

สมมติว่ารหัสต่อไปนี้อยู่ในไฟล์ชื่อ consoleMessagePrinter.js

'use strict';

// Our console message printer
// deps is injected by circuitbox with the dependencies
function ConsoleMessagePrinter(deps) {
  return {
    print: function () {
      console.log(deps.messageSource.message());
    }
  };
}

module.exports = ConsoleMessagePrinter;

สมมติว่าสิ่งต่อไปนี้อยู่ในไฟล์ main.js

'use strict';

// our simple message source
// deps is injected by circuitbox with the dependencies
var simpleMessageSource = function (deps) {
  return {
    message: function () {
      return deps.message;
    }
  };
};

// require circuitbox
var circuitbox = require('../lib');

// create a circuitbox
circuitbox.create({
  modules: [
    function (registry) {
      // the message to be used
      registry.for('message').use('This is the message');

      // define the message source
      registry.for('messageSource').use(simpleMessageSource)
        .dependsOn('message');

      // define the message printer - does a module.require internally
      registry.for('messagePrinter').requires('./consoleMessagePrinter')
        .dependsOn('messageSource');
    }
  ]
}).done(function (cbx) {

  // get the message printer and print a message
  cbx.get('messagePrinter').done(function (printer) {
    printer.print();
  }, function (err) {
    console.log('Could not recieve a printer');
    return;
  });

}, function (err) {
  console.log('Could not create circuitbox');
});

Circuitbox ช่วยให้คุณกำหนดองค์ประกอบของคุณและประกาศการพึ่งพาเป็นโมดูล เมื่อเริ่มต้นมันจะช่วยให้คุณสามารถดึงส่วนประกอบ Circuitbox จะทำการฉีดส่วนประกอบทั้งหมดที่องค์ประกอบเป้าหมายต้องการโดยอัตโนมัติและมอบให้กับคุณเพื่อการใช้งาน

โครงการอยู่ในเวอร์ชันอัลฟ่า ความคิดเห็นความคิดเห็นและข้อเสนอแนะของคุณยินดีต้อนรับ

หวังว่ามันจะช่วย!


-1

ฉันคิดว่าโพสต์อื่นทำได้ดีมากในการโต้เถียงสำหรับการใช้ DI สำหรับฉันด้วยเหตุผลคือ

  1. ฉีดพึ่งพาโดยไม่ทราบเส้นทางของพวกเขา ซึ่งหมายความว่าหากคุณเปลี่ยนตำแหน่งโมดูลบนดิสก์หรือสลับกับที่อื่นคุณไม่จำเป็นต้องแตะทุกไฟล์ที่ขึ้นอยู่กับมัน

  2. มันทำให้ง่ายขึ้นมากในการจำลองการอ้างอิงสำหรับการทดสอบโดยไม่ต้องเจ็บปวดจากการแทนที่requireฟังก์ชั่นระดับโลกในลักษณะที่ทำงานได้โดยไม่มีปัญหา

  3. มันช่วยให้คุณจัดระเบียบและให้เหตุผลเกี่ยวกับการใช้งานของคุณเป็นโมดูลคู่ที่หลวม

แต่ฉันมีเวลายากมากในการหากรอบ DI ที่ทีมของฉันและฉันสามารถนำมาใช้ได้อย่างง่ายดาย ดังนั้นฉันเพิ่งสร้างกรอบเรียกว่า deppieตามคุณสมบัติเหล่านี้

  • API ขั้นต่ำที่สามารถเรียนรู้ได้ในไม่กี่นาที
  • ไม่มีรหัสพิเศษ / การกำหนดค่า / หมายเหตุประกอบ
  • การแมปโดยตรงหนึ่งถึงหนึ่งกับrequireโมดูล
  • สามารถนำไปใช้บางส่วนเพื่อทำงานกับรหัสที่มีอยู่

-1

ควรมีความยืดหยุ่นและเรียบง่ายเช่นนี้:

var MyClass1 = function () {}
var MyClass2 = function (myService1) {
    // myService1.should.be.instanceof(MyClass1); 
}


container.register('myService1', MyClass1);
container.register('myService2', MyClass2, ['myService1']);

ฉันได้เขียนบทความเกี่ยวกับการพึ่งพาการฉีดใน node.js

ฉันหวังว่ามันจะช่วยคุณได้


-1

Node.js ต้องการ DI มากเท่ากับแพลตฟอร์มอื่น ๆ หากคุณกำลังสร้างสิ่งที่มีขนาดใหญ่ DI จะทำให้ง่ายต่อการเยาะเย้ยการพึ่งพาของรหัสของคุณและทดสอบรหัสของคุณอย่างละเอียด

ตัวอย่างเช่นโมดูลเลเยอร์ฐานข้อมูลของคุณไม่ควรถูกเรียกใช้ที่โมดูลรหัสธุรกิจของคุณเพราะเมื่อหน่วยทดสอบโมดูลโค้ดธุรกิจเหล่านี้ daos จะโหลดและเชื่อมต่อกับฐานข้อมูล

ทางออกหนึ่งคือการผ่านการพึ่งพาเป็นพารามิเตอร์โมดูล:

module.exports = function (dep1, dep2) {
// private methods

   return {
    // public methods
       test: function(){...}
   }
}

วิธีนี้สามารถล้อเลียนการพึ่งพาได้ง่ายและเป็นธรรมชาติและคุณสามารถจดจ่อกับการทดสอบโค้ดของคุณโดยไม่ต้องใช้ห้องสมุดบุคคลที่สามที่ยุ่งยาก

มีวิธีแก้ไขปัญหาอื่น ๆ อีกมากมาย (บรอดเวย์สถาปนิก ฯลฯ ) ซึ่งสามารถช่วยเหลือคุณ แม้ว่าพวกเขาอาจทำมากกว่าสิ่งที่คุณต้องการหรือใช้ความยุ่งเหยิงมากขึ้น


เกือบจะผ่านวิวัฒนาการทางธรรมชาติที่ฉันได้ทำเช่นเดียวกัน ฉันผ่านการพึ่งพาเป็นพารามิเตอร์และใช้งานได้ดีสำหรับการทดสอบ
munkee

-1

ฉันได้พัฒนาห้องสมุดที่จัดการการฉีดพึ่งพาวิธีง่าย ๆ ที่ลดรหัสสำเร็จรูป แต่ละโมดูลจะถูกกำหนดโดยชื่อที่ไม่ซ้ำกันและฟังก์ชั่นการควบคุม พารามิเตอร์ของคอนโทรลเลอร์สะท้อนการพึ่งพาของโมดูล

อ่านเพิ่มเติมเกี่ยวกับKlarkJS

ตัวอย่างย่อ:

KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
    return {
        log: function() { console.log('Hello from module myModuleName1') }
    };
});
  • myModuleName1 เป็นชื่อของโมดูล
  • $nodeModule1node_moduleเป็นห้องสมุดภายนอกจาก ชื่อnode-module1นี้เปลี่ยนเป็น คำนำหน้า$ระบุว่าเป็นโมดูลภายนอก
  • myModuleName2 เป็นชื่อของโมดูลภายใน
  • ค่าตอบแทนของตัวควบคุมถูกนำมาใช้จากโมดูลภายในอื่น ๆ myModuleName1เมื่อพวกเขากำหนดพารามิเตอร์

-1

ฉันค้นพบคำถามนี้ในขณะที่ตอบปัญหาในโมดูล DI ของตัวเองถามว่าทำไมระบบ DI สำหรับการเขียนโปรแกรม NodeJS

คำตอบนั้นมีแนวโน้มที่ชัดเจนกับคำตอบที่ได้รับในหัวข้อนี้ มีการแลกเปลี่ยนทั้งวิธีการและการอ่านคำตอบของคำถามนี้ให้รูปร่างที่ดีของพวกเขา

ดังนั้นคำตอบที่แท้จริงสำหรับคำถามนี้ควรเป็นเช่นนั้นในบางสถานการณ์คุณจะใช้ระบบ DI ในที่อื่น ๆ ไม่ใช่

ที่กล่าวมาสิ่งที่คุณต้องการในฐานะนักพัฒนาคือการไม่ทำซ้ำตัวเองและนำบริการของคุณกลับมาใช้ซ้ำในแอปพลิเคชันต่างๆของคุณ

ซึ่งหมายความว่าเราควรเขียนบริการที่พร้อมใช้งานในระบบ DI แต่ไม่เชื่อมโยงกับไลบรารี DI สำหรับฉันมันหมายความว่าเราควรเขียนบริการเช่นนี้:

module.exports = initDBService;

// Tells any DI lib what it expects to find in it context object
// The $inject prop is the de facto standard for DI imo 
initDBService.$inject = ['ENV'];

// Note the context object, imo, a DI tool should bring
// services in a single context object
function initDBService({ ENV }) {
/// actual service code
}

วิธีนี้จะทำให้การบริการของคุณไม่สำคัญหากคุณใช้ด้วยหรือไม่ใช้เครื่องมือ DI


-1

TypeDIนั้นหวานที่สุดของทั้งหมดที่กล่าวถึงที่นี่ดูรหัสนี้ใน TypeDI

import "reflect-metadata";
import {Service, Container} from "typedi";

@Service()
class SomeClass {

    someMethod() {
    }

}

let someClass = Container.get(SomeClass);
someClass.someMethod();

ดูรหัสนี้ด้วย:

import {Container, Service, Inject} from "typedi";

// somewhere in your global app parameters
Container.set("authorization-token", "RVT9rVjSVN");

@Service()
class UserRepository {

    @Inject("authorization-token")
    authorizationToken: string;

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.