NodeJS'de bağımlılık enjeksiyonuna ihtiyacım var mı yoksa nasıl başa çıkılır?


220

Şu anda nodej'lerle bazı deneysel projeler üretiyorum. Spring ile çok sayıda Java EE web uygulaması programladım ve orada bağımlılık enjeksiyon kolaylığını takdir ettim.

Şimdi merak ediyorum: Düğümle nasıl bağımlılık enjeksiyonu yapabilirim? Veya: Hatta ihtiyacım var mı? Programlama tarzı farklı olduğu için değiştirilecek bir kavram var mı?

Şimdiye kadar bir veritabanı bağlantı nesnesini paylaşmak gibi basit şeylerden bahsediyorum, ama beni tatmin edecek bir çözüm bulamadım.


1
DI kullanmaya karar verirseniz, OpenTable kısa süre önce onun için bir kütüphane açtı : github.com/opentable/spur-ioc Onu kullandım (orada çalışıyorum) ve test için oldukça basit ve harika olduğunu söyleyebiliriz.
tybro0103

Yanıtlar:


108

Kısacası, C # / Java'da yaptığınız gibi bir bağımlılık enjeksiyon kabına veya servis noktasına ihtiyacınız yoktur. Node.js'den dolayı module pattern, yapıcı veya özellik enjeksiyonu yapmak gerekli değildir. Yine de yapabilirsin.

JS ile ilgili en güzel şey, istediğinizi elde etmek için hemen hemen her şeyi değiştirebilmenizdir. Bu, test söz konusu olduğunda kullanışlıdır.

Bakalım topallamış örnek.

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

Modüle nasıl MyClassbağlı olduğuna dikkat edin fs? @ShatyemShekhar'ın belirttiği gibi, gerçekten diğer dillerde olduğu gibi yapıcı veya özellik enjeksiyonu yapabilirsiniz. Ancak Javascript'te gerekli değildir.

Bu durumda, iki şey yapabilirsiniz.

fs.readdirSyncYöntemi saplayabilir veya aradığınızda tamamen farklı bir modül döndürebilirsiniz require.

Yöntem 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;

Yöntem 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);

}

Anahtar, Node.js ve Javascript'in gücünden yararlanmaktır. Ben bir CoffeeScript erkeğiyim, bu yüzden JS sözdizimim bir yerde yanlış olabilir. Ayrıca, bunun en iyi yol olduğunu söylemiyorum, ama bir yol. Javascript gurus diğer çözümlerle uyum sağlayabilir.

Güncelleme:

Bu, veritabanı bağlantıları ile ilgili sorunuza cevap vermelidir. Ben veritabanı bağlantı mantığı kapsüllemek için ayrı bir modül oluşturmak istiyorum. Bunun gibi bir şey:

MyDbConnection.js: (daha iyi bir isim seçtiğinizden emin olun)

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    
}

Daha sonra, veritabanı bağlantısına ihtiyaç duyan herhangi bir modül sadece MyDbConnectionmodülünüzü içerecektir .

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. 

Bu örneği aynen takip etmeyin. moduleBağımlılıklarınızı yönetmek için kalıptan yararlandığınızı bildirmeye çalışırken topal bir örnek . Umarım bu biraz daha yardımcı olur.


42
Bu test açısından doğrudur, ancak DI'nin başka faydaları da vardır; DI'yi kullanarak bir uygulamaya değil, bir arabirime programlayabilirsiniz.
moteutsch

3
@moteutsch JS'nin çoğu statik dil gibi arabirim kavramına sahip olmadığından neden emin değilsiniz. Belgelendirilmiş "arayüz" üzerinde önceden kararlaştırılmış bazılarını kullanmak isteseniz bile, gerçekten sahip olduğunuz uygulamalar.
JP Richardson

16
@JPRichardson Herhangi bir kütüphaneye bağlı kalmadan bir kayıt cihazı kullanan bir bileşeni nasıl yazabilirim? Ben ise require('my_logger_library'), bileşenimi kullanan kişilerin kendi kitaplıklarını kullanma gereksinimlerini geçersiz kılması gerekir. Bunun yerine, insanların bir logger uygulamasını "yapıcı" veya "init" yöntemine saran bir geri arama iletmesine izin verebilirim. DI'nin amacı budur.
moteutsch

4
2014 ortasından itibaren - npmjs.org/package/proxyquire alay etmeyi “zorunlu” bağımlılıkların önemsiz kılmaktadır.
arcseldon

4
Anlamadım, bir modüldeki gereksinimin değiştirilmesi başka bir modülün yerine geçmez. Testimde bir fonksiyona ihtiyaç duyduğumu ve daha sonra modülün test edilmesini gerektirdiğimde, test edilecek nesnedeki zorunlu ifadeler test modülünde ayarlanan fonksiyonu kullanmayın. Bu bağımlılıkları nasıl enjekte eder?
HMR

73

requireolduğu node.js içinde bağımlılıkları yönetme yolu ve mutlaka bu sezgisel ve etkilidir, ama aynı zamanda kendi sınırlamaları vardır.

Benim tavsiyem, Node.js'nin kendi artıları / eksileri hakkında bir fikir sahibi olması için bugün mevcut olan Bağımlılık Enjeksiyon kaplarından bazılarına göz atmaktır. Onlardan bazıları:

Sadece birkaç isim.

Şimdi asıl soru, bir Node.js DI konteyneriyle bir basitle karşılaştırıldığında ne elde edebilirsiniz require?

Artıları:

  • daha iyi test edilebilirlik: modüller bağımlılıklarını girdi olarak kabul eder
  • Kontrolün Ters Çevrilmesi: uygulamanızın ana koduna dokunmadan modüllerinizi nasıl bağlayacağınıza karar verin.
  • modülleri çözmek için özelleştirilebilir bir algoritma: bağımlılıkların "sanal" tanımlayıcıları vardır, genellikle dosya sistemindeki bir yola bağlı değildirler.
  • Daha iyi genişletilebilirlik: IoC ve "sanal" tanımlayıcılar tarafından etkinleştirilir.
  • Diğer süslü şeyler mümkün:
    • Zaman uyumsuz başlatma
    • Modül yaşam döngüsü yönetimi
    • DI kabının kendisinin genişletilebilirliği
    • Üst düzey soyutlamaları kolayca uygulayabilir (örneğin AOP)

Eksileri:

  • Node.js "deneyiminden" farklı: requirekesinlikle kullanmamanız , Düğüm düşünme tarzından sapmış gibi hissettirir.
  • Bir bağımlılık ve uygulanması arasındaki ilişki her zaman açık değildir. Bir bağımlılık çalışma zamanında çözülebilir ve çeşitli parametrelerden etkilenebilir. Kodun anlaşılması ve hatalarının ayıklanması zorlaşıyor
  • Yavaş başlatma süresi
  • Olgunluk (şu anda): şu anki çözümlerin hiçbiri şu anda gerçekten popüler değil, bu yüzden çok fazla öğretici değil, ekosistem yok, savaş test edilmedi.
  • Bazı DI kapları, Browserify ve Webpack gibi modül paketleyicileriyle iyi oynamaz.

Yazılım geliştirmeyle ilgili her şeyde olduğu gibi, DI veya arasında seçim yapmak requiregereksinimlerinize, sistem karmaşıklığınıza ve programlama stilinize bağlıdır.


3
Sizce durum '09'dan beri önemli ölçüde değişti mi?
Juho Vepsäläinen

13
10 gün önce mi demek istiyorsun? :)
Mario

2
Nooo. 9 Aralık ... Bilmeliydim.
Juho Vepsäläinen

4
Module.exports = function (deps) {} türünü kullanarak DI'yi "uyguladım". Evet, işe yarıyor, ama pek ideal değil.
Juho Vepsäläinen

3
modüller bağımlılıklarını girdi olarak kabul eder ve Bağımlılıklar bana bir çelişki gibi açık sesler değildir .
Anton Rudeshko

53

Bu iş parçacığının bu noktada oldukça eski olduğunu biliyorum, ama bu konudaki düşüncelerimle karşılaşacağımı düşündüm. TL; DR, JavaScript'in türlenmemiş, dinamik doğası nedeniyle, bağımlılık enjeksiyon (DI) desenine başvurmadan veya bir DI çerçevesi kullanmadan gerçekten çok şey yapabileceğinizdir. Ancak, bir uygulama büyüdükçe ve karmaşıklaştıkça, DI kesinlikle kodunuzun korunmasına yardımcı olabilir.

C # 'de DI

DI'nin neden JavaScript'te bir ihtiyaç kadar büyük olmadığını anlamak için, C # gibi güçlü yazılan bir dile bakmak faydalıdır. (C # bilmeyenler özür dileriz, ama takip etmek yeterince kolay olmalı.) Bir araba ve boynuzu açıklayan bir uygulamamız olduğunu varsayalım. İki sınıf tanımlarsınız:

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

Kodu bu şekilde yazmanın birkaç sorunu vardır.

  1. CarSınıf sıkıca boynuzun özel uygulama ile bağlanmıştır Hornsınıfı. Arabanın kullandığı korna türünü değiştirmek istiyorsak, korna Carkullanımı değişmese bile sınıfı değiştirmek zorundayız . Bu ayrıca testi zorlaştırır çünkü Carsınıfı bağımlılığından, Hornsınıftan ayrı olarak test edemeyiz .
  2. CarSınıf yaşam döngüsü sorumludur Hornsınıfın. Bunun gibi basit bir örnekte bu büyük bir sorun değildir, ancak gerçek uygulamalarda bağımlılıkların bağımlılıkları, vb. CarOlacak bağımlılıkları olacaktır. Bu sadece karmaşık ve tekrarlayıcı olmakla kalmaz, aynı zamanda sınıfın “tek sorumluluğunu” da ihlal eder. Örnek oluşturmaya değil, araba olmaya odaklanmalıdır.
  3. Aynı bağımlılık örneklerini yeniden kullanmanın bir yolu yoktur. Yine, bu oyuncak uygulamasında önemli değildir, ancak bir veritabanı bağlantısını düşünün. Genellikle uygulamanızda paylaşılan tek bir örneğiniz olur.

Şimdi bir bağımlılık enjeksiyon modeli kullanmak için bunu yeniden düzenleyelim.

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

Burada iki önemli şey yaptık. İlk olarak, Hornsınıfımızın uyguladığı bir arayüz geliştirdik. Bu, Carsınıfı belirli bir uygulama yerine arabirime kodlamamızı sağlar. Şimdi kod uygulayan her şeyi alabilir IHorn. İkincisi, korna örneklemesini çıkardık Carve yerine geçtik . Bu, yukarıdaki sorunları çözer ve belirli örnekleri ve yaşam döngülerini yönetmek için uygulamanın ana işlevine bırakır.

Bunun anlamı, araca Carsınıfa dokunmadan kullanmak için yeni bir boynuz türü getirebilmesidir :

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

Main FrenchHornbunun yerine sınıfın bir örneğini enjekte edebilir . Bu aynı zamanda testi önemli ölçüde basitleştirir. Yalnızca MockHornsınıfı Cartek başına test ettiğinizden emin olmak için yapıcıya enjekte edilecek bir sınıf oluşturabilirsiniz Car.

Yukarıdaki örnek, manuel bağımlılık enjeksiyonunu göstermektedir. Tipik olarak DI bir çerçeve ile yapılır (örneğin C # dünyasında Unity veya Ninject ). Bu çerçeveler, bağımlılık grafiğinizi yürüterek ve gerektiği gibi örnekler oluşturarak sizin için tüm bağımlılık kablolarını yapacaktır.

Standart Node.js Yolu

Şimdi Node.js'deki aynı örneğe bakalım. Muhtemelen kodumuzu 3 modüle böleriz:

// 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 türetilmediğinden, daha önce sahip olduğumuzla aynı sıkı bağlantıya sahip değiliz. carModül, sadece modülün dışa honkaktardığı yöntemle ilgili yöntemi çağırmaya çalışacağından, arabirimlere gerek yoktur (bunlar da yoktur) horn.

Ayrıca, Düğümün requireher şeyi önbelleğe aldığı için, modüller aslında bir kapta saklanan tekil düğmelerdir. Bir gerçekleştiren Başka modül requireüzerinde hornmodül aynı örneği alacak. Bu, veritabanı bağlantıları gibi tek nesnelerin paylaşılmasını çok kolaylaştırır.

Şimdi hala carmodülün kendi bağımlılığını getirmekten sorumlu olduğu sorunu var horn. Arabanın kornası için farklı bir modül kullanmasını istiyorsanız require, carmodüldeki ifadeyi değiştirmeniz gerekir . Bu çok yaygın bir şey değildir, ancak test ile ilgili sorunlara neden olur.

İnsanların test problemini ele alma alışkanlığı proxyquire'dir . JavaScript'in dinamik doğası nedeniyle proxyquire, çağrıları gerektirecek çağrıları keser ve bunun yerine sağladığınız saplamaları / alayları döndürür.

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

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

// Now make test assertions on car...

Bu, çoğu uygulama için fazlasıyla yeterlidir. Uygulamanız için çalışıyorsa, onunla devam edin. Ancak, deneyimlerime göre, uygulamalar büyüdükçe ve daha karmaşık hale geldikçe, bunun gibi kodların korunması zorlaşmaktadır.

JavaScript'te DI

Node.js çok esnektir. Yukarıdaki yöntemden memnun değilseniz, bağımlılık enjeksiyon desenini kullanarak modüllerinizi yazabilirsiniz. Bu modelde her modül bir fabrika işlevini (veya bir sınıf yapıcısını) dışa aktarır.

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

Bu, index.jsmodülün örneğin yaşam döngüleri ve kablolamadan sorumlu olması nedeniyle C # yöntemine çok benzer . Ünite testleri oldukça basittir, çünkü işlevlere alayları / saplamaları geçirebilirsiniz. Yine, bu uygulama için yeterince iyi ise onunla gitmek.

Bolus DI Çerçevesi

C # 'dan farklı olarak, bağımlılık yönetiminize yardımcı olacak yerleşik bir standart DI çerçevesi yoktur. Npm kayıt defterinde birkaç çerçeve vardır, ancak hiçbirinin yaygın olarak benimsenmesi yoktur. Bu seçeneklerin birçoğu diğer yanıtlarda belirtilmiştir.

Mevcut seçeneklerden herhangi biriyle özellikle mutlu değildim, bu yüzden kendi bolus'umu yazdım . Bolus, yukarıdaki DI tarzında yazılmış kodla çalışacak şekilde tasarlanmıştır ve çok KURU ve çok basit olmaya çalışır . Yukarıdaki modül car.jsve horn.jsmodüllerin aynısını kullanarak, index.jsmodülü bolus ile aşağıdaki gibi yeniden yazabilirsiniz :

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

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

Temel fikir, bir enjektör oluşturmanızdır. Tüm modüllerinizi enjektöre kaydedersiniz. Sonra ihtiyacınız olanı çözersiniz. Bolus bağımlılık grafiğinde yürüyecek ve gerektiğinde bağımlılıklar yaratacak ve enjekte edecektir. Böyle bir oyuncak örneğinde fazla tasarruf yapmazsınız, ancak karmaşık bağımlı ağaçlara sahip büyük uygulamalarda tasarruflar çok büyüktür.

Bolus, isteğe bağlı bağımlılıklar ve test globalleri gibi bir dizi şık özelliği destekler, ancak standart Node.js yaklaşımına göre gördüğüm iki önemli avantaj vardır. İlk olarak, çok sayıda benzer uygulamanız varsa, tabanınız için bir enjektör oluşturan ve üzerine yararlı nesneler kaydeden özel bir npm modülü oluşturabilirsiniz. Ardından, belirli uygulamalarınız AngularJS'lerin yaptığı gibi gerektiği gibi ekleyebilir, geçersiz kılabilir ve çözülebilirenjektör çalışır. İkinci olarak, çeşitli bağımlılık bağlamlarını yönetmek için bolus kullanabilirsiniz. Örneğin, isteğe bağlı olarak bir alt enjektör oluşturmak, kullanıcı kimliğini, oturum kimliğini, kaydediciyi vb. Bunlara bağlı olarak herhangi bir modülle birlikte enjektöre kaydetmek için ara katman yazılımı kullanabilirsiniz. Ardından, istekleri sunmak için gerekeni çözün. Bu, istek başına modüllerinizin örneklerini verir ve her modül işlev çağrısı boyunca kaydediciyi vb. Geçirmeyi engeller.


1
aynı zamanda çok özlü alaylar yapmanıza izin veren proxyquiregibi bir alternatif olduğu da doğrudur sinon, örn. let readFileStub = sinon.stub(fs, 'readFile').yields(new Error('something went wrong'));ve daha sonra yapılan çağrılar fs.readFile, saplamayı geri döndürene kadar hata döndürecektir readFileStub.restore(). Şahsen şüpheli kullanım DI'sini buluyorum çünkü neredeyse sınıfların / nesnelerin kullanımını gerektirdiğini hissediyorum, bu da javascriptin fonksiyonel eğilimleri verildiğinde şüpheli bir varsayım.
Kevin

Bu iyi ve ayrıntılı cevap için teşekkürler. C # 'da DI başlığını ilk okuduğumda neredeyse kaçırmıştım .
Konstantin A.Magg

1
Mükemmel cevap. Ben düşüncelerinizi tercih edersiniz ki, kişisel bir tercih meselesi olarak, büyük projeler için 2019 yılında ne merak ediyorum - DI / IoC Düğümünde, ya da sadece alay / stubbing jest, rewire, proxyquirevb? Teşekkürler.
Jamie Corkhill

Mükemmel dengeli cevap! Teşekkür ederim.
Johnny Oshika

36

Bunu gerçekleştirmek için bir modül de yazdım, buna rewire deniyor . Sadece kullanın npm install rewireve sonra:

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

Nathan MacInnes'in enjeksiyonundan ilham aldım ama farklı bir yaklaşım kullandım. Ben kullanmıyorum vmben düğüm kendi gerektirir kullanmak aslında, test modülü eval. Bu şekilde modülünüz tam olarak kullanmak gibi davranır require()(değişiklikleriniz hariç). Ayrıca hata ayıklama tamamen desteklenir.


7
2014 ortasından itibaren - npmjs.org/package/proxyquire alay etmeyi “zorunlu” bağımlılıkların önemsiz kılmaktadır.
arcseldon

proxyquire de havalı! Şimdi düğümün vm'sini kullanmaktan çok daha iyi olan dahili "modül" modülünü kullanıyorlar. Ama sonuçta bu sadece bir stil meselesi. Modülümün orijinal gereksinimi kullanmasını ve bağımlılıkları daha sonra değiştirmesini seviyorum. Ayrıca rewire globalleri geçersiz kılmaya da izin verir.
Johannes Ewald

İş yerinde kullanmak için böyle bir şey çok ilginçti, bu modül aşağı akım modüllerini de etkiliyor mu?
akst

için proxyquire O test için kullanılan bu açıklamasında söylenir, 'Proxy nodejs sırasında bağımlılıkları geçersiz kılma sağlamak için ihtiyaç test .' DI için değil, değil mi?
Marwen Trabelsi

17

Elektrolit'i sadece bu amaçla inşa ettim . Diğer bağımlılık enjeksiyon çözümleri zevklerim için çok invazivdi ve küresel requireile uğraşmak benim için özel bir sıkıntı.

Elektrolit, özellikle Connect / Express ara katman yazılımında gördüğünüz gibi bir "kurulum" işlevini dışa aktarılan modülleri içerir. Esasen, bu tür modüller geri döndükleri bazı nesneler için fabrikalardır.

Örneğin, veritabanı bağlantısı oluşturan bir modül:

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

Altta gördüğünüz ek açıklamalar , Elektrolitin bağımlılıkları başlatmak ve enjekte etmek için kullandığı, uygulamanızın bileşenlerini otomatik olarak birbirine bağlayan ekstra bir meta veri parçasıdır.

Bir veritabanı bağlantısı oluşturmak için:

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

Elektrolit @require'd bağımlılıklarını geçişli olarak geçer ve örnekleri dışa aktarılan işleve bağımsız değişkenler olarak enjekte eder.

Anahtar, bunun minimal invaziv olmasıdır. Bu modül tamamen kullanılabilir, Elektrolitin kendisinden bağımsızdır. Bu, birim testlerinizin yalnızca test edilen modülü test edebileceği ve dahili kabloları yeniden bağlamak için ek bağımlılıklara gerek kalmadan sahte nesnelere geçebileceği anlamına gelir .

Tam uygulamayı çalıştırırken, Elektrolit modüller arası seviyede devreye girer, küresellere, tektonlara veya aşırı sıhhi tesisatlara gerek kalmadan işleri birbirine bağlar.


1
Atma çağrısı olduğunda koyduğunuz kodda ne olduğunu connect()açıklar mısınız? Düğüm için MySql API'sine aşina olmasam da, bu çağrının eşzamansız olmasını bekliyorum, bu yüzden illüstrasyon oldukça net değil.
Andrey Agibalov

şu anda elektrolit kullanıyor. Dışa aktarma yoluyla modülleri ENJECT etmenin kolay olduğunu iddia edersiniz ['@ requir']. Ancak gerekli modüllerden birini saplamam gerekirse elektrolitte nasıl elde edilebilir. Şu anda modüllere ihtiyaç duyduğumuzda bu kolayca gerçekleştirilebilir. Ancak elektrolit için bu çok büyük bir olumsuzluktur .... Test durumlarından örnekleme / ioc.use yaparken modüllerin stubbed sürümünü kullanabileceğimiz ve dinamik olarak geçirebileceğimiz örnekleriniz var mı? Temelde birim testinde, eğer ioc.create ('modulename') yapabilir ve daha sonra bağımlı modüllerin enjeksiyonu yapabilirsek (ama inatçı olanlar) idealdir ...
user1102171

1
ioc.createBirim testinden arama yapmazsınız. Birim testi, yalnızca test edilen modülü test etmeli ve Elektrolit dahil diğer bağımlılıkları getirmemelidir. Bu tavsiyeyi objToTest = require('modulename')(mockObj1, mockObj2);
Jared Hanson

8

Buna kendim baktım. Sihirli bağımlılığı tanıtmaktan hoşlanmıyorum, modül ithalatımı kaçırmak için mekanizmalar sağlayan kütüphaneler kullanıyor. Bunun yerine, takımımın, modüllerimde bir fabrika işlev ihracatı getirerek hangi bağımlılıkların alay edilebileceğini açıkça belirtmesi için bir "tasarım kılavuzu" buldum.

Bazı kazan levhalarından kaçınmak ve adlandırılmış bir bağımlılık geçersiz kılma mekanizması sağlamak için ES6 özelliklerini parametreler ve yıkım için kapsamlı bir şekilde kullanıyorum.

İşte bir örnek:

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

Ve işte bunun kullanımına bir örnek

import { factory } from './bar';

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

ES6 sözdizimini bilmeyenler için özür dileriz.


Gerçekten çok ustaca!
Arnold

4

Geçenlerde OP ile aynı nedenden dolayı bu konuyu kontrol ettim - karşılaştığım kütüphanelerin çoğu geçici olarak zorunlu ifadeyi yeniden yazdı. Bu yöntemle karışık derecelerde başarı elde ettim ve bu yüzden aşağıdaki yaklaşımı kullandım.

Hızlı bir uygulama bağlamında - bir bootstrap.js dosyasında app.js dosyasını sararım:

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

Yükleyiciye aktarılan nesne haritası şuna benzer:

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

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

Sonra, doğrudan aramak yerine gerektirir ...

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

Yükleyicide herhangi bir takma ad bulunmuyorsa - varsayılan olarak normal bir gereksinim olacaktır. Bunun iki faydası vardır: Sınıfın herhangi bir sürümünde takas edebilirim ve uygulama boyunca göreli yol adlarını kullanma ihtiyacını kaldırabilirim (bu nedenle geçerli dosyanın altında veya üstünde özel bir lib gerekiyorsa, geçiş yapmam gerekmez ve requir modülü aynı anahtarla önbelleğe alır). Ayrıca, anında test paketinden ziyade, uygulamanın herhangi bir noktasında alayları belirtmeme izin veriyor.

Kolaylık sağlamak için küçük bir npm modülü yayınladım:

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


3

Gerçek şu ki, node.js'nizi IoC kapsayıcısı olmadan test edebilirsiniz, çünkü JavaScript gerçekten dinamik bir programlama dilidir ve hemen hemen her şeyi çalışma zamanında değiştirebilirsiniz.

Aşağıdakileri göz önünde bulundur:

import UserRepository from "./dal/user_repository";

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

export default UserController;

Böylece çalışma zamanında bileşenler arasındaki bağlantıyı geçersiz kılabilirsiniz. JavaScript modüllerini ayırmayı amaçlamamız gerektiğini düşünüyorum.

Gerçek ayırma elde etmenin tek yolu, aşağıdakilere yapılan referansı kaldırmaktır UserRepository:

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

export default UserController;

Bu, başka bir yerde nesne kompozisyonunu yapmanız gerektiği anlamına gelir:

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

export default new UserController(new UserRepository());

Nesne kompozisyonunu bir IoC konteynerine devretme fikrini seviyorum. Bu fikir hakkında daha fazla bilgiyi JavaScript'te geçerli bağımlılık tersine çevirme makalesinde bulabilirsiniz . Makale, bazı "JavaScript IoC kapsayıcı mitleri" ni kaldırmaya çalışıyor:

Efsane 1: JavaScript'te IoC kapsayıcıları için yer yok

Efsane 2: IoC konteynerlerine ihtiyacımız yok, zaten modül yükleyicilerimiz var!

Efsane 3: Bağımlılık evrimi === bağımlılık enjekte etme

IoC kapsayıcısı kullanma fikrinden de hoşlanıyorsanız, InversifyJS'ye göz atabilirsiniz. En son sürüm (2.0.0) birçok kullanım durumunu desteklemektedir:

  • Çekirdek modülleri
  • Çekirdek ara katman yazılımı
  • Bağımlılık tanımlayıcıları olarak sınıfları, dize değişmezlerini veya Sembolleri kullanma
  • Sabit değerlerin enjeksiyonu
  • Sınıf yapıcılarının enjeksiyonu
  • Fabrika enjeksiyonu
  • Otomobil fabrikası
  • Tedarikçilerin enjeksiyonu (zaman uyumsuz fabrika)
  • Aktivasyon işleyicileri (vekil enjekte etmek için kullanılır)
  • Çoklu enjeksiyonlar
  • Etiketli ciltler
  • Özel etiket dekoratörleri
  • Adlandırılmış ciltlemeler
  • Bağlamsal bağlamalar
  • Kolay istisnalar (örn. Dairesel bağımlılıklar)

Bu konuda InversifyJS'den daha fazla bilgi edinebilirsiniz .


2

ES6 için bu kapsayıcıyı geliştirdim https://github.com/zazoomauro/node-dependency-injection

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

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

Daha sonra, örneğin, konteynerdeki taşıma seçimini ayarlayabilirsiniz:

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

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

Bu sınıf, artık uygulamadan konteynere taşıma seçimini ayırdığınız için çok daha esnektir.

Artık postalama hizmeti konteynerde olduğundan, diğer sınıflara bağımlı olarak enjekte edebilirsiniz. Bunun gibi bir NewsletterManager sınıfınız varsa:

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

export default NewsletterManager

Newsletter_manager hizmetini tanımlarken, posta hizmeti henüz mevcut değildir. Referans sınıfını kullanarak, kapsama bülten yöneticisini başlattığında posta hizmetini enjekte etmesini söyleyin:

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

Kapsayıcıyı Yaml, Json veya JS dosyaları gibi yapılandırma dosyalarıyla da kurabilirsiniz

Servis kabı çeşitli nedenlerle derlenebilir. Bu nedenler arasında dairesel referanslar gibi olası sorunların kontrol edilmesi ve kabın daha verimli hale getirilmesi yer alır.

container.compile()

1

Uygulamanızın tasarımına bağlıdır. Açıkçası böyle bir yapıcı içinde geçirilen bağımlılık ile bir sınıf nesnesi oluşturduğunuz bir java benzeri enjeksiyon yapabilirsiniz.

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

var cache = new Cache(mysqlStore);

Javascript'te OOP yapmıyorsanız, her şeyi ayarlayan bir init işlevi yapabilirsiniz.

Ancak, node.js gibi olay tabanlı bir sistemde daha yaygın olarak kullanabileceğiniz başka bir yaklaşım daha vardır. Uygulamayı yalnızca (çoğu zaman) olaylar üzerinde hareket edecek şekilde modelleyebiliyorsanız, yapmanız gereken tek şey (genellikle bir init işlevini çağırarak yaptığım) her şeyi ayarlamak ve bir saplamadan olaylar yayınlamaktır. Bu, testi oldukça kolay ve okunabilir hale getirir.


Cevabınız için teşekkürler, ancak cevabınızın ikinci bölümünü tam olarak anlamıyorum.
Erik

1

IoC konseptinin sadeliğini hep sevdim - "Çevre hakkında hiçbir şey bilmek zorunda değilsiniz, ihtiyaç duyduğunuzda biri tarafından aranacaksınız"

Ancak gördüğüm tüm IoC uygulamaları tam tersini yaptı - kodu onsuzdan daha fazla şeyle karıştırıyorlar. Böylece, istediğim gibi çalışan kendi IoC'mi yarattım - zamanın% 90'ında gizli ve görünmez kalır .

MonoJS web çerçevesinde kullanılır http://monojs.org

Şimdiye kadar bir veritabanı bağlantı nesnesini paylaşmak gibi basit şeylerden bahsediyorum, ama beni tatmin edecek bir çözüm bulamadım.

Bu şekilde yapılır - bileşeni bir kez config içinde kaydedin.

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

Ve her yerde kullanın

app.db.findSomething()

Bileşen tanım kodunun tamamını (DB Bağlantısı ve diğer Bileşenler ile) burada görebilirsiniz https://github.com/sinizinairina/mono/blob/master/mono.coffee

IoC'ye ne yapmanız gerektiğini söylemeniz gereken tek yer, bundan sonra tüm bu bileşenler otomatik olarak oluşturulacak ve kablolanacak ve artık uygulamanızda IoC'ye özel kodu görmeniz gerekmeyecek.

IoC'nin kendisi https://github.com/alexeypetrushin/miconjs


6
Bir DI reklamı olmasına rağmen, bu daha çok bir servis bulucu gibi görünüyor.
KyorCode

2
Harika görünüyor, utanç verici sadece coffescript
Rafael P. Miranda

1

Sanırım Nodejs'de Bağımlılık Enjeksiyonuna ihtiyacımız var çünkü hizmetler arasındaki bağımlılıkları gevşetiyor ve uygulamayı daha net hale getiriyor.

Spring Framework'ten esinlenerek , Nodejs'e bağımlılık enjeksiyonunu desteklemek için kendi modülümü de uyguluyorum. Benim modül ayrıca tespit edebiliyor code changesve auto reloaduygulamanıza yeniden başlatma olmadan hizmet.

Projemi şurada ziyaret et: Buncha - IoC konteyner

Teşekkür ederim!



0

Net, PHP ve Java ile uzun süre çalıştım, bu yüzden NodeJS'de de uygun bir Bağımlılık Enjeksiyonu istedim. İnsanlar NodeJS'deki yerleşik DI'nin Modül ile alabileceğimiz için yeterli olduğunu söyledi. Ama beni tatmin etmedi. Bir Modülü bir Sınıftan daha fazla tutmak istemedim. Buna ek olarak, DI'nin Modül yaşam döngüsü yönetimi (singleton modülü, geçici modül vb.) İçin tam bir desteğe sahip olmasını istedim, ancak Node modülü ile çok sık manuel kod yazmak zorunda kaldım. Son olarak Birim Testini kolaylaştırmak istedim. Bu yüzden kendim için bir Bağımlılık Enjeksiyonu yarattım.

Bir DI arıyorsanız, bir deneyin. Burada bulunabilir: https://github.com/robo-creative/nodejs-robo-container . Tamamen belgelenmiştir. Ayrıca DI ile ilgili bazı genel sorunları ve bunların OOP yolunda nasıl çözüleceğini ele almaktadır. Umarım yardımcı olur.


Evet haklısın, projelerinizde bir DI kütüphanesi iyi mimariler için önemlidir, DI için bir kullanım örneği görmek istiyorsanız, bu deponun benioku için ayrıca Jems DI düğümü için bir DI kütüphanesine bakın .
Francisco Mercedes

-1

Son zamanlarda node.js ile bağımlılık enjeksiyonu kullanmanıza izin veren devre kutusu adı verilen bir kütüphane oluşturdum. Gördüğüm bağımlılık arama tabanlı kütüphanelerin çoğuna karşı gerçek bağımlılık enjeksiyonu yapıyor. Devre ayrıca eşzamansız oluşturma ve başlatma yordamlarını da destekler. Aşağıda bir örnek verilmiştir:

Aşağıdaki kodun consoleMessagePrinter.js adlı bir dosyada olduğunu varsayalım

'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 dosyasında aşağıdakileri varsayalım

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

Devre kutusu bileşenlerinizi tanımlamanızı ve bağımlılıklarını modül olarak bildirmenizi sağlar. Başlatıldıktan sonra, bir bileşeni almanıza izin verir. Devre Kutusu, hedef bileşenin ihtiyaç duyduğu tüm bileşenleri otomatik olarak enjekte eder ve kullanım için size verir.

Proje alfa versiyonunda. Yorum, fikir ve görüşlerinizi bekliyoruz.

Umarım yardımcı olur!


-1

Diğer gönderilerin DI'yi kullanma argümanında harika bir iş çıkardığını düşünüyorum. Benim için nedenler

  1. Yollarını bilmeden bağımlılıkları enjekte edin. Bu, diskteki bir modül konumunu değiştirir veya başka biriyle değiştirirseniz, ona bağlı olan her dosyaya dokunmanız gerekmediği anlamına gelir.

  2. Global requireişlevi sorunsuz çalışacak şekilde geçersiz kılma acısı olmadan test için bağımlılıkları taklit etmeyi çok daha kolay hale getirir .

  3. Gevşek bağlı modüller olarak uygulamanızı organize etmenize ve gerekçelendirmenize yardımcı olur.

Ancak ekibimin ve benim kolayca benimseyebileceğim bir DI çerçevesi bulmakta gerçekten zorlandım. Son zamanlarda bu özelliklere dayanarak deppie adı verilen bir çerçeve oluşturdum

  • Birkaç dakika içinde öğrenilebilen minimum API
  • Ek kod / yapılandırma / ek açıklama gerekmez
  • requireModüllerle bire bir doğrudan eşleme
  • Mevcut kodla çalışmak için kısmen kabul edilebilir


-1

Node.js, diğer platformlar kadar DI gerektirir. Büyük bir şey inşa ediyorsanız, DI kodunuzun bağımlılıklarını alay etmeyi ve kodunuzu iyice test etmeyi kolaylaştıracaktır.

Örneğin, veritabanı katmanı modülleriniz sadece iş kodu modüllerinizde gerekli olmamalıdır, çünkü bu iş kodu modüllerini birim test ederken, daos yüklenir ve veritabanına bağlanır.

Bir çözüm, bağımlılıkları modül parametreleri olarak geçirmek olacaktır:

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

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

Bu şekilde bağımlılıklar kolayca ve doğal bir şekilde alay edilebilir ve herhangi bir zor üçüncü taraf kitaplığı kullanmadan kodunuzu test etmeye odaklanabilirsiniz.

Bu konuda size yardımcı olabilecek başka çözümler de var (broadway, mimar vb.). ancak istediğinizden daha fazlasını yapabilir veya daha fazla dağınıklık kullanabilirler.


Neredeyse doğal evrim yoluyla aynı şeyi yaptım. Bir parametre olarak bağımlılığı geçiyorum ve test için harika çalışıyor.
munkee

-1

Bağımlılık enjeksiyonunu basit bir yolla ele alan, kaynak kodunu azaltan bir kütüphane geliştirdim. Her modül benzersiz bir ad ve denetleyici işlevi ile tanımlanır. Kontrolörün parametreleri modülün bağımlılıklarını yansıtır.

KlarkJS hakkında devamını oku

Kısa örnek:

KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
    return {
        log: function() { console.log('Hello from module myModuleName1') }
    };
});
  • myModuleName1 modülün adıdır.
  • $nodeModule1adlı siteden harici bir kütüphanedir node_module. İsim olarak çözülür node-module1. Ön ek $, harici bir modül olduğunu gösterir.
  • myModuleName2 dahili bir modülün adıdır.
  • Kontrolörün dönüş değeri, parametreyi tanımladıklarında diğer dahili modüllerden kullanılır myModuleName1.

-1

Kendi DI modülümde NodeJS programlama için neden bir DI sistemine ihtiyaç duyulacağını soran bir soruyu cevaplarken bu soruyu keşfettim .

Cevap açık bir şekilde bu konuda verilenlere yönelmişti: duruma göre değişir. Her iki yaklaşım için de değiş tokuşlar vardır ve bu sorunun cevaplarını okumak iyi bir şekil verir.

Yani, bu sorunun gerçek cevabı, bazı durumlarda bir DI sistemi kullanmanız, bazılarında kullanmamanız olmalıdır.

Bununla birlikte, bir geliştirici olarak istediğiniz şey, kendinizi tekrar etmemek ve hizmetlerinizi çeşitli uygulamalarınızda yeniden kullanmak değildir.

Bu, DI sisteminde kullanılmaya hazır olan ancak DI kütüphanelerine bağlı olmayan hizmetler yazmamız gerektiği anlamına gelir. Bana göre, bunun gibi hizmetler yazmalıyız:

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
}

Bu şekilde, bir DI aracıyla veya aracı olmadan kullanırsanız hizmetiniz önemli değildir.


-1

TypeDI burada bahsedilenlerin en tatlısıdır , TypeDI'da bu kodu inceleyin

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

@Service()
class SomeClass {

    someMethod() {
    }

}

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

Bu koda da bakın:

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;

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.