268 votes

Ai-je besoin d'injection de dépendance dans NodeJS, ou la façon de traiter avec ...?

J'ai actuellement de la création de certains projets expérimentaux avec nodejs. J'ai programmé un lot de Java EE des applications web avec le Printemps et apprécié la facilité de l'injection de dépendance.

Maintenant, je suis curieux de savoir: Comment dois-je faire de l'injection de dépendances avec noeud? Ou: ai-je besoin? Est-il un remplaçant concept, parce que le style de programmation est différente?

Je suis en train de parler de choses simples, comme le partage d'une connexion de base de données objet, jusqu'à présent, mais je n'ai pas trouvé une solution qui me satisfait.

135voto

JP Richardson Points 11920

En bref, vous n'avez pas besoin d'un conteneur d'injection de dépendance ou d'un service de localisation comme vous le feriez en C#/Java. Depuis Node.js, tire parti de l' module pattern, il n'est pas nécessaire d'effectuer constructeur ou des biens de l'injection. Bien que vous le pouvez encore.

La grande chose à propos de JS, c'est que vous pouvez modifier à peu près tout pour obtenir ce que vous voulez. C'est pratique quand il s'agit de tester.

Voici ma très boiteux exemple artificiel.

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

Remarquez comment MyClass dépend de l' fs module? @ShatyemShekhar mentionné, vous pouvez le faire en effet, le constructeur ou bien à l'injection dans d'autres langues. Mais il n'est pas nécessaire en Javascript.

Dans ce cas, vous pouvez faire deux choses.

Vous pouvez stub l' fs.readdirSync méthode ou vous pouvez revenir un tout autre module lorsque vous appelez require.

Méthode 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;

Méthode 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 clé est de tirer parti de la puissance de Node.js et Javascript. Remarque, je suis un CoffeeScript gars, mon JS syntaxe est peut-être incorrect quelque part. Aussi, je ne dis pas que c'est la meilleure façon, mais c'est une façon. Javascript gourous pourriez être en mesure de carillon avec d'autres solutions.

Mise à jour:

Cela devrait répondre à votre question spécifique concernant les connexions de base de données. J'aimerais créer un module séparé pour votre pour encapsuler votre connexion de base de données logique. Quelque chose comme ceci:

MyDbConnection.js: (assurez-vous de choisir un meilleur nom)

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    
}

Ensuite, un module qui a besoin d'une connexion de base de données seraient alors il suffit d'inclure votre MyDbConnection module.

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. 

Ne suivez pas cet exemple verbatim. C'est un piètre exemple pour essayer de communiquer que vous tirer parti de l' module modèle pour la gestion de vos dépendances. Espérons que cela aide un peu plus.

96voto

Mario Points 660

require est la façon de gérer les dépendances en Node.js et certainement c'est intuitive et efficace, mais il a aussi ses limites.

Mon conseil est de prendre un coup d'oeil à certains de l'Injection de Dépendance des conteneurs disponibles aujourd'hui pour Node.js pour avoir une idée sur ce que sont leurs avantages/inconvénients. Certains d'entre eux sont:

Juste pour en nommer quelques-uns.

Maintenant la vraie question est, que pouvez-vous atteindre avec un Node.js DI conteneur, par rapport à un simple require?

Pour:

  • meilleure testabilité: modules accepte leurs dépendances comme entrée
  • L'Inversion de Contrôle: décider de la façon de câbler des modules sans toucher le code principal de votre application.
  • un autre algorithme pour la résolution des modules: dépendances "virtuel" identificateurs, habituellement, ils ne sont pas liés à un chemin d'accès dans le système de fichiers.
  • Une meilleure extensibilité: activé par le Cio et les "virtuel" identificateurs.
  • D'autres de fantaisie possible:
    • Initialisation asynchrone
    • Module de gestion du cycle de vie
    • L'extensibilité de la DI conteneur lui-même
    • Peut facilement mettre en œuvre des abstractions de plus haut niveau (par exemple, Nommé services et AOP)

Inconvénients:

  • Différente de la Node.js "l'expérience": l'utilisation d' require certainement se sent comme vous êtes la déviation du Nœud façon de penser.
  • Les dépendances ne sont pas explicites: le flux de programme est plus difficile à comprendre et à déboguer
  • Temps de démarrage plus lent
  • Maturité (pour le moment): aucune de ces solutions n'est vraiment populaire en ce moment, donc pas beaucoup de tutoriels, pas d'écosystème, pas de bataille testés.

Comme tout ce qui est lié au développement de logiciels, le choix entre les DI - require dépend donc de vos besoins, de votre la complexité du système et de votre style de programmation.

37voto

jhnns Points 532

J'ai aussi écrit un module pour ce faire, il est appelé rewire. Suffit d'utiliser npm install rewire puis:

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

J'ai été inspiré par Nathan MacInnes est injectr mais utilisé une approche différente. Je n'utilise pas d' vm d'eval le test du module, en fait j'utilise du nœud propre besoin. De cette façon, votre module se comporte exactement comme l'utilisation d' require() (à l'exception des modifications). Aussi, le débogage est entièrement pris en charge.

17voto

Jared Hanson Points 8390

J'ai construit l'Électrolyte pour cet effet. L'autre injection de dépendance des solutions à l'extérieur, il y avait trop envahissante à mon goût, et de jouer avec le global require est un grief donné de la mine.

L'électrolyte embrasse modules, en particulier ceux que l'exportation d'un "setup" de la fonction comme vous le voyez dans Connect/Express middleware. Essentiellement, ces types de modules sont juste des usines pour certains, objet de leur retour.

Par exemple, un module qui crée une connexion de base de données:

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

Ce que vous voyez au fond sont des annotations, un bit supplémentaire de métadonnées que l'Électrolyte utilise pour instancier et injecter des dépendances, automatiquement le câblage de votre application à l'ensemble des composants.

Pour créer une connexion de base de données:

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

L'électrolyte transitivement traverse l' @require'd dépendances, et l'injecte des instances comme arguments de la fonction exportée.

La clé, c'est que ce mini-invasive. Ce module est totalement fonctionnelle, indépendante de l'Électrolyte. Cela signifie que vos tests unitaires pouvez tester simplement le module en cours de test, en passant dans la maquette des objets sans avoir besoin de dépendances supplémentaires de refaire l'installation des éléments internes.

Lors de l'exécution de la demande complète, l'Électrolyte étapes de l'inter-module de niveau, le câblage des choses ensemble, sans la nécessité pour les variables globales, les singletons ou excessive de la plomberie.

4voto

sunwukung Points 1173

Récemment, j'ai vérifié ce fil de discussion pour la même raison que l'OP - la plupart des libs que j'ai rencontré temporairement la réécriture de la directive require. J'ai eu des degrés de succès avec cette méthode, et j'ai donc fini par utiliser l'approche suivante.

Dans le contexte d'une demande expresse - je wrap app.js dans un bootstrap.js fichier:

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

L'objet de la carte réussi à le chargeur ressemble à ceci:

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

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

Alors, plutôt que de les appeler...

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

Si aucun alias n'est situé dans le chargeur - alors, il va juste valeur par défaut à la régularité de l'exiger. Ceci a deux avantages: je peux échanger dans n'importe quelle version de la classe, et il supprime la nécessité pour utiliser un chemin relatif noms tout au long de l'application (donc Si j'ai besoin d'un custom lib ci-dessous ou au-dessus de l'actuel fichier, je n'ai pas besoin de traverser, et nécessitent la mise en cache sera le module contre la même clé). Il me permet également de spécifier les objets fantaisie à n'importe quel point dans l'application, plutôt que dans l'immédiat suite de tests.

J'ai tout juste de publier un petit module npm pour plus de commodité:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X