Ho bisogno dell'iniezione di dipendenza in NodeJS o come gestire ...?


219

Attualmente sto creando alcuni progetti sperimentali con nodejs. Ho programmato molte applicazioni web Java EE con Spring e ho apprezzato la facilità di iniezione di dipendenze lì.

Ora sono curioso: come faccio l'iniezione di dipendenza con il nodo? Oppure: ne ho nemmeno bisogno? Esiste un concetto sostitutivo, perché lo stile di programmazione è diverso?

Sto parlando di cose semplici, come condividere un oggetto di connessione al database, finora, ma non ho trovato una soluzione che mi soddisfi.


1
Se dovessi decidere di utilizzare DI, OpenTable ha recentemente aperto una libreria per questo: github.com/opentable/spur-ioc L'ho usato (ci lavoro) e posso dire che è abbastanza semplice e ottimo per i test.
tybro0103

Risposte:


107

In breve, non è necessario un contenitore di iniezione delle dipendenze o un localizzatore di servizi come in C # / Java. Dal momento che Node.js, sfrutta il module pattern, non è necessario eseguire l'iniezione di proprietà o costruttore. Anche se puoi ancora.

La cosa grandiosa di JS è che puoi modificare qualsiasi cosa per ottenere ciò che desideri. Questo è utile quando si tratta di test.

Ecco il mio esempio inventato molto zoppo.

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

Notare come MyClassdipende dal fsmodulo? Come accennato da @ShatyemShekhar, puoi davvero fare l'iniezione di proprietà o costruttore come in altre lingue. Ma non è necessario in Javascript.

In questo caso, puoi fare due cose.

Puoi stub il fs.readdirSyncmetodo o puoi restituire un modulo completamente diverso quando chiami require.

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

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

}

La chiave è sfruttare la potenza di Node.js e Javascript. Nota, sono un tipo CoffeeScript, quindi la mia sintassi JS potrebbe non essere corretta da qualche parte. Inoltre, non sto dicendo che questo è il modo migliore, ma è un modo. I guru di Javascript potrebbero essere in grado di entrare in contatto con altre soluzioni.

Aggiornare:

Questo dovrebbe rispondere alla tua domanda specifica relativa alle connessioni al database. Creerei un modulo separato per incapsulare la logica di connessione del database. Qualcosa come questo:

MyDbConnection.js: (assicurati di scegliere un nome migliore)

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    
}

Quindi, qualsiasi modulo che necessita di una connessione al database includerebbe semplicemente il MyDbConnectionmodulo.

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. 

Non seguire questo esempio alla lettera. È un esempio zoppo nel tentativo di comunicare che si sfrutta il modulemodello per gestire le proprie dipendenze. Spero che questo aiuti un po 'di più.


42
Questo è vero per quanto riguarda i test, ma DI ha altri vantaggi; usando DI puoi programmare su un'interfaccia, non su un'implementazione.
moteutsch,

3
@moteutsch Non sono sicuro del motivo per cui JS non ha la nozione di interfacce come la maggior parte dei linguaggi statici. Tutto quello che hai veramente sono implementazioni, anche se volevi usare alcune "interfacce" documentate.
JP Richardson,

16
@JPRichardson Come posso scrivere un componente che utilizza un logger senza dipendere da nessuna libreria? In tal caso require('my_logger_library'), le persone che utilizzano il mio componente dovranno ignorare la necessità di utilizzare la propria libreria. Invece, posso consentire alle persone di passare un callback che avvolge un'implementazione del logger nel metodo "costruttore" o "init" dei componenti. Questo è lo scopo di DI.
moteutsch,

4
A partire dalla metà del 2014 - npmjs.org/package/proxyquire rende deridere le "dipendenze" banali.
ArcSeldon,

4
Non capisco, la sostituzione richiede in un modulo non lo sostituisce in un altro. Se imposto la richiesta di una funzione nel mio test e quindi richiedo di testare il modulo, le istruzioni richieste nell'oggetto da testare non usano la funzione impostata nel modulo di test. Come si iniettano le dipendenze?
HMR,

72

requireè il modo di gestire le dipendenze in Node.js e sicuramente è intuitivo ed efficace, ma ha anche i suoi limiti.

Il mio consiglio è di dare un'occhiata ad alcuni dei contenitori di Iniezione delle dipendenze disponibili oggi per Node.js per avere un'idea di quali sono i loro pro / contro. Alcuni di loro sono:

Solo per citarne alcuni.

Ora la vera domanda è: cosa puoi ottenere con un contenitore DI Node.js, rispetto a un semplice require?

Professionisti:

  • migliore testabilità: i moduli accettano le loro dipendenze come input
  • Inversione del controllo: decidi come cablare i tuoi moduli senza toccare il codice principale della tua applicazione.
  • un algoritmo personalizzabile per la risoluzione dei moduli: le dipendenze hanno identificatori "virtuali", di solito non sono legate a un percorso sul filesystem.
  • Migliore estensibilità: abilitato da IoC e identificatori "virtuali".
  • Altre cose fantasiose possibili:
    • Inizializzazione asincrona
    • Gestione del ciclo di vita del modulo
    • Estensibilità del contenitore DI stesso
    • Può facilmente implementare astrazioni di livello superiore (ad es. AOP)

Contro:

  • Diverso dall '"esperienza" di Node.js: non usare requiredefinitivamente ti sembra di deviare dal modo di pensare del Nodo.
  • La relazione tra una dipendenza e la sua implementazione non è sempre esplicita. Una dipendenza può essere risolta in fase di esecuzione e influenzata da vari parametri. Il codice diventa più difficile da capire ed eseguire il debug
  • Tempo di avvio più lento
  • Maturità (al momento): nessuna delle soluzioni attuali è molto popolare al momento, quindi non così tanti tutorial, nessun ecosistema, non testato in battaglia.
  • Alcuni contenitori DI non funzionano bene con i bundler di moduli come Browserify e Webpack.

Come per qualsiasi cosa relativa allo sviluppo del software, la scelta tra DI o requiredipende dalle vostre esigenze, dalla complessità del vostro sistema e dal vostro stile di programmazione.


3
Pensi che la situazione sia cambiata notevolmente dal '09?
Juho Vepsäläinen,

13
Intendi da 10 giorni? :)
Mario,

2
Nooo. 9 dic ... Avrei dovuto saperlo.
Juho Vepsäläinen,

4
Ho "implementato" DI usando module.exports = function (deps) {} tipo di pattern. Sì, funziona, ma non è del tutto ideale.
Juho Vepsäläinen,

3
i moduli accettano le loro dipendenze come input e le dipendenze non sono suoni espliciti per me come una contraddizione.
Anton Rudeshko,

53

So che questo thread è piuttosto vecchio a questo punto, ma ho pensato di entrare con i miei pensieri su questo. TL; DR è che a causa della natura non tipizzata e dinamica di JavaScript, puoi effettivamente fare molto senza ricorrere al modello di iniezione di dipendenza (DI) o usando un framework DI. Tuttavia, man mano che un'applicazione diventa più grande e complessa, DI può sicuramente aiutare la manutenibilità del codice.

DI in C #

Per capire perché DI non è un grande bisogno di JavaScript, è utile guardare un linguaggio fortemente tipizzato come C #. (Chiedo scusa a coloro che non conoscono C #, ma dovrebbe essere abbastanza facile da seguire.) Supponiamo che abbiamo un'app che descrive un'auto e il suo clacson. Definiresti due classi:

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

Ci sono alcuni problemi con la scrittura del codice in questo modo.

  1. La Carclasse è strettamente accoppiata alla particolare implementazione del clacson nella Hornclasse. Se vogliamo cambiare il tipo di clacson utilizzato dall'auto, dobbiamo modificare la Carclasse anche se il suo utilizzo del clacson non cambia. Questo rende anche difficile il test perché non possiamo testare la Carclasse isolatamente dalla sua dipendenza, la Hornclasse.
  2. La Carclasse è responsabile del ciclo di vita della Hornclasse. In un semplice esempio come questo non è un grosso problema, ma nelle applicazioni reali le dipendenze avranno dipendenze, che avranno dipendenze, ecc. La Carclasse dovrebbe essere responsabile della creazione dell'intero albero delle sue dipendenze. Questo non è solo complicato e ripetitivo, ma viola la "singola responsabilità" della classe. Dovrebbe concentrarsi sull'essere un'auto, non creare istanze.
  3. Non è possibile riutilizzare le stesse istanze di dipendenza. Ancora una volta, questo non è importante in questa applicazione giocattolo, ma considera una connessione al database. In genere si avrebbe una singola istanza condivisa nell'applicazione.

Riflettiamo ora questo per usare un modello di iniezione di dipendenza.

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

Abbiamo fatto due cose chiave qui. Innanzitutto, abbiamo introdotto un'interfaccia Hornimplementata dalla nostra classe. Questo ci consente di codificare la Carclasse sull'interfaccia anziché la particolare implementazione. Ora il codice potrebbe prendere tutto ciò che implementa IHorn. In secondo luogo, abbiamo rimosso l'istanza del corno Care l'abbiamo passata. Ciò risolve i problemi precedenti e lascia alla funzione principale dell'applicazione la gestione delle istanze specifiche e dei loro cicli di vita.

Ciò significa che potrebbe introdurre un nuovo tipo di clacson da utilizzare senza toccare la Carclasse:

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

FrenchHornInvece il main potrebbe semplicemente iniettare un'istanza della classe. Ciò semplifica notevolmente anche i test. È possibile creare una MockHornclasse da iniettare nel Carcostruttore per assicurarsi di testare solo la Carclasse in isolamento.

L'esempio sopra mostra l'iniezione manuale delle dipendenze. In genere DI viene eseguito con un framework (ad esempio Unity o Ninject nel mondo C #). Questi framework eseguiranno tutto il cablaggio delle dipendenze eseguendo il grafico delle dipendenze e creando le istanze necessarie.

Il modo standard Node.js

Ora diamo un'occhiata allo stesso esempio in Node.js. Probabilmente vorremmo suddividere il nostro codice in 3 moduli:

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

Poiché JavaScript non è tipizzato, non abbiamo lo stesso accoppiamento stretto che avevamo prima. Non sono necessarie interfacce (né esistono) in quanto il carmodulo tenterà semplicemente di chiamare il honkmetodo su qualunque hornesportazione del modulo.

Inoltre, poiché Node requirememorizza nella cache tutto, i moduli sono essenzialmente singoli memorizzati in un contenitore. Qualsiasi altro modulo che esegue una requiresul hornmodulo di otterrà l'esatto stessa istanza. Ciò rende molto semplice la condivisione di oggetti singleton come connessioni al database.

Ora c'è ancora il problema che il carmodulo è responsabile del recupero della propria dipendenza horn. Se si desidera che l'auto utilizzi un modulo diverso per il clacson, è necessario modificare la requiredichiarazione nel carmodulo. Questa non è una cosa molto comune da fare, ma causa problemi con i test.

Il solito modo in cui le persone gestiscono il problema del test è con proxyquire . A causa della natura dinamica di JavaScript, proxyquire intercetta le chiamate da richiedere e restituisce invece eventuali matrici / derisioni fornite.

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

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

// Now make test assertions on car...

Questo è più che sufficiente per la maggior parte delle applicazioni. Se funziona per la tua app, seguila. Tuttavia, nella mia esperienza man mano che le applicazioni diventano più grandi e complesse, mantenere il codice in questo modo diventa più difficile.

DI in JavaScript

Node.js è molto flessibile. Se non sei soddisfatto del metodo sopra, puoi scrivere i tuoi moduli usando il modello di iniezione delle dipendenze. In questo modello, ogni modulo esporta una funzione di fabbrica (o un costruttore di classi).

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

Questo è molto simile al metodo C # in precedenza in quanto il index.jsmodulo è responsabile per i cicli di vita e il cablaggio. Il test delle unità è abbastanza semplice in quanto puoi semplicemente passare in beffe / tronconi alle funzioni. Ancora una volta, se questo è abbastanza buono per la tua applicazione, vai con esso.

Bolus DI Framework

A differenza di C #, non esistono framework DI standard standard che possano aiutare nella gestione delle dipendenze. Esistono numerosi framework nel registro npm ma nessuno ha un'adozione diffusa. Molte di queste opzioni sono già state citate nelle altre risposte.

Non ero particolarmente contento di nessuna delle opzioni disponibili, quindi ho scritto il mio bolo chiamato . Bolus è progettato per funzionare con il codice scritto nello stile DI sopra e cerca di essere molto ASCIUTTO e molto semplice. Utilizzando lo stesso identico car.jse i horn.jsmoduli sopra, è possibile riscrivere il index.jsmodulo con bolo come:

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

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

L'idea di base è che si crea un iniettore. Registri tutti i tuoi moduli nell'iniettore. Quindi risolvi semplicemente ciò di cui hai bisogno. Bolus seguirà il grafico delle dipendenze e creerà e inietterà dipendenze secondo necessità. Non risparmi molto in un esempio di giocattolo come questo, ma in grandi applicazioni con alberi di dipendenza complicati i risparmi sono enormi.

Bolus supporta una serie di eleganti funzioni come dipendenze opzionali e test globali, ma ci sono due vantaggi chiave che ho visto rispetto all'approccio standard Node.js. Innanzitutto, se hai molte applicazioni simili, puoi creare un modulo npm privato per la tua base che crea un iniettore e registra oggetti utili su di esso. Quindi le tue app specifiche possono aggiungere, sovrascrivere e risolvere secondo necessità, proprio come AngularJSl'iniettore funziona. In secondo luogo, è possibile utilizzare bolo per gestire vari contesti di dipendenze. Ad esempio, è possibile utilizzare il middleware per creare un iniettore figlio per richiesta, registrare l'ID utente, ID sessione, logger, ecc. Sull'iniettore insieme a tutti i moduli a seconda di quelli. Quindi risolvi ciò di cui hai bisogno per soddisfare le richieste. Ciò fornisce istanze dei moduli per richiesta e impedisce di dover passare il logger, ecc. A ogni chiamata di funzione del modulo.


1
è anche vero che ci sono alternative a proxyquirequelle sinonche ti permettono di fare beffe molto concise, ad esempio let readFileStub = sinon.stub(fs, 'readFile').yields(new Error('something went wrong'));e quindi le chiamate successive a fs.readFilerestituiranno errori fino a quando non ripristini lo stub tramite readFileStub.restore(). Personalmente trovo DI di uso discutibile perché mi sembra quasi che richieda l'uso di classi / oggetti, il che è un presupposto discutibile date le tendenze funzionali di javascript.
Kevin,

Grazie per questa buona + risposta dettagliata. Quasi mi mancava, mentre leggevo per la prima volta il titolo DI in C # .
Konstantin A. Magg,

1
Bella risposta. Mi chiedo che cosa i vostri pensieri sono nel 2019. Per i progetti di grandi dimensioni, come una questione di preferenze personali, quale preferisci - DI / IOC nel nodo, o semplicemente spegnendo / beffardo con jest, rewire, proxyquiree così via? Grazie.
Jamie Corkhill,

Ottima risposta equilibrata! Grazie.
Johnny Oshika,

36

Ho anche scritto un modulo per raggiungere questo obiettivo, si chiama rewire . Basta usare npm install rewiree quindi:

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

Sono stato ispirato dall'iniettore di Nathan MacInnes ma ho usato un approccio diverso. Non uso vmper valutare il modulo di test, infatti uso il proprio requisito del nodo. In questo modo il tuo modulo si comporta esattamente come usando require()(tranne le tue modifiche). Anche il debug è completamente supportato.


7
A partire dalla metà del 2014 - npmjs.org/package/proxyquire rende deridere le "dipendenze" banali.
ArcSeldon,

proxyquire è anche bello! Stanno ora usando il modulo "modulo" interno che è molto meglio dell'uso della vm del nodo. Ma dopo tutto è solo una questione di stile. Mi piace che il mio modulo usi il requisito originale e cambi le dipendenze in seguito. Inoltre, il ricollegamento consente anche di ignorare i globi.
Johannes Ewald,

È stato molto interessante cercare qualcosa di simile da utilizzare sul posto di lavoro, questo modulo influisce anche sui moduli a valle?
AKST

per proxyquire Nella descrizione viene indicato che viene utilizzato per i test, "Necessari proxy proxy per consentire le dipendenze di override durante i test ". non per DI, giusto?
Marwen Trabelsi,

17

Ho costruito Electrolyte proprio per questo scopo. Le altre soluzioni di iniezione di dipendenza là fuori erano troppo invasive per i miei gusti, e fare casini con il globale requireè una mia lamentela particolare.

Electrolyte abbraccia i moduli, in particolare quelli che esportano una funzione di "installazione" come quella vista nel middleware Connect / Express. In sostanza, questi tipi di moduli sono solo fabbriche per alcuni oggetti che restituiscono.

Ad esempio, un modulo che crea una connessione al database:

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

Quello che vedi in basso sono le annotazioni , un ulteriore bit di metadati che Electrolyte utilizza per creare un'istanza e iniettare dipendenze, collegando automaticamente i componenti dell'applicazione.

Per creare una connessione al database:

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

L'elettrolita attraversa in modo transitorio le @requiredipendenze e inserisce istanze come argomenti nella funzione esportata.

La chiave è che questo è minimamente invasivo. Questo modulo è completamente utilizzabile, indipendentemente dall'elettrolita stesso. Ciò significa che i test delle unità possono testare solo il modulo in prova , passando oggetti finti senza ricorrere a dipendenze aggiuntive per ricablare gli interni.

Durante l'esecuzione dell'applicazione completa, Electrolyte interviene a livello di inter-modulo, collegando le cose senza la necessità di globuli, singleton o tubature eccessive.


1
Chiariresti cosa succede nel codice che hai pubblicato quando viene lanciata una chiamata connect()? Anche se non ho familiarità con l'API MySql per Node, mi aspetto che questa chiamata sia asincrona, quindi l'illustrazione non è del tutto chiara.
Andrey Agibalov,

attualmente utilizzando elettrolita. Sostieni che è facile INIETTARE i moduli tramite le esportazioni ['@ request']. Ma se devo stub uno dei moduli richiesti, come è possibile farlo in elettrolita. Attualmente, se abbiamo bisogno di moduli, questo può essere facilmente raggiunto. Ma per l'elettrolita questo è un enorme negativo ... Hai esempi in cui possiamo usare la versione stubbed dei moduli e passarla dinamicamente mentre istanza / ioc.use dai casi di test. Quindi fondamentalmente nel test unitario, se possiamo fare ioc.create ('modulename') e quindi fare l'iniezione di moduli dipendenti (ma quelli
stub

1
Non chiameresti ioc.createda un unit test. Un test unitario dovrebbe testare solo il modulo in prova e non introdurre altre dipendenze, incluso Electrolyte. Seguendo questo consiglio, farestiobjToTest = require('modulename')(mockObj1, mockObj2);
Jared Hanson,

8

Ho esaminato questo da solo. Non mi piace introdurre librerie utils di dipendenza magica che forniscono meccanismi per dirottare le importazioni dei miei moduli. Invece ho escogitato una "linea guida di progettazione" per il mio team per affermare in modo piuttosto esplicito quali dipendenze possono essere derise introducendo un'esportazione delle funzioni di fabbrica nei miei moduli.

Faccio ampio uso delle funzionalità ES6 per i parametri e la destrutturazione al fine di evitare alcune piastre di caldaia e fornire un meccanismo di sostituzione delle dipendenze denominato.

Ecco un esempio:

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

Ed ecco un esempio del suo utilizzo

import { factory } from './bar';

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

Scusa la sintassi ES6 per coloro che non hanno familiarità con esso.


Davvero molto geniale!
Arnold

4

Recentemente ho controllato questo thread per lo stesso motivo dell'OP - la maggior parte delle librerie che ho incontrato riscrivono temporaneamente l'istruzione request. Ho avuto diversi gradi di successo con questo metodo e quindi ho finito con il seguente approccio.

Nel contesto di un'applicazione express - Avvolgo app.js in un 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();

La mappa degli oggetti passata al caricatore è simile alla seguente:

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

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

Quindi, anziché chiamare direttamente, è necessario ...

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

Se nel caricatore non è presente alcun alias, verrà automaticamente visualizzato un valore predefinito. Questo ha due vantaggi: posso scambiare qualsiasi versione della classe e rimuovere la necessità di usare nomi di percorso relativi in ​​tutta l'applicazione (quindi se ho bisogno di una lib personalizzata sotto o sopra il file corrente, non ho bisogno di attraversare , e request memorizzerà il modulo nella stessa chiave). Mi permette anche di specificare simulazioni in qualsiasi punto dell'app, piuttosto che nella suite di test immediati.

Ho appena pubblicato un piccolo modulo npm per comodità:

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


3

La realtà è che puoi testare il tuo node.js senza contenitore IoC perché JavaScript è un linguaggio di programmazione davvero dinamico e puoi modificare quasi tutto in fase di esecuzione.

Considera quanto segue:

import UserRepository from "./dal/user_repository";

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

export default UserController;

In questo modo è possibile ignorare l'accoppiamento tra i componenti in fase di esecuzione. Mi piace pensare che dovremmo mirare a disaccoppiare i nostri moduli JavaScript.

L'unico modo per ottenere il disaccoppiamento reale è rimuovere il riferimento a UserRepository:

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

export default UserController;

Ciò significa che da qualche altra parte dovrai fare la composizione dell'oggetto:

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

export default new UserController(new UserRepository());

Mi piace l'idea di delegare la composizione dell'oggetto a un contenitore IoC. Puoi saperne di più su questa idea nell'articolo Lo stato attuale dell'inversione di dipendenza in JavaScript . L'articolo tenta di sfatare alcuni "miti del contenitore IoC JavaScript":

Mito 1: non esiste spazio per i contenitori IoC in JavaScript

Mito 2: Non abbiamo bisogno di contenitori IoC, abbiamo già caricatori di moduli!

Mito 3: inversione di dipendenza === iniettando dipendenze

Se ti piace anche l'idea di utilizzare un contenitore IoC, potresti dare un'occhiata a InversifyJS. L'ultima versione (2.0.0) supporta molti casi d'uso:

  • Moduli del kernel
  • Middleware del kernel
  • Utilizzare classi, valori letterali di stringa o simboli come identificatori di dipendenza
  • Iniezione di valori costanti
  • Iniezione di costruttori di classe
  • Iniezione di fabbriche
  • Fabbrica auto
  • Iniezione di fornitori (fabbrica asincrona)
  • Gestori di attivazione (utilizzati per iniettare proxy)
  • Iniezioni multiple
  • Associazioni etichettate
  • Decoratori di tag personalizzati
  • Associazioni nominate
  • Vincoli contestuali
  • Eccezioni amichevoli (ad es. Dipendenze circolari)

Puoi saperne di più su InversifyJS .


2

Per ES6 ho sviluppato questo contenitore https://github.com/zazoomauro/node-dependency-injection

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

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

Quindi è possibile impostare, ad esempio, la scelta del trasporto nel contenitore:

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

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

Questa classe è ora molto più flessibile poiché hai separato la scelta del trasporto dall'implementazione e nel contenitore.

Ora che il servizio mailer è nel contenitore, è possibile iniettarlo come dipendenza di altre classi. Se hai una classe NewsletterManager come questa:

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

export default NewsletterManager

Quando si definisce il servizio newsletter_manager, il servizio mailer non esiste ancora. Utilizzare la classe di riferimento per indicare al contenitore di iniettare il servizio mailer quando inizializza il gestore newsletter:

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

È inoltre possibile impostare il contenitore con file di configurazione come file Yaml, Json o JS

Il contenitore del servizio può essere compilato per vari motivi. Questi motivi includono la verifica di eventuali problemi potenziali come riferimenti circolari e rendere il contenitore più efficiente.

container.compile()

1

Dipende dal design della tua applicazione. Ovviamente puoi fare un'iniezione come Java dove crei un oggetto di una classe con la dipendenza passata nel costruttore in questo modo.

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

var cache = new Cache(mysqlStore);

Se non stai eseguendo OOP in javascript, puoi creare una funzione init che imposta tutto.

Tuttavia, è possibile adottare un altro approccio che è più comune in un sistema basato su eventi come node.js. Se riesci a modellare la tua applicazione solo per (per la maggior parte del tempo) agire sugli eventi, tutto ciò che devi fare è impostare tutto (cosa che di solito faccio chiamando una funzione init) ed emettere eventi da uno stub. Questo rende il test abbastanza semplice e leggibile.


Grazie per la tua risposta, ma non capisco perfettamente la tua seconda parte della tua risposta.
Erik,

1

Mi è sempre piaciuta la semplicità del concetto IoC: "Non devi sapere nulla sull'ambiente, verrai chiamato da qualcuno quando necessario"

Ma tutte le implementazioni IoC che ho visto hanno fatto esattamente il contrario: ingombrano il codice con ancora più cose che senza. Quindi, ho creato il mio IoC che funziona come vorrei che fosse - rimane nascosto e invisibile il 90% delle volte .

È utilizzato nel framework web MonoJS http://monojs.org

Sto parlando di cose semplici, come condividere un oggetto di connessione al database, finora, ma non ho trovato una soluzione che mi soddisfi.

È fatto così: registra il componente una volta in config.

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

E usalo ovunque

app.db.findSomething()

Puoi vedere il codice di definizione completo del componente (con DB Connection e altri componenti) qui https://github.com/sinizinairina/mono/blob/master/mono.coffee

Questo è l'unico posto in cui devi dire a IoC cosa fare, dopo che tutti quei componenti verranno creati e cablati automaticamente e non dovrai più vedere il codice specifico IoC nella tua applicazione.

Lo stesso IoC https://github.com/alexeypetrushin/miconjs


6
Sebbene pubblicizzato come DI, questo sembra molto più simile a un localizzatore di servizi.
KyorCode

2
Sembra fantastico, peccato che sia solo in una bara
Rafael P. Miranda,

1

Penso che abbiamo ancora bisogno dell'iniezione di dipendenza in Nodejs perché allenta le dipendenze tra i servizi e rende più chiara l'applicazione.

Ispirato da Spring Framework , implemento anche il mio modulo per supportare l'iniezione delle dipendenze in Nodejs. Il mio modulo è anche in grado di rilevare i code changese auto reloadi servizi senza riavviare l'applicazione.

Visita il mio progetto su: Buncha - contenitore IoC

Grazie!



0

Ho lavorato a lungo con .Net, PHP e Java, quindi volevo avere anche una comoda Iniezione delle dipendenze in NodeJS. La gente ha detto che il DI integrato in NodeJS è abbastanza come possiamo ottenerlo con Module. Ma non mi soddisfaceva bene. Volevo mantenere un modulo non più di una classe. Inoltre, volevo che il DI avesse un supporto completo per la gestione del ciclo di vita del modulo (modulo singleton, modulo transitorio ecc.) Ma con il modulo Node, dovevo scrivere molto spesso il codice manuale. Infine, volevo rendere più semplice Unit Test. Ecco perché ho creato un'iniezione di dipendenza per me stesso.

Se stai cercando un DI, provalo. Può essere trovato qui: https://github.com/robo-creative/nodejs-robo-container . È completamente documentato. Risolve anche alcuni problemi comuni con DI e come risolverli in modo OOP. Spero che sia d'aiuto.


Sì, hai ragione, una libreria DI nei tuoi progetti è importante per le buone architetture. Se vuoi vedere un caso d'uso per DI, vedi il readme per questo repository anche una libreria DI per il nodo Jems DI .
Francisco Mercedes,

-1

Di recente ho creato una libreria chiamata circuitbox che ti permette di usare la dipendenza-iniezione con node.js. Fa vera iniezione di dipendenza rispetto a molte delle librerie basate sulla ricerca di dipendenza che ho visto. Circuitbox supporta anche routine di creazione e inizializzazione asincrone. Di seguito è riportato un esempio:

Supponiamo che il seguente codice sia in un file chiamato 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;

Supponiamo che quanto segue sia nel 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 consente di definire i componenti e dichiarare le loro dipendenze come moduli. Una volta inizializzato, consente di recuperare un componente. Circuitbox inietta automaticamente tutti i componenti richiesti dal componente target e te lo fornisce per l'uso.

Il progetto è in versione alfa. I vostri commenti, idee e feedback sono i benvenuti.

Spero che sia d'aiuto!


-1

Penso che altri post abbiano fatto un ottimo lavoro nell'argomento per l'utilizzo di DI. Per me i motivi sono

  1. Iniettare dipendenze senza conoscere i loro percorsi. Ciò significa che se si modifica un percorso del modulo sul disco o lo si scambia con un altro, non è necessario toccare tutti i file che dipendono da esso.

  2. Rende molto più facile deridere le dipendenze per i test senza il dolore di ignorare la requirefunzione globale in un modo che funzioni senza problemi.

  3. Ti aiuta a organizzare e ragionare sulla tua applicazione come moduli debolmente accoppiati.

Ma ho avuto davvero delle difficoltà a trovare un framework DI che il mio team e io possiamo facilmente adottare. Di recente ho quindi creato un framework chiamato deppie basato su queste funzionalità

  • API minima che può essere appresa in pochi minuti
  • Nessun codice / config / annotazioni extra richiesti
  • Mappatura diretta one to one ai requiremoduli
  • Può essere adottato parzialmente per funzionare con il codice esistente


-1

Node.js richiede DI tanto quanto qualsiasi altra piattaforma. Se stai costruendo qualcosa di grande, DI renderà più semplice deridere le dipendenze del tuo codice e testarlo a fondo.

I moduli del livello del database, ad esempio, non dovrebbero essere richiesti solo ai moduli del codice aziendale poiché, durante il test dell'unità di questi moduli del codice aziendale, i daos verranno caricati e collegati al database.

Una soluzione sarebbe quella di passare le dipendenze come parametri del modulo:

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

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

In questo modo le dipendenze possono essere derise in modo semplice e naturale e puoi rimanere concentrato sul test del codice, senza utilizzare alcuna libreria di terze parti.

Ci sono altre soluzioni là fuori (broadway, architetto ecc.) Che possono aiutarti in questo. sebbene possano fare più di quello che vuoi o usare più disordine.


Quasi attraverso l'evoluzione naturale ho finito per fare lo stesso. Passo una dipendenza come parametro e funziona benissimo per i test.
Munkee,

-1

Ho sviluppato una libreria che gestisce l'iniezione delle dipendenze in modo semplice, riducendo il codice del boilerplate. Ogni modulo è definito da un nome univoco e da una funzione controller. I parametri del controller riflettono le dipendenze del modulo.

Maggiori informazioni su KlarkJS

Breve esempio:

KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
    return {
        log: function() { console.log('Hello from module myModuleName1') }
    };
});
  • myModuleName1 è il nome del modulo.
  • $nodeModule1è una libreria esterna da node_module. Il nome si risolve in node-module1. Il prefisso $indica che si tratta di un modulo esterno.
  • myModuleName2 è il nome di un modulo interno.
  • Il valore di ritorno del controller viene utilizzato dagli altri moduli interni, quando definiscono il parametro myModuleName1.

-1

Ho scoperto questa domanda mentre rispondevo a un problema sul mio modulo DI che mi chiedeva perché uno avrebbe mai avuto bisogno di un sistema DI per la programmazione di NodeJS.

La risposta era chiaramente tendente a quelle fornite in questo thread: dipende. Ci sono compromessi per entrambi gli approcci e la lettura delle risposte a questa domanda ne dà una buona forma.

Quindi, la vera risposta a questa domanda, dovrebbe essere che in alcune situazioni, useresti un sistema DI, in altre no.

Detto questo, ciò che vuoi come sviluppatore è non ripetere te stesso e riutilizzare i tuoi servizi tra le varie applicazioni.

Ciò significa che dovremmo scrivere servizi pronti per essere utilizzati nel sistema DI ma non legati alle librerie DI. Per me, significa che dovremmo scrivere servizi come questo:

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
}

In questo modo il tuo servizio non ha importanza se lo usi con o senza uno strumento DI.


-1

TypeDI è il più dolce di tutti citati qui, guarda questo codice in TypeDI

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

@Service()
class SomeClass {

    someMethod() {
    }

}

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

Guarda anche questo codice:

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;

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.