Apakah saya memerlukan injeksi ketergantungan pada NodeJS, atau bagaimana cara mengatasinya ...?


219

Saat ini saya membuat beberapa proyek eksperimental dengan nodejs. Saya telah memprogram banyak aplikasi web Java EE dengan Spring dan menghargai kemudahan injeksi ketergantungan di sana.

Sekarang saya ingin tahu: Bagaimana cara saya melakukan injeksi ketergantungan dengan simpul? Atau: Apakah saya membutuhkannya? Apakah ada konsep pengganti, karena gaya pemrogramannya berbeda?

Saya berbicara tentang hal-hal sederhana, seperti berbagi objek koneksi database, sejauh ini, tetapi saya belum menemukan solusi yang memuaskan saya.


1
Jika Anda memutuskan untuk menggunakan DI, OpenTable baru-baru ini membuka perpustakaan untuknya: github.com/opentable/spur-ioc Saya telah menggunakannya (saya bekerja di sana), dan dapat mengatakan itu cukup sederhana dan bagus untuk pengujian.
tybro0103

Jawaban:


107

Singkatnya, Anda tidak memerlukan wadah injeksi ketergantungan atau penyedia layanan seperti yang Anda lakukan di C # / Java. Sejak Node.js, memanfaatkan module pattern, tidak perlu melakukan konstruktor atau injeksi properti. Meskipun kamu masih bisa.

Hal hebat tentang JS adalah Anda dapat memodifikasi apa saja untuk mencapai apa yang Anda inginkan. Ini sangat berguna ketika datang ke pengujian.

Lihatlah contoh buat saya yang sangat timpang.

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

Perhatikan bagaimana MyClasstergantung pada fsmodul? Seperti @ShatyemShekhar sebutkan, Anda memang bisa melakukan injeksi konstruktor atau properti seperti dalam bahasa lain. Tapi itu tidak perlu di Javascript.

Dalam hal ini, Anda dapat melakukan dua hal.

Anda dapat mematikan fs.readdirSyncmetode atau Anda dapat mengembalikan modul yang sama sekali berbeda saat Anda menelepon require.

Metode 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;

Metode 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);

}

Kuncinya adalah memanfaatkan kekuatan Node.js dan Javascript. Catatan, saya seorang pria CoffeeScript, jadi sintaks JS saya mungkin salah di suatu tempat. Juga, saya tidak mengatakan bahwa ini adalah cara terbaik, tetapi ini adalah cara. Guru Javascript mungkin bisa berbaur dengan solusi lain.

Memperbarui:

Ini harus menjawab pertanyaan spesifik Anda tentang koneksi basis data. Saya akan membuat modul terpisah untuk Anda untuk merangkum logika koneksi database Anda. Sesuatu seperti ini:

MyDbConnection.js: (pastikan untuk memilih nama yang lebih baik)

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    
}

Kemudian, modul apa pun yang membutuhkan koneksi database kemudian hanya akan menyertakan MyDbConnectionmodul Anda .

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. 

Jangan ikuti contoh ini kata demi kata. Ini adalah contoh lemah dalam mencoba berkomunikasi bahwa Anda memanfaatkan modulepola untuk mengelola dependensi Anda. Semoga ini sedikit membantu.


42
Ini benar sehubungan dengan pengujian, tetapi DI memiliki manfaat lain; dengan menggunakan DI, Anda dapat memprogram ke antarmuka, bukan implementasi.
moteutsch

3
@moteutsch Tidak yakin mengapa Anda melakukannya karena JS tidak memiliki gagasan tentang antarmuka seperti kebanyakan bahasa statis. Yang Anda miliki hanyalah implementasi, bahkan jika Anda ingin menggunakan "antarmuka" yang telah didokumentasikan sebelumnya.
JP Richardson

16
@ JPRichardson Bagaimana saya bisa menulis komponen yang menggunakan logger tanpa bergantung pada satu perpustakaan? Jika saya require('my_logger_library'), orang yang menggunakan komponen saya harus menimpa keharusan untuk menggunakan perpustakaan mereka sendiri. Sebagai gantinya, saya dapat mengizinkan orang untuk melewati callback yang membungkus implementasi logger ke komponen "konstruktor" atau "init" metode. Itulah tujuan DI.
moteutsch

4
Pada pertengahan 2014 - npmjs.org/package/proxyquire membuat mengejek "memerlukan" dependensi sepele.
arcseldon

4
Saya tidak mengerti, mengganti yang diperlukan dalam satu modul tidak menggantikannya dengan yang lain. Jika saya atur perlu fungsi dalam pengujian saya dan kemudian memerlukan modul untuk diuji, pernyataan memerlukan dalam objek yang akan diuji tidak menggunakan fungsi yang ditetapkan dalam modul tes. Bagaimana ini menyuntikkan dependensi?
HMR

72

requireadalah dengan cara mengelola dependensi di Node.js dan pasti itu adalah intuitif dan efektif, tetapi juga memiliki keterbatasan.

Saran saya adalah untuk melihat beberapa wadah Ketergantungan Injeksi yang tersedia hari ini untuk Node.js untuk memiliki gagasan tentang apa pro / kontra mereka. Beberapa dari mereka adalah:

Untuk beberapa nama.

Sekarang pertanyaan sebenarnya adalah, apa yang bisa Anda capai dengan wadah DI Node.js, dibandingkan dengan yang sederhana require?

Pro:

  • testabilitas yang lebih baik: modul menerima dependensinya sebagai input
  • Inversion of Control: putuskan cara menyambungkan modul Anda tanpa menyentuh kode utama aplikasi Anda.
  • algoritme yang dapat disesuaikan untuk menyelesaikan modul: dependensi memiliki pengidentifikasi "virtual", biasanya mereka tidak terikat pada lintasan pada sistem file.
  • Ekstensibilitas yang lebih baik: diaktifkan oleh IoC dan pengidentifikasi "virtual".
  • Barang mewah lainnya yang mungkin:
    • Inisialisasi async
    • Manajemen siklus hidup modul
    • Extensibility wadah DI itu sendiri
    • Dapat dengan mudah mengimplementasikan abstraksi level yang lebih tinggi (mis. AOP)

Cons:

  • Berbeda dari "pengalaman" Node.js: tidak menggunakan requirepasti terasa seperti Anda menyimpang dari cara berpikir Node.
  • Hubungan antara ketergantungan dan implementasinya tidak selalu eksplisit. Ketergantungan dapat diselesaikan pada saat runtime dan dipengaruhi oleh berbagai parameter. Kode menjadi lebih sulit untuk dipahami dan didebug
  • Waktu startup lebih lambat
  • Kematangan (saat ini): tidak ada solusi saat ini yang benar - benar populer saat ini, jadi tidak begitu banyak tutorial, tidak ada ekosistem, tidak ada pertempuran yang diuji.
  • Beberapa kontainer DI tidak akan berfungsi dengan baik dengan bundler modul seperti Browserify dan Webpack.

Seperti halnya apa pun yang terkait dengan pengembangan perangkat lunak, memilih antara DI atau requiretergantung pada kebutuhan Anda, kompleksitas sistem Anda, dan gaya pemrograman Anda.


3
Apakah Anda pikir situasinya telah banyak berubah sejak '09?
Juho Vepsäläinen

13
Apakah maksud Anda sejak 10 hari yang lalu? :)
Mario

2
Tidaaaak 9 Des ... Seharusnya tahu.
Juho Vepsäläinen

4
Saya "mengimplementasikan" DI menggunakan module.exports = fungsi (deps) {} jenis pola. Ya, itu berhasil, tetapi itu tidak cukup ideal.
Juho Vepsäläinen

3
modul menerima dependensi mereka sebagai input dan Ketergantungan tidak terdengar eksplisit bagi saya seperti sebuah kontradiksi.
Anton Rudeshko

53

Saya tahu utas ini cukup lama pada saat ini, tetapi saya pikir saya akan berpadu dengan pemikiran saya tentang ini. TL; DR adalah bahwa karena sifat JavaScript yang tidak diketik dan dinamis, Anda sebenarnya dapat melakukan cukup banyak tanpa menggunakan pola ketergantungan injeksi (DI) atau menggunakan kerangka kerja DI. Namun, ketika aplikasi tumbuh lebih besar dan lebih kompleks, DI pasti dapat membantu pemeliharaan kode Anda.

DI dalam C #

Untuk memahami mengapa DI tidak sebesar kebutuhan dalam JavaScript, sangat membantu untuk melihat bahasa yang sangat diketik seperti C #. (Permintaan maaf kepada mereka yang tidak tahu C #, tetapi seharusnya cukup mudah untuk diikuti.) Katakanlah kita memiliki aplikasi yang menggambarkan mobil dan klaksonnya. Anda akan mendefinisikan dua kelas:

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

Ada beberapa masalah dengan penulisan kode dengan cara ini.

  1. The Carkelas erat digabungkan dengan pelaksanaan tertentu dari tanduk di Hornkelas. Jika kita ingin mengubah jenis klakson yang digunakan oleh mobil, kita harus memodifikasi Carkelasnya meskipun penggunaan klakson tidak berubah. Ini juga membuat pengujian sulit karena kita tidak dapat menguji Carkelas secara terpisah dari ketergantungannya, Hornkelas.
  2. The Carkelas bertanggung jawab untuk siklus hidup dari Hornkelas. Dalam contoh sederhana seperti ini bukan masalah besar, tetapi dalam aplikasi nyata dependensi akan memiliki dependensi, yang akan memiliki dependensi, dll. CarKelas akan perlu bertanggung jawab untuk membuat seluruh pohon dependensi. Ini tidak hanya rumit dan berulang, tetapi juga melanggar "tanggung jawab tunggal" kelas. Seharusnya fokus pada menjadi mobil, bukan menciptakan instance.
  3. Tidak ada cara untuk menggunakan kembali contoh ketergantungan yang sama. Sekali lagi, ini tidak penting dalam aplikasi mainan ini, tetapi pertimbangkan koneksi basis data. Anda biasanya memiliki satu instance yang dibagikan di seluruh aplikasi Anda.

Sekarang, mari kita refactor ini untuk menggunakan pola injeksi ketergantungan.

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

Kami telah melakukan dua hal penting di sini. Pertama, kami telah memperkenalkan antarmuka yang Hornmengimplementasikan kelas kami . Ini memungkinkan kita mengkodekan Carkelas ke antarmuka alih-alih implementasi tertentu. Sekarang kode dapat mengambil apa pun yang mengimplementasikan IHorn. Kedua, kami telah mengambil instantiasi klakson Cardan membagikannya. Ini menyelesaikan masalah di atas dan membiarkannya ke fungsi utama aplikasi untuk mengelola instance spesifik dan siklus hidupnya.

Apa artinya ini yang akan memperkenalkan klakson tipe baru untuk digunakan mobil tanpa menyentuh Carkelas:

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

Main hanya bisa menyuntikkan instance FrenchHornkelas sebagai gantinya. Ini juga secara dramatis menyederhanakan pengujian. Anda bisa membuat MockHornkelas untuk disuntikkan ke Carkonstruktor untuk memastikan Anda menguji hanya Carkelas secara terpisah.

Contoh di atas menunjukkan injeksi ketergantungan manual. Biasanya DI dilakukan dengan kerangka kerja (misalnya Unity atau Ninject di dunia C #). Kerangka kerja ini akan melakukan semua kabel dependensi untuk Anda dengan menapaki grafik dependensi Anda dan membuat instance yang diperlukan.

Cara Node.js Standar

Sekarang mari kita lihat contoh yang sama di Node.js. Kami mungkin akan memecah kode kami menjadi 3 modul:

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

Karena JavaScript tidak diketik, kami tidak memiliki kopling ketat yang sama seperti yang kami miliki sebelumnya. Tidak perlu antarmuka (juga tidak ada) karena carmodul hanya akan mencoba memanggil honkmetode apa pun yang horndiekspor modul.

Selain itu, karena requirecache Node semuanya, modul pada dasarnya adalah lajang yang disimpan dalam sebuah wadah. Modul lain yang melakukan requirepada hornmodul akan mendapatkan contoh yang sama persis. Ini membuat berbagi objek tunggal seperti koneksi basis data sangat mudah.

Sekarang masih ada masalah bahwa carmodul bertanggung jawab untuk mengambil ketergantungannya sendiri horn. Jika Anda ingin mobil menggunakan modul berbeda untuk klaksonnya, Anda harus mengubah requirepernyataan di carmodul. Ini bukan hal yang sangat umum untuk dilakukan, tetapi hal itu menyebabkan masalah dengan pengujian.

Cara biasa orang menangani masalah pengujian adalah dengan proxyquire . Karena sifat dinamis dari JavaScript, proksi meminta intersep panggilan untuk meminta dan mengembalikan setiap bertopik / mengejek yang Anda berikan.

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

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

// Now make test assertions on car...

Ini lebih dari cukup untuk sebagian besar aplikasi. Jika itu berfungsi untuk aplikasi Anda, maka ikutilah. Namun, dalam pengalaman saya ketika aplikasi tumbuh lebih besar dan lebih kompleks, mempertahankan kode seperti ini menjadi lebih sulit.

DI dalam JavaScript

Node.js sangat fleksibel. Jika Anda tidak puas dengan metode di atas, Anda dapat menulis modul menggunakan pola injeksi ketergantungan. Dalam pola ini, setiap modul mengekspor fungsi pabrik (atau konstruktor kelas).

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

Ini sangat analog dengan metode C # sebelumnya dalam index.jsmodul yang bertanggung jawab untuk siklus hidup dan kabel misalnya. Pengujian unit cukup sederhana karena Anda bisa mengoper / bertopik pada fungsi. Sekali lagi, jika ini cukup baik untuk aplikasi Anda, ikutilah.

Kerangka Kerja Bolus DI

Tidak seperti C #, tidak ada kerangka kerja DI standar yang ditetapkan untuk membantu manajemen ketergantungan Anda. Ada sejumlah kerangka kerja dalam registri npm tetapi tidak ada yang memiliki adopsi luas. Banyak dari opsi ini telah dikutip di jawaban lain.

Saya tidak terlalu senang dengan salah satu opsi yang tersedia jadi saya menulis bolus saya sendiri . Bolus dirancang untuk bekerja dengan kode yang ditulis dengan gaya DI di atas dan mencoba menjadi sangat KERING dan sangat sederhana. Dengan menggunakan modul car.jsdan horn.jsmodul yang sama persis di atas, Anda dapat menulis ulang index.jsmodul dengan bolus sebagai:

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

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

Ide dasarnya adalah Anda membuat injektor. Anda mendaftarkan semua modul Anda di injektor. Maka Anda cukup menyelesaikan apa yang Anda butuhkan. Bolus akan menjalankan grafik dependensi dan membuat serta menyuntikkan dependensi sesuai kebutuhan. Anda tidak menyimpan banyak dalam contoh mainan seperti ini, tetapi dalam aplikasi besar dengan pohon ketergantungan yang rumit, penghematannya sangat besar.

Bolus mendukung banyak fitur bagus seperti dependensi opsional dan uji global, tetapi ada dua manfaat utama yang saya lihat relatif terhadap pendekatan standar Node.js. Pertama, jika Anda memiliki banyak aplikasi serupa, Anda dapat membuat modul npm pribadi untuk basis Anda yang membuat injektor dan mendaftarkan objek yang berguna di dalamnya. Kemudian aplikasi spesifik Anda dapat menambah, menimpa, dan menyelesaikan sesuai kebutuhan seperti cara AngularJSinjektor bekerja. Kedua, Anda dapat menggunakan bolus untuk mengelola berbagai konteks dependensi. Misalnya, Anda bisa menggunakan middleware untuk membuat injektor anak per permintaan, mendaftarkan id pengguna, id sesi, logger, dll. Pada injektor bersama dengan modul apa pun tergantung pada mereka. Kemudian selesaikan apa yang Anda butuhkan untuk melayani permintaan. Ini memberi Anda contoh modul Anda per permintaan dan mencegah harus lulus logger, dll. Sepanjang setiap panggilan fungsi modul.


1
itu juga benar bahwa ada alternatif proxyquireseperti sinonyang memungkinkan Anda untuk melakukan ejekan yang sangat singkat, misalnya let readFileStub = sinon.stub(fs, 'readFile').yields(new Error('something went wrong'));dan kemudian panggilan berikutnya fs.readFileakan mengembalikan kesalahan sampai Anda mengembalikan rintisan melalui readFileStub.restore(). Secara pribadi saya menemukan DI penggunaan yang dipertanyakan karena saya merasa hampir memerlukan penggunaan kelas / objek, yang merupakan asumsi yang meragukan mengingat kecenderungan fungsional javascript.
Kevin

Terima kasih atas + detail terperinci ini. Saya hampir melewatkannya, ketika saya pertama kali membaca judul DI di C # .
Konstantin A. Magg

1
Jawaban yang bagus Aku bertanya-tanya apa pikiran Anda di 2019. Untuk proyek-proyek besar, sebagai masalah preferensi pribadi, mana yang Anda suka - DI / IOC di Node, atau hanya mematikan / mengejek dengan jest, rewire, proxyquire, dll? Terima kasih.
Jamie Corkhill

Jawaban seimbang yang bagus! Terima kasih.
Johnny Oshika

36

Saya juga menulis modul untuk menyelesaikan ini, ini disebut rewire . Cukup gunakan npm install rewiredan kemudian:

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

Saya terinspirasi oleh suntikan Nathan MacInnes tetapi menggunakan pendekatan yang berbeda. Saya tidak menggunakan vmeval modul-tes, pada kenyataannya saya menggunakan node sendiri membutuhkan. Dengan cara ini modul Anda berperilaku persis seperti menggunakan require()(kecuali modifikasi Anda). Juga debugging didukung sepenuhnya.


7
Pada pertengahan 2014 - npmjs.org/package/proxyquire membuat mengejek "memerlukan" dependensi sepele.
arcseldon

proxyquire juga keren! Mereka sekarang menggunakan "modul" internal -module yang jauh lebih baik daripada menggunakan simpul vm. Tapi bagaimanapun juga itu hanya masalah gaya. Saya suka modul saya untuk menggunakan persyaratan asli dan untuk menukar dependensi nanti. Selanjutnya rewire juga memungkinkan untuk menimpa global.
Johannes Ewald

Sangat menarik mencari sesuatu seperti ini untuk digunakan di tempat kerja, apakah modul ini mempengaruhi modul hilir juga?
akst

untuk proxyquire Dikatakan dalam deskripsi bahwa itu digunakan untuk pengujian, 'Proxy proxy diperlukan untuk memungkinkan menggantinya dependensi selama pengujian .' bukan untuk DI, kan?
Marwen Trabelsi

17

Saya membangun Elektrolit hanya untuk tujuan ini. Solusi injeksi ketergantungan lain di luar sana terlalu invasif untuk selera saya, dan mengacaukan dunia requireadalah keluhan saya yang khusus.

Elektrolit mencakup modul, khususnya yang mengekspor fungsi "pengaturan" seperti yang Anda lihat di Connect / Express middleware. Pada dasarnya, jenis modul ini hanyalah pabrik untuk beberapa objek yang mereka kembalikan.

Misalnya, modul yang membuat koneksi basis data:

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' ];

Apa yang Anda lihat di bagian bawah adalah anotasi , metadata ekstra yang digunakan Elektrolit untuk membuat instance dan menyuntikkan dependensi, secara otomatis menghubungkan komponen aplikasi Anda bersama-sama.

Untuk membuat koneksi database:

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

Elektrolit secara transitif melintasi @requiredependensi d, dan menyuntikkan instance sebagai argumen ke fungsi yang diekspor.

Kuncinya adalah ini invasif minimal. Modul ini sepenuhnya dapat digunakan, independen dari Elektrolit itu sendiri. Itu berarti unit test Anda dapat menguji hanya modul yang diuji , melewati benda tiruan tanpa perlu ketergantungan tambahan untuk memasang ulang internal.

Saat menjalankan aplikasi lengkap, Elektrolit masuk pada level antar-modul, menghubungkan berbagai hal tanpa perlu global, lajang atau pipa ledeng yang berlebihan.


1
Apakah Anda akan mengklarifikasi apa yang terjadi dalam kode yang Anda poskan ketika panggilan untuk connect()melempar? Meskipun saya tidak terbiasa dengan MySql API untuk Node, saya berharap panggilan ini tidak sinkron, sehingga ilustrasinya tidak begitu jelas.
Andrey Agibalov

saat ini menggunakan elektrolit. Anda mengklaim bahwa mudah untuk INJECT modul melalui ekspor ['@ wajib']. Tetapi jika saya harus mematikan salah satu modul yang diperlukan bagaimana itu bisa dicapai dalam elektrolit. Saat ini jika kita memerlukan modul ini dapat dengan mudah dicapai. Tetapi untuk elektrolit, ini adalah negatif yang sangat besar .... Apakah Anda memiliki contoh di mana kita dapat menggunakan modul versi stubbed dan meneruskannya secara dinamis saat instantiation / ioc.use dari kasus uji. Jadi pada dasarnya dalam unit test, jika kita dapat melakukan ioc.create ('modulename') dan kemudian melakukan injeksi modul-modul dependen (tetapi yang mematikan) akan ideal ...
user1102171

1
Anda tidak akan menelepon ioc.createdari unit test. Tes unit harus menguji hanya modul yang diuji, dan tidak membawa dependensi lain, termasuk Elektrolit. Mengikuti saran ini, Anda akan melakukannyaobjToTest = require('modulename')(mockObj1, mockObj2);
Jared Hanson

8

Saya melihat ini sendiri. Saya tidak suka memperkenalkan pustaka dependensi sulap utilitas yang menyediakan mekanisme untuk membajak impor modul saya. Sebaliknya saya datang dengan "pedoman desain" untuk tim saya untuk secara eksplisit menyatakan dependensi apa yang dapat diejek dengan memperkenalkan ekspor fungsi pabrik dalam modul saya.

Saya menggunakan banyak fitur ES6 untuk parameter dan perusakan untuk menghindari boilerplate dan menyediakan mekanisme override dependensi.

Berikut ini sebuah contoh:

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();

Dan berikut adalah contoh penggunaannya

import { factory } from './bar';

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

Maafkan sintaks ES6 bagi mereka yang tidak terbiasa dengannya.


Memang sangat cerdik!
Arnold

4

Saya baru-baru ini memeriksa utas ini dengan alasan yang hampir sama dengan OP - sebagian besar lib yang saya temui untuk sementara menulis ulang pernyataan yang diperlukan. Saya memiliki tingkat keberhasilan yang beragam dengan metode ini, dan akhirnya saya menggunakan pendekatan berikut.

Dalam konteks aplikasi ekspres - saya bungkus app.js dalam file 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();

Peta objek yang dilewatkan ke loader terlihat seperti ini:

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

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

Maka, daripada langsung menelepon membutuhkan ...

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

Jika tidak ada alias yang terletak di loader - maka hanya akan default ke kebutuhan reguler. Ini memiliki dua manfaat: Saya dapat menukar versi versi apa pun dari kelas, dan ini menghilangkan kebutuhan untuk menggunakan nama jalur relatif di seluruh aplikasi (jadi Jika saya memerlukan lib khusus di bawah atau di atas file saat ini, saya tidak perlu melintasi , dan akan meminta cache modul terhadap tombol yang sama). Ini juga memungkinkan saya untuk menentukan ejekan pada titik mana saja di aplikasi, daripada di suite tes langsung.

Saya baru saja menerbitkan modul npm kecil untuk kenyamanan:

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


3

Kenyataannya adalah bahwa Anda dapat menguji node.js Anda tanpa wadah IoC karena JavaScript adalah bahasa pemrograman yang benar-benar dinamis dan Anda dapat memodifikasi hampir semua hal saat dijalankan.

Pertimbangkan yang berikut ini:

import UserRepository from "./dal/user_repository";

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

export default UserController;

Jadi Anda bisa mengganti kopling antar komponen saat run-time. Saya suka berpikir bahwa kita harus bertujuan untuk memisahkan modul JavaScript kita.

Satu-satunya cara untuk mencapai decoupling nyata adalah dengan menghapus referensi ke UserRepository:

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

export default UserController;

Ini berarti bahwa di tempat lain Anda perlu melakukan komposisi objek:

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

export default new UserController(new UserRepository());

Saya suka ide mendelegasikan komposisi objek ke wadah IoC. Anda dapat mempelajari lebih lanjut tentang ide ini di artikel Keadaan terbalik ketergantungan saat ini di JavaScript . Artikel ini mencoba menyanggah beberapa "mitos wadah IoC JavaScript":

Mitos 1: Tidak ada tempat untuk wadah IoC di JavaScript

Mitos 2: Kami tidak membutuhkan wadah IOC, kami sudah memiliki pemuat modul!

Mitos 3: Ketergantungan inversi === ketergantungan injeksi

Jika Anda juga menyukai gagasan menggunakan wadah IOC Anda bisa melihat ke InversifyJS. Rilis terbaru (2.0.0) mendukung banyak kasus penggunaan:

  • Modul kernel
  • Middleware kernel
  • Gunakan kelas, string literal atau Simbol sebagai pengidentifikasi dependensi
  • Injeksi nilai konstan
  • Injeksi konstruktor kelas
  • Injeksi pabrik
  • Pabrik mobil
  • Injeksi penyedia (pabrik async)
  • Penangan aktivasi (digunakan untuk menyuntikkan proxy)
  • Multi injeksi
  • Binding Tagged
  • Dekorator tag khusus
  • Binding yang dinamai
  • Binding kontekstual
  • Pengecualian ramah (mis. Dependensi melingkar)

Anda dapat mempelajari lebih lanjut di InversifyJS .


2

Untuk ES6 saya mengembangkan wadah ini https://github.com/zazoomauro/node-dependency-injection

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

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

Kemudian Anda dapat mengatur, misalnya, pilihan transportasi dalam wadah:

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

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

Kelas ini sekarang jauh lebih fleksibel karena Anda telah memisahkan pilihan transportasi dari implementasi dan ke dalam wadah.

Sekarang layanan mailer ada di dalam kontainer, Anda dapat menyuntikkannya sebagai dependensi kelas lain. Jika Anda memiliki kelas NewsletterManager seperti ini:

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

export default NewsletterManager

Saat mendefinisikan layanan newsletter_manager, layanan mailer belum ada. Gunakan kelas Referensi untuk memberi tahu wadah untuk menyuntikkan layanan mailer ketika itu menginisialisasi manajer buletin:

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'))

Anda juga dapat mengatur wadah dengan file konfigurasi seperti file Yaml, Json atau JS

Wadah layanan dapat dikompilasi karena berbagai alasan. Alasan-alasan ini termasuk memeriksa untuk setiap masalah potensial seperti referensi melingkar dan membuat wadah lebih efisien.

container.compile()

1

Itu tergantung pada desain aplikasi Anda. Anda jelas dapat melakukan injeksi seperti java di mana Anda membuat objek kelas dengan dependensi yang diteruskan dalam konstruktor seperti ini.

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

var cache = new Cache(mysqlStore);

Jika Anda tidak melakukan OOP dalam javascript, Anda dapat membuat fungsi init yang mengatur semuanya.

Namun, ada pendekatan lain yang dapat Anda ambil yang lebih umum dalam sistem berbasis acara seperti node.js. Jika Anda dapat memodelkan aplikasi Anda untuk hanya (sebagian besar waktu) bertindak berdasarkan peristiwa maka yang perlu Anda lakukan adalah mengatur semuanya (yang biasanya saya lakukan dengan memanggil fungsi init) dan memancarkan peristiwa dari sebuah rintisan. Ini membuat pengujian lebih mudah dan mudah dibaca.


Terima kasih atas jawaban Anda, tetapi saya tidak sepenuhnya memahami bagian kedua dari jawaban Anda.
Erik

1

Saya selalu menyukai kesederhanaan konsep IoC - "Anda tidak perlu tahu apa-apa tentang lingkungan, Anda akan dipanggil oleh seseorang ketika dibutuhkan"

Tetapi semua implementasi IoC yang saya lihat melakukan sebaliknya - mereka mengacaukan kode dengan lebih banyak hal daripada tanpa itu. Jadi, saya membuat IoC saya sendiri yang berfungsi seperti yang saya inginkan - ia tetap tersembunyi dan tidak terlihat 90% dari waktu .

Ini digunakan dalam kerangka web MonoJS http://monojs.org

Saya berbicara tentang hal-hal sederhana, seperti berbagi objek koneksi database, sejauh ini, tetapi saya belum menemukan solusi yang memuaskan saya.

Ini dilakukan seperti ini - register komponen sekali dalam konfigurasi.

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

Dan gunakan di mana saja

app.db.findSomething()

Anda dapat melihat kode definisi komponen lengkap (dengan Koneksi DB dan Komponen lainnya) di sini https://github.com/sinizinairina/mono/blob/master/mono.coffee

Ini adalah satu-satunya tempat ketika Anda harus memberitahu IoC apa yang harus dilakukan, setelah itu semua komponen akan dibuat dan ditransfer secara otomatis dan Anda tidak perlu melihat kode spesifik IoC dalam aplikasi Anda lagi.

IoC itu sendiri https://github.com/alexeypetrushin/miconjs


6
Meskipun diiklankan sebagai DI, ini sepertinya lebih seperti pencari lokasi.
KyorCode

2
Tampak hebat, memalukan hanya dalam bentuk naskah
Rafael P. Miranda

1

Saya pikir kita masih memerlukan Ketergantungan Injeksi di Nodejs karena itu melonggarkan ketergantungan antara layanan dan membuat aplikasi lebih jelas.

Terinspirasi oleh Spring Framework , saya juga mengimplementasikan modul saya sendiri untuk mendukung injeksi dependensi di Nodejs. Modul saya juga dapat mendeteksi code changesdan auto reloadlayanan tanpa me-restart aplikasi Anda.

Kunjungi proyek saya di: Buncha - wadah IoC

Terima kasih!



0

Saya bekerja dengan .Net, PHP dan Java untuk waktu yang lama jadi saya ingin memiliki Dependency Injection yang nyaman di NodeJS juga. Orang-orang mengatakan DI built-in di NodeJS sudah cukup karena kita bisa mendapatkannya dengan Module. Tapi itu tidak memuaskan saya dengan baik. Saya ingin menyimpan Modul tidak lebih dari Kelas. Selain itu, saya ingin DI memiliki dukungan penuh untuk manajemen siklus hidup Modul (modul singleton, modul transient, dll.) Tetapi dengan modul Node, saya harus sering menulis kode manual. Terakhir, saya ingin membuat Unit Test lebih mudah. Itu sebabnya saya membuat Injeksi Ketergantungan untuk diri saya sendiri.

Jika Anda mencari DI, cobalah. Itu dapat ditemukan di sini: https://github.com/robo-creative/nodejs-robo-container . Ini sepenuhnya didokumentasikan. Ini juga membahas beberapa masalah umum dengan DI dan cara menyelesaikannya dengan cara OOP. Semoga ini bisa membantu.


Ya Anda benar, perpustakaan DI dalam proyek Anda penting untuk arsitektur yang baik, Jika Anda ingin melihat use case untuk DI, lihat readme untuk repositori ini juga perpustakaan DI untuk node Jems DI .
Francisco Mercedes

-1

Saya baru-baru ini membuat perpustakaan bernama circuitbox yang memungkinkan Anda untuk menggunakan dependensi-injeksi dengan node.js. Itu benar ketergantungan-injeksi vs banyak perpustakaan berbasis dependensi-lookup yang telah saya lihat. Circuitbox juga mendukung kreasi dan sinkronisasi inisialisasi asinkron. Di bawah ini adalah contohnya:

Asumsikan kode berikut ini dalam file yang disebut 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;

Asumsikan yang berikut ini ada di file 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 memungkinkan Anda mendefinisikan komponen Anda dan mendeklarasikan dependensinya sebagai modul. Setelah diinisialisasi, ini memungkinkan Anda untuk mengambil komponen. Circuitbox secara otomatis menyuntikkan semua komponen yang dibutuhkan komponen target dan memberikannya kepada Anda untuk digunakan.

Proyek ini dalam versi alpha. Komentar, ide, dan umpan balik Anda dipersilakan.

Semoga ini bisa membantu!


-1

Saya pikir posting lain telah melakukan pekerjaan yang bagus dalam argumen untuk menggunakan DI. Bagi saya alasannya adalah

  1. Suntikkan dependensi tanpa mengetahui jalurnya. Ini berarti bahwa jika Anda mengubah lokasi modul pada disk atau bertukar dengan yang lain, Anda tidak perlu menyentuh setiap file yang bergantung padanya.

  2. Itu membuatnya jauh lebih mudah untuk mengejek dependensi untuk pengujian tanpa rasa sakit mengesampingkan requirefungsi global dengan cara yang bekerja tanpa masalah.

  3. Ini membantu Anda mengatur dan alasan tentang aplikasi Anda sebagai modul yang digabungkan secara longgar.

Tetapi saya mengalami kesulitan menemukan kerangka DI yang dapat saya dan tim saya adopsi dengan mudah. Jadi saya baru-baru ini membangun kerangka kerja yang disebut deppie berdasarkan fitur-fitur ini

  • API minimal yang dapat dipelajari dalam beberapa menit
  • Tidak diperlukan kode / konfigurasi / anotasi tambahan
  • Pemetaan langsung ke requiremodul
  • Dapat diadopsi sebagian untuk bekerja dengan kode yang ada

-1

Itu harus fleksibel dan sederhana seperti ini:

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


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

Saya telah menulis artikel tentang Dependency Injection di node.js.

Saya harap ini dapat membantu Anda dengan ini.


-1

Node.js membutuhkan DI sebanyak platform lainnya. Jika Anda membangun sesuatu yang besar, DI akan membuatnya lebih mudah untuk mengejek dependensi kode Anda dan menguji kode Anda secara menyeluruh.

Modul-modul layer database Anda misalnya, tidak seharusnya hanya diperlukan di modul kode bisnis Anda karena, ketika unit menguji modul-modul kode bisnis ini, daos akan dimuat dan terhubung ke database.

Salah satu solusinya adalah dengan melewatkan dependensi sebagai parameter modul:

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

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

Dengan cara ini dependensi dapat diejek dengan mudah dan alami dan Anda dapat tetap fokus pada pengujian kode Anda, tanpa menggunakan pustaka pihak ke-3 yang rumit.

Ada solusi lain di luar sana (broadway, arsitek dll) yang dapat membantu Anda dengan ini. meskipun mereka dapat melakukan lebih dari apa yang Anda inginkan atau menggunakan lebih banyak kekacauan.


Hampir melalui evolusi alami saya akhirnya melakukan hal yang sama. Saya melewatkan dependensi sebagai parameter dan berfungsi bagus untuk pengujian.
munkee

-1

Saya telah mengembangkan perpustakaan yang menangani injeksi ketergantungan dengan cara sederhana, yang mengurangi kode boilerplate. Setiap modul ditentukan oleh nama unik dan fungsi pengontrol. Parameter controller mencerminkan dependensi modul.

Baca lebih lanjut tentang KlarkJS

Contoh singkat:

KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
    return {
        log: function() { console.log('Hello from module myModuleName1') }
    };
});
  • myModuleName1 adalah nama modul.
  • $nodeModule1adalah perpustakaan eksternal dari node_module. Nama memutuskan untuk node-module1. Awalan $menunjukkan bahwa itu adalah modul eksternal.
  • myModuleName2 adalah nama modul internal.
  • Nilai pengembalian controller digunakan dari modul internal lainnya, ketika mereka menentukan parameter myModuleName1.

-1

Saya menemukan pertanyaan ini ketika menjawab masalah pada modul DI saya sendiri dan bertanya mengapa seseorang membutuhkan sistem DI untuk pemrograman NodeJS.

Jawabannya jelas cenderung pada yang diberikan di utas ini: itu tergantung. Ada trade-off untuk kedua pendekatan dan membaca jawaban pertanyaan ini memberikan bentuk yang baik dari mereka.

Jadi, jawaban sebenarnya untuk pertanyaan ini, semestinya dalam beberapa situasi, Anda akan menggunakan sistem DI, di lain tidak.

Yang mengatakan, apa yang Anda inginkan sebagai pengembang adalah tidak mengulangi sendiri dan menggunakan kembali layanan Anda di berbagai aplikasi Anda.

Ini berarti bahwa kita harus menulis layanan yang siap digunakan dalam sistem DI tetapi tidak terikat pada perpustakaan DI. Bagi saya, itu berarti bahwa kita harus menulis layanan seperti ini:

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
}

Dengan begitu layanan Anda berfungsi tidak masalah jika Anda menggunakannya dengan atau tanpa alat DI.


-1

TypeDI adalah yang paling manis dari semua yang disebutkan di sini, lihat kode ini di TypeDI

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

@Service()
class SomeClass {

    someMethod() {
    }

}

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

Lihat kode ini juga:

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;

}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.