¿Necesito inyección de dependencia en NodeJS, o cómo lidiar con ...?

219

Actualmente estoy creando algunos proyectos experimentales con nodejs. He programado muchas aplicaciones web Java EE con Spring y aprecié la facilidad de la inyección de dependencia allí.

Ahora tengo curiosidad: ¿cómo hago la inyección de dependencia con el nodo? O: ¿lo necesito? ¿Existe un concepto de reemplazo, porque el estilo de programación es diferente?

Estoy hablando de cosas simples, como compartir un objeto de conexión de base de datos, hasta ahora, pero no he encontrado una solución que me satisfaga.

Erik
fuente
1
En caso de que decida utilizar DI, OpenTable recientemente le proporcionó una biblioteca de código abierto: github.com/opentable/spur-ioc La he usado (trabajo allí), y puedo decir que es bastante simple y excelente para probar.
tybro0103

Respuestas:

107

En resumen, no necesita un contenedor de inyección de dependencia o un localizador de servicios como lo haría en C # / Java. Como Node.js aprovecha el module pattern, no es necesario realizar una inyección de constructor o propiedad. Aunque todavía puedes.

Lo mejor de JS es que puede modificar casi cualquier cosa para lograr lo que desea. Esto es útil cuando se trata de pruebas.

He aquí mi ejemplo inventado muy cojo.

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

¿Notan cómo MyClassdepende del fsmódulo? Como @ShatyemShekhar mencionó, de hecho puede hacer una inyección de constructor o propiedad como en otros idiomas. Pero no es necesario en Javascript.

En este caso, puedes hacer dos cosas.

Puede desactivar el fs.readdirSyncmétodo o puede devolver un módulo completamente diferente cuando llame require.

Método 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étodo 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 clave es aprovechar el poder de Node.js y Javascript. Tenga en cuenta que soy un tipo de CoffeeScript, por lo que mi sintaxis JS puede ser incorrecta en alguna parte. Además, no estoy diciendo que esta sea la mejor manera, pero es una forma. Los gurús de Javascript podrían intervenir con otras soluciones.

Actualizar:

Esto debería abordar su pregunta específica con respecto a las conexiones de la base de datos. Crearía un módulo separado para que encapsule la lógica de conexión de su base de datos. Algo como esto:

MyDbConnection.js: (asegúrese de elegir un nombre mejor)

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    
}

Entonces, cualquier módulo que necesite una conexión de base de datos incluiría su MyDbConnectionmódulo.

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. 

No siga este ejemplo literalmente. Es un ejemplo lamentable al tratar de comunicar que aprovecha el modulepatrón para administrar sus dependencias. Esperemos que esto ayude un poco más.

JP Richardson
fuente
42
Esto es cierto con respecto a las pruebas, pero la DI tiene otros beneficios; Al usar DI puede programar en una interfaz, no en una implementación.
moteutsch
3
@moteutsch No estoy seguro de por qué lo haría ya que JS no tiene la noción de interfaces como la mayoría de los lenguajes estáticos. Todo lo que realmente tiene son implementaciones, incluso si desea utilizar alguna "interfaz" documentada previamente acordada.
JP Richardson
16
@JPRichardson ¿Cómo puedo escribir un componente que use un registrador sin depender de ninguna biblioteca? Si yo require('my_logger_library'), las personas que usan mi componente tendrán que anular el requisito de usar su propia biblioteca. En cambio, puedo permitir que las personas pasen una devolución de llamada que envuelva una implementación del registrador en el componente "constructor" o método "init". Ese es el propósito de DI.
moteutsch
44
A mediados de 2014, npmjs.org/package/proxyquire hace que burlarse de las dependencias " obligatorias " sea trivial.
arcseldon 01 de
44
No lo entiendo, el reemplazo requerido en un módulo no lo reemplaza en otro. Si configuro require a una función en mi prueba y luego requiero que se pruebe el módulo, las declaraciones require en el objeto a probar no usarán la función establecida en el módulo de prueba. ¿Cómo inyecta esto las dependencias?
HMR
72

requirees la forma de administrar dependencias en Node.js y seguramente es intuitivo y efectivo, pero también tiene sus limitaciones.

Mi consejo es que eche un vistazo a algunos de los contenedores de inyección de dependencia disponibles hoy para que Node.js tenga una idea de cuáles son sus pros / contras. Algunos de ellos son:

Sólo para nombrar unos pocos.

Ahora la verdadera pregunta es, ¿qué se puede lograr con un contenedor DI Node.js, en comparación con un simple require?

Pros:

  • mejor capacidad de prueba: los módulos aceptan sus dependencias como entrada
  • Inversión de control: decida cómo conectar sus módulos sin tocar el código principal de su aplicación.
  • un algoritmo personalizable para resolver módulos: las dependencias tienen identificadores "virtuales", generalmente no están vinculados a una ruta en el sistema de archivos.
  • Mayor extensibilidad: habilitado por IoC e identificadores "virtuales".
  • Otras cosas elegantes posibles:
    • Inicialización asincrónica
    • Módulo de gestión del ciclo de vida
    • Extensibilidad del propio contenedor DI
    • Puede implementar fácilmente abstracciones de nivel superior (por ejemplo, AOP)

Contras:

  • A diferencia de la "experiencia" de Node.js: no usar requiredefinitivamente se siente como si se estuviera desviando de la forma de pensar de Node.
  • La relación entre una dependencia y su implementación no siempre es explícita. Una dependencia puede resolverse en tiempo de ejecución e influenciarse por varios parámetros. El código se vuelve más difícil de entender y depurar
  • Tiempo de inicio más lento
  • Madurez (en este momento): ninguna de las soluciones actuales es realmente popular en este momento, por lo que no hay tantos tutoriales, ni ecosistemas, ni pruebas de batalla.
  • Algunos contenedores DI no funcionarán bien con los paquetes de módulos como Browserify y Webpack.

Al igual que con cualquier cosa relacionada con el desarrollo de software, elegir entre DI o requiredepende de sus requisitos, la complejidad de su sistema y su estilo de programación.

Mario
fuente
3
¿Crees que la situación ha cambiado considerablemente desde el '09?
Juho Vepsäläinen
13
¿Te refieres a hace 10 días? :)
Mario
2
Nooo 9 de diciembre ... Debería haberlo sabido.
Juho Vepsäläinen
44
"Implementé" DI usando module.exports = function (deps) {} tipo de patrón. Sí, funciona, pero no es lo ideal.
Juho Vepsäläinen
3
Los módulos aceptan sus dependencias como entrada y las dependencias no son sonidos explícitos para mí como una contradicción.
Anton Rudeshko
53

Sé que este hilo es bastante antiguo en este punto, pero pensé que me gustaría intervenir con mis pensamientos sobre esto. El TL; DR es que debido a la naturaleza dinámica y sin tipo de JavaScript, en realidad puede hacer mucho sin recurrir al patrón de inyección de dependencia (DI) o usar un marco DI. Sin embargo, a medida que una aplicación se hace más grande y más compleja, DI definitivamente puede ayudar a la mantenibilidad de su código.

DI en C #

Para entender por qué DI no es una necesidad tan grande en JavaScript, es útil mirar un lenguaje fuertemente tipado como C #. (Disculpas a aquellos que no conocen C #, pero debería ser lo suficientemente fácil de seguir). Digamos que tenemos una aplicación que describe un automóvil y su bocina. Definirías dos clases:

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

Hay pocos problemas al escribir el código de esta manera.

  1. La Carclase está estrechamente vinculada a la implementación particular de la bocina en la Hornclase. Si queremos cambiar el tipo de bocina utilizada por el automóvil, tenemos que modificar la Carclase a pesar de que su uso de la bocina no cambia. Esto también dificulta las pruebas porque no podemos probar la Carclase aisladamente de su dependencia, la Hornclase.
  2. La Carclase es responsable del ciclo de vida de la Hornclase. En un ejemplo simple como este no es un gran problema, pero en aplicaciones reales las dependencias tendrán dependencias, que tendrán dependencias, etc.Car clase debería ser responsable de crear el árbol completo de sus dependencias. Esto no solo es complicado y repetitivo, sino que viola la "responsabilidad única" de la clase. Debería centrarse en ser un automóvil, no en crear instancias.
  3. No hay forma de reutilizar las mismas instancias de dependencia. Nuevamente, esto no es importante en esta aplicación de juguete, pero considere una conexión de base de datos. Por lo general, tendría una única instancia compartida en su aplicación.

Ahora, refactoricemos esto para usar un patrón de inyección de dependencia.

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

Hemos hecho dos cosas clave aquí. Primero, hemos introducido una interfaz que Hornimplementa nuestra clase. Esto nos permite codificar la Carclase en la interfaz en lugar de la implementación particular. Ahora el código podría tomar cualquier cosa que implemente IHorn. En segundo lugar, hemos sacado la instanciación de la bocina y la hemos pasado Car. Esto resuelve los problemas anteriores y deja a la función principal de la aplicación administrar las instancias específicas y sus ciclos de vida.

Lo que esto significa es que podría introducir un nuevo tipo de bocina para que el automóvil use sin tocar la Carclase:

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

El principal podría simplemente inyectar una instancia de la FrenchHornclase en su lugar. Esto también simplifica dramáticamente las pruebas. Puede crear una MockHornclase para inyectar en el Carconstructor para asegurarse de que está probando solo elCar clase de forma aislada.

El ejemplo anterior muestra la inyección manual de dependencia. Por lo general, DI se realiza con un marco (por ejemplo, Unity o Ninject en el mundo C #). Estos marcos harán todo el cableado de dependencia por usted al recorrer su gráfico de dependencia y crear instancias según sea necesario.

La forma estándar de Node.js

Ahora veamos el mismo ejemplo en Node.js. Probablemente dividiríamos nuestro código en 3 módulos:

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

Debido a que JavaScript no está tipificado, no tenemos el mismo acoplamiento estrecho que teníamos antes. No hay necesidad de interfaces (ni existen) ya que el carmódulo solo intentará llamar al honkmétodo en cualquierhorn que exporta módulo.

Además, debido a que Node's requirealmacena en caché todo, los módulos son esencialmente singletons almacenados en un contenedor. Cualquier otro módulo que realice un requireen el hornmódulo obtendrá exactamente la misma instancia. Esto hace que compartir objetos singleton como conexiones de bases de datos sea muy fácil.

Ahora todavía existe el problema de que el carmódulo es responsable de recuperar su propia dependencia horn. Si quisieras que el auto usara un módulo diferente para su bocina, tendrías que cambiar la requiredeclaración en el carmódulo. Esto no es algo muy común, pero causa problemas con las pruebas.

La forma habitual en que las personas manejan el problema de las pruebas es con proxyquire . Debido a la naturaleza dinámica de JavaScript, proxyquire intercepta las llamadas para exigir y devuelve los resguardos / simulacros que proporcione en su lugar.

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

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

// Now make test assertions on car...

Esto es más que suficiente para la mayoría de las aplicaciones. Si funciona para tu aplicación, ve con ella. Sin embargo, en mi experiencia a medida que las aplicaciones se hacen más grandes y complejas, mantener un código como este se vuelve más difícil.

DI en JavaScript

Node.js es muy flexible. Si no está satisfecho con el método anterior, puede escribir sus módulos utilizando el patrón de inyección de dependencia. En este patrón, cada módulo exporta una función de fábrica (o un constructor de clase).

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

Esto es muy análogo al método C # anterior en que el index.jsmódulo es responsable, por ejemplo, de los ciclos de vida y el cableado. Las pruebas unitarias son bastante simples, ya que puede pasar simulaciones / apéndices a las funciones. Nuevamente, si esto es lo suficientemente bueno para su aplicación, hágalo.

Marco Bolus DI

A diferencia de C #, no hay marcos DI estándar establecidos para ayudarlo con su gestión de dependencias. Existen varios marcos en el registro npm, pero ninguno tiene una adopción generalizada. Muchas de estas opciones ya se han citado en las otras respuestas.

No estaba particularmente satisfecho con ninguna de las opciones disponibles, así que escribí mi propio bolo llamado . Bolus está diseñado para funcionar con código escrito en el estilo DI anterior e intenta ser muy SECO y muy simple. Usando exactamente lo mismo car.jsy los horn.jsmódulos anteriores, puede reescribir el index.jsmódulo con bolo como:

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

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

La idea básica es crear un inyector. Registra todos sus módulos en el inyector. Entonces simplemente resuelves lo que necesitas. Bolus recorrerá el gráfico de dependencia y creará e inyectará dependencias según sea necesario. No se ahorra mucho en un ejemplo de juguete como este, pero en aplicaciones grandes con árboles de dependencia complicados, los ahorros son enormes.

Bolus admite un montón de características ingeniosas como dependencias opcionales y pruebas globales, pero hay dos beneficios clave que he visto en relación con el enfoque estándar de Node.js. Primero, si tiene muchas aplicaciones similares, puede crear un módulo npm privado para su base que cree un inyector y registre objetos útiles en él. Luego, sus aplicaciones específicas pueden agregar, anular y resolver según sea necesario, de manera similar a cómo AngularJSinyector funciona. En segundo lugar, puede usar el bolo para administrar varios contextos de dependencias. Por ejemplo, podría usar middleware para crear un inyector secundario por solicitud, registrar la identificación de usuario, la identificación de sesión, el registrador, etc. en el inyector junto con los módulos que dependen de ellos. Luego resuelva lo que necesita para atender las solicitudes. Esto le proporciona instancias de sus módulos por solicitud y evita tener que pasar el registrador, etc. a cada llamada de función del módulo.

Dave Johnson
fuente
1
También es cierto que existen alternativas proxyquirecomo las sinonque le permiten hacer simulacros muy concisos, por ejemplo, let readFileStub = sinon.stub(fs, 'readFile').yields(new Error('something went wrong'));y luego las llamadas posteriores fs.readFiledevolverán un error hasta que revierta el código auxiliar readFileStub.restore(). Personalmente, encuentro DI de uso cuestionable porque siento que casi requiere el uso de clases / objetos, lo cual es una suposición dudosa dadas las inclinaciones funcionales de JavaScript.
Kevin
Gracias por esta buena respuesta detallada. Casi me lo pierdo, ya que leí por primera vez el titular DI en C # .
Konstantin A. Magg
1
Gran respuesta. Me pregunto cuáles son sus pensamientos en 2019. Para grandes proyectos, como una cuestión de preferencia personal, ¿cuál te gusta - DI / COI en nodo, o simplemente apagando / burlarse con jest, rewire, proxyquire, etc.? Gracias.
Jamie Corkhill
Gran respuesta equilibrada! Gracias.
Johnny Oshika
36

También he escrito un módulo para lograr esto, se llama rewire . Solo usa npm install rewirey luego:

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

Me inspiré en la inyección de Nathan MacInnes, pero utilicé un enfoque diferente. No uso vmpara evaluar el módulo de prueba, de hecho, uso el propio require del nodo. De esta manera, su módulo se comporta exactamente como si estuviera usando require()(excepto sus modificaciones). También la depuración es totalmente compatible.

Johannes Ewald
fuente
77
A mediados de 2014, npmjs.org/package/proxyquire hace que burlarse de las dependencias " obligatorias " sea trivial.
arcseldon 01 de
¡proxyquire también es genial! Ahora están usando el módulo interno "módulo", que es mucho mejor que usar la vm del nodo. Pero después de todo, es solo una cuestión de estilo. Me gusta que mi módulo use el requerimiento original y que luego cambie las dependencias. Además, el nuevo cableado también permite anular los globales.
Johannes Ewald
Muy interesante ha estado buscando algo como esto para usar en el trabajo, ¿este módulo afecta también a los módulos posteriores?
akst
for proxyquire Se dice en la descripción que se usa para probar, 'Los proxyjs requieren para poder anular las dependencias durante la prueba '. no para DI, ¿verdad?
Marwen Trabelsi
17

Construí Electrolyte solo para este propósito. Las otras soluciones de inyección de dependencia por ahí eran demasiado invasivas para mis gustos, y jugar con lo global requirees un agravio particular mío.

Electrolyte abarca módulos, específicamente aquellos que exportan una función de "configuración" como se ve en el middleware Connect / Express. Esencialmente, estos tipos de módulos son solo fábricas para algún objeto que devuelven.

Por ejemplo, un módulo que crea una conexión de base de datos:

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

Lo que ve en la parte inferior son anotaciones , un bit adicional de metadatos que Electrolyte usa para crear instancias e inyectar dependencias, conectando automáticamente los componentes de su aplicación.

Para crear una conexión de base de datos:

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

Electrolyte atraviesa transitivamente las @requiredependencias 'd' e inyecta instancias como argumentos para la función exportada.

La clave es que esto es mínimamente invasivo. Este módulo es completamente utilizable, independiente del propio electrolito. Eso significa que las pruebas unitarias pueden probar solo el módulo bajo prueba , pasando objetos simulados sin necesidad de dependencias adicionales para volver a cablear los componentes internos.

Cuando se ejecuta la aplicación completa, Electrolyte interviene en el nivel entre módulos, conectando todo sin necesidad de globales, soldaduras o plomería excesiva.

Jared Hanson
fuente
1
¿Podría aclarar qué sucede en el código que ha publicado cuando se connect()lanza una llamada ? Aunque no estoy familiarizado con la API MySql para Node, esperaría que esta llamada sea asíncrona, por lo que la ilustración no está del todo clara.
Andrey Agibalov
Actualmente utilizando electrolitos. Afirma que es fácil INYECTAR módulos a través de exportaciones ['@ require']. Pero si tengo que tropezar con uno de los módulos requeridos, ¿cómo se puede lograr con electrolito? Actualmente, si requerimos módulos, esto se puede lograr fácilmente. Pero para el electrolito, esto es un gran negativo ... ¿Tiene ejemplos en los que podemos usar la versión tropezada de los módulos y pasarla dinámicamente mientras que la instanciación / ioc.use de los casos de prueba. Básicamente, en una prueba unitaria, si podemos hacer ioc.create ('nombre de módulo') y luego hacer la inyección de módulos dependientes (pero con trozos) sería lo ideal ...
user1102171
1
No llamarías ioc.createdesde una prueba unitaria. Una prueba unitaria debe probar solo el módulo bajo prueba y no incluir otras dependencias, incluido Electrolyte. Siguiendo este consejo, lo haríasobjToTest = require('modulename')(mockObj1, mockObj2);
Jared Hanson
8

Lo investigué yo mismo. No me gusta introducir bibliotecas de utilidades de dependencia mágica que proporcionan mecanismos para secuestrar las importaciones de mi módulo. En su lugar, se me ocurrió una "directriz de diseño" para que mi equipo establezca explícitamente qué dependencias se pueden burlar al introducir una exportación de funciones de fábrica dentro de mis módulos.

Hago un uso extenso de las funciones de ES6 para los parámetros y la desestructuración con el fin de evitar algunas repeticiones y proporcionar un mecanismo de anulación de dependencia con nombre.

Aquí hay un ejemplo:

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

Y aquí hay un ejemplo de su uso.

import { factory } from './bar';

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

Disculpe la sintaxis de ES6 para aquellos que no están familiarizados con ella.

ctrlplusb
fuente
Muy ingenioso de hecho!
arnold
4

Recientemente revisé este hilo por la misma razón que el OP: la mayoría de las bibliotecas que he encontrado reescriben temporalmente la declaración require. He tenido diversos grados de éxito con este método, por lo que terminé usando el siguiente enfoque.

En el contexto de una aplicación express, envuelvo app.js en un archivo 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();

El mapa de objetos pasado al cargador se ve así:

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

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

Entonces, en lugar de llamar directamente requiere ...

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

Si no se encuentra ningún alias en el cargador, entonces solo se requerirá de forma predeterminada. Esto tiene dos ventajas: puedo intercambiar en cualquier versión de la clase, y elimina la necesidad de usar nombres de ruta relativos en toda la aplicación (por lo tanto, si necesito una biblioteca personalizada debajo o encima del archivo actual, no necesito recorrer , y require cacheará el módulo contra la misma clave). También me permite especificar simulacros en cualquier punto de la aplicación, en lugar de en el conjunto de pruebas inmediato.

Acabo de publicar un pequeño módulo npm por conveniencia:

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

sunwukung
fuente
3

La realidad es que puede probar su node.js sin el contenedor IoC porque JavaScript es un lenguaje de programación realmente dinámico y puede modificar casi todo en tiempo de ejecución.

Considera lo siguiente:

import UserRepository from "./dal/user_repository";

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

export default UserController;

Por lo tanto, puede anular el acoplamiento entre componentes en tiempo de ejecución. Me gusta pensar que deberíamos aspirar a desacoplar nuestros módulos JavaScript.

La única forma de lograr un desacoplamiento real es eliminando la referencia a UserRepository:

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

export default UserController;

Esto significa que en otro lugar deberá hacer la composición del objeto:

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

export default new UserController(new UserRepository());

Me gusta la idea de delegar la composición del objeto a un contenedor de IoC. Puede obtener más información sobre esta idea en el artículo El estado actual de la inversión de dependencia en JavaScript . El artículo intenta desacreditar algunos "mitos del contenedor JavaScript IoC":

Mito 1: no hay lugar para los contenedores de IoC en JavaScript

Mito 2: No necesitamos contenedores IoC, ¡ya tenemos cargadores de módulos!

Mito 3: Inversión de dependencia === inyección de dependencias

Si también le gusta la idea de usar un contenedor IoC, puede echar un vistazo a InversifyJS. La última versión (2.0.0) admite muchos casos de uso:

  • Módulos del núcleo
  • Kernel middleware
  • Use clases, literales de cadena o símbolos como identificadores de dependencia
  • Inyección de valores constantes.
  • Inyección de constructores de clase.
  • Inyección de fábricas
  • Fábrica de automóviles
  • Inyección de proveedores (fábrica asíncrona)
  • Controladores de activación (utilizados para inyectar proxies)
  • Inyecciones múltiples
  • Enlaces etiquetados
  • Decoradores de etiquetas personalizadas
  • Enlaces nombrados
  • Enlaces contextuales
  • Excepciones amistosas (por ejemplo, dependencias circulares)

Puede obtener más información al respecto en InversifyJS .

Remo H. Jansen
fuente
2

Para ES6 desarrollé este contenedor https://github.com/zazoomauro/node-dependency-injection

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

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

Luego puede establecer, por ejemplo, la elección del transporte en el contenedor:

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

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

Esta clase ahora es mucho más flexible, ya que ha separado la opción de transporte de la implementación al contenedor.

Ahora que el servicio de correo está en el contenedor, puede inyectarlo como una dependencia de otras clases. Si tiene una clase NewsletterManager como esta:

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

export default NewsletterManager

Al definir el servicio newsletter_manager, el servicio de correo aún no existe. Use la clase Referencia para indicarle al contenedor que inyecte el servicio de correo cuando inicializa el administrador de boletines:

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

También puede configurar el contenedor con archivos de configuración como archivos Yaml, Json o JS

El contenedor de servicios se puede compilar por varias razones. Estas razones incluyen la verificación de posibles problemas, como referencias circulares y hacer que el contenedor sea más eficiente.

container.compile()
Mauro
fuente
1

Depende del diseño de su aplicación. Obviamente, puede hacer una inyección similar a Java donde crea un objeto de una clase con la dependencia pasada en el constructor de esta manera.

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

var cache = new Cache(mysqlStore);

Si no está haciendo OOP en javascript, puede hacer una función init que configure todo.

Sin embargo, hay otro enfoque que puede adoptar que es más común en un sistema basado en eventos como node.js. Si puede modelar su aplicación para que solo (la mayoría de las veces) actúe sobre eventos, entonces todo lo que necesita hacer es configurar todo (lo que generalmente hago llamando a una función init) y emitir eventos desde un stub. Esto hace que las pruebas sean bastante más fáciles y legibles.

Satyam Shekhar
fuente
Gracias por su respuesta, pero no entiendo completamente su segunda parte de su respuesta.
Erik
1

Siempre me gustó la simplicidad del concepto de IoC: "No tiene que saber nada sobre el medio ambiente, alguien lo llamará cuando sea necesario"

Pero todas las implementaciones de IoC que vi hicieron exactamente lo contrario: saturan el código con aún más cosas que sin él. Entonces, creé mi propio IoC que funciona como me gustaría que sea: permanece oculto e invisible el 90% del tiempo .

Se utiliza en el marco web de MonoJS http://monojs.org

Estoy hablando de cosas simples, como compartir un objeto de conexión de base de datos, hasta ahora, pero no he encontrado una solución que me satisfaga.

Se hace así: registre el componente una vez en la configuración.

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

Y úsalo en cualquier lugar

app.db.findSomething()

Puede ver el código de definición de componente completo (con DB Connection y otros componentes) aquí https://github.com/sinizinairina/mono/blob/master/mono.coffee

Este es el único lugar donde tiene que decirle a IoC qué hacer, después de eso, todos esos componentes se crearán y cablearán automáticamente y ya no tendrá que ver el código específico de IoC en su aplicación.

El IoC en sí https://github.com/alexeypetrushin/miconjs

Alexey Petrushin
fuente
66
Aunque se anuncia como DI, esto parece mucho más como un localizador de servicios.
KyorCode
2
Se ve genial, lástima que solo esté en el coffescript
Rafael P. Miranda
1

Creo que todavía necesitamos la inyección de dependencias en Nodejs porque afloja las dependencias entre los servicios y hace que la aplicación sea más clara.

Inspirado por Spring Framework , también implemento mi propio módulo para admitir la inyección de dependencia en Nodejs. Mi módulo también puede detectar el code changesyauto reload los servicios sin reiniciar la aplicación.

Visita mi proyecto en: Buncha - contenedor IoC

¡Gracias!

aunque
fuente
0

Trabajé con .Net, PHP y Java durante mucho tiempo, así que también quería tener una inyección de dependencia conveniente en NodeJS. La gente dijo que el DI incorporado en NodeJS es suficiente, ya que podemos obtenerlo con el Módulo. Pero no me satisfizo bien. Quería mantener un Módulo no más que una Clase. Además, quería que el DI tuviera un soporte completo para la gestión del ciclo de vida del módulo (módulo singleton, módulo transitorio, etc.) pero con el módulo Node, tuve que escribir el código manual con mucha frecuencia. Por último, quería hacer que Unit Test sea más fácil. Es por eso que creé una Inyección de dependencia para mí.

Si estás buscando una DI, pruébalo. Se puede encontrar aquí: https://github.com/robo-creative/nodejs-robo-container . Está completamente documentado. También aborda algunos problemas comunes con DI y cómo resolverlos en forma POO. Espero eso ayude.

Robo
fuente
Sí, tiene razón, una biblioteca DI en sus proyectos es importante para las buenas arquitecturas. Si desea ver un caso de uso para DI, consulte el archivo Léame de este repositorio y también una biblioteca DI para el nodo Jems DI .
Francisco Mercedes
-1

Recientemente creé una biblioteca llamada circuitbox que te permite usar la inyección de dependencia con node.js. Realiza una verdadera inyección de dependencia frente a muchas de las bibliotecas basadas en la búsqueda de dependencia que he visto. Circuitbox también admite rutinas asincrónicas de creación e inicialización. A continuación se muestra un ejemplo:

Suponga que el siguiente código está en un archivo llamado 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;

Suponga que lo siguiente está en el archivo 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 le permite definir sus componentes y declarar sus dependencias como módulos. Una vez que se inicializa, le permite recuperar un componente. Circuitbox inyecta automáticamente todos los componentes que requiere el componente de destino y se lo proporciona para su uso.

El proyecto está en versión alfa. Sus comentarios, ideas y comentarios son bienvenidos.

¡Espero eso ayude!

Oddjobsman
fuente
-1

Creo que otras publicaciones han hecho un gran trabajo en el argumento para usar DI. Para mi las razones son

  1. Inyectar dependencias sin conocer sus caminos. Esto significa que si cambia la ubicación de un módulo en el disco o lo cambia por otro, no necesita tocar todos los archivos que dependen de él.

  2. Hace que sea mucho más fácil burlarse de las dependencias para las pruebas sin la molestia de anular la requirefunción global de una manera que funcione sin problemas.

  3. Le ayuda a organizar y razonar sobre su aplicación como módulos sueltos.

Pero me costó mucho encontrar un marco DI que mi equipo y yo podamos adoptar fácilmente. Así que recientemente construí un marco llamado deppie basado en estas características

  • API mínima que se puede aprender en unos minutos
  • No se requiere código / configuración / anotaciones adicionales
  • Mapeo directo uno a uno a requiremódulos
  • Se puede adoptar parcialmente para trabajar con el código existente
gafi
fuente
-1

Node.js requiere DI tanto como cualquier otra plataforma. Si está construyendo algo grande, DI facilitará burlarse de las dependencias de su código y probarlo a fondo.

Sus módulos de capa de base de datos, por ejemplo, no solo deberían ser necesarios en sus módulos de código comercial porque, cuando la unidad prueba estos módulos de código comercial, los datos se cargarán y se conectarán a la base de datos.

Una solución sería pasar las dependencias como parámetros del módulo:

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

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

De esta manera, las dependencias se pueden burlar de forma fácil y natural, y puede concentrarse en probar su código, sin usar ninguna biblioteca de terceros complicada.

Existen otras soluciones (broadway, arquitecto, etc.) que pueden ayudarlo con esto. aunque pueden hacer más de lo que quieres o usar más desorden.

usuario2468170
fuente
Casi a través de la evolución natural, terminé haciendo lo mismo. Paso una dependencia como parámetro y funciona muy bien para las pruebas.
munkee
-1

He desarrollado una biblioteca que maneja la inyección de dependencia de una manera simple, que disminuye el código repetitivo. Cada módulo está definido por un nombre único y una función de controlador. Los parámetros del controlador reflejan las dependencias del módulo.

Lea más sobre KlarkJS

Breve ejemplo:

KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
    return {
        log: function() { console.log('Hello from module myModuleName1') }
    };
});
  • myModuleName1 es el nombre del módulo
  • $nodeModule1es una biblioteca externa de node_module. El nombre se resuelve en node-module1. El prefijo $indica que es un módulo externo.
  • myModuleName2 es el nombre de un módulo interno.
  • El valor de retorno del controlador se utiliza desde los otros módulos internos, cuando definen el parámetro myModuleName1.
Apostolidis
fuente
-1

Descubrí esta pregunta mientras respondía a un problema en mi propio módulo DI preguntando por qué uno necesitaría un sistema DI para la programación de NodeJS.

La respuesta fue claramente a las que se dan en este hilo: depende. Hay compensaciones para ambos enfoques y leer las respuestas de esta pregunta les da una buena forma.

Entonces, la verdadera respuesta a esta pregunta, debería ser que en algunas situaciones, usaría un sistema DI, en otras no.

Dicho esto, lo que desea como desarrollador es no repetirse y reutilizar sus servicios en sus diversas aplicaciones.

Esto significa que deberíamos escribir servicios que estén listos para usarse en el sistema DI pero que no estén vinculados a las bibliotecas DI. Para mí, significa que deberíamos escribir servicios como este:

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
}

De esa manera, su servicio funciona sin importar si lo usa con o sin una herramienta DI.

nfroidure
fuente
-1

TypeDI es el más dulce de todos los mencionados aquí, mira este código en TypeDI

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

@Service()
class SomeClass {

    someMethod() {
    }

}

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

Mira este código tambié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;

}
ahmadalibaloch
fuente