Preciso de injeção de dependência no NodeJS ou como lidar com…?


219

Atualmente, estou criando alguns projetos experimentais com o nodejs. Programei muitas aplicações web Java EE com o Spring e apreciei a facilidade de injeção de dependência lá.

Agora estou curioso: como faço para injetar dependência no nó? Ou: eu preciso disso? Existe um conceito de substituição, porque o estilo de programação é diferente?

Estou falando de coisas simples, como compartilhar um objeto de conexão com o banco de dados até agora, mas não encontrei uma solução que me satisfaça.


1
Se você decidir usar o DI, o OpenTable recentemente forneceu uma biblioteca de código aberto para ele: github.com/opentable/spur-ioc Eu usei (eu trabalho lá) e posso dizer que é bastante simples e ótimo para testes.
tybro0103

Respostas:


107

Em resumo, você não precisa de um contêiner de injeção de dependência ou localizador de serviço, como faria em C # / Java. Desde o Node.js, aproveita o module pattern, não é necessário executar a injeção de construtor ou propriedade. Embora você ainda possa.

O melhor de JS é que você pode modificar praticamente qualquer coisa para alcançar o que deseja. Isso é útil quando se trata de testes.

Eis meu exemplo artificial muito coxo.

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

Observe como MyClassdepende do fsmódulo? Como o @ShatyemShekhar mencionou, você pode realmente executar injeção de construtor ou propriedade como em outros idiomas. Mas não é necessário em Javascript.

Nesse caso, você pode fazer duas coisas.

Você pode stub o fs.readdirSyncmétodo ou você pode retornar um módulo totalmente diferente quando você chama 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);

}

A chave é aproveitar o poder do Node.js e Javascript. Observe que eu sou do tipo CoffeeScript, então minha sintaxe JS pode estar incorreta em algum lugar. Além disso, não estou dizendo que esse é o melhor caminho, mas é um caminho. Os gurus do Javascript podem ser capazes de acompanhar outras soluções.

Atualizar:

Isso deve abordar sua pergunta específica sobre conexões com o banco de dados. Eu criaria um módulo separado para encapsular sua lógica de conexão com o banco de dados. Algo assim:

MyDbConnection.js: (certifique-se de escolher um nome melhor)

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    
}

Então, qualquer módulo que precise de uma conexão com o banco de dados incluiria apenas o seu 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. 

Não siga este exemplo literalmente. É um exemplo fraco ao tentar comunicar que você aproveita o modulepadrão para gerenciar suas dependências. Espero que isso ajude um pouco mais.


42
Isso é verdade em relação aos testes, mas o DI tem outros benefícios; usando o DI, você pode programar para uma interface, não para uma implementação.
Moteutsch 22/08/2012

3
@moteutsch Não sei por que você teria, já que o JS não tem a noção de interfaces, como a maioria das linguagens estáticas. Tudo o que você realmente tem são implementações, mesmo se você quiser usar alguma "interface" documentada previamente acordada.
JP Richardson

16
@JPRichardson Como escrever um componente que usa um criador de logs sem depender de nenhuma biblioteca? Se eu require('my_logger_library'), as pessoas que usam meu componente precisarão substituir a necessidade de usar sua própria biblioteca. Em vez disso, posso permitir que as pessoas passem um retorno de chamada que agrupe uma implementação do criador de logs no método "construtor" ou "init" dos componentes. Esse é o objetivo do DI.
Moteutsch 22/08/2012

4
A partir de meados de 2014 - npmjs.org/package/proxyquire torna trivial zombar de "exigir" dependências.
Arcseldon

4
Não entendi, a substituição de require em um módulo não a substitui em outro. Se eu definir exigir uma função no meu teste e exigir que o módulo seja testado, as instruções exigir no objeto a ser testado não usarão a função definida no módulo de teste. Como isso injeta dependências?
HMR

72

requireé a maneira de gerenciar dependências no Node.js e certamente é intuitivo e eficaz, mas também possui suas limitações.

Meu conselho é dar uma olhada em alguns dos contêineres de injeção de dependência disponíveis hoje para o Node.js para ter uma idéia de quais são seus prós / contras. Alguns deles são:

Só para citar alguns.

Agora, a verdadeira questão é: o que você pode obter com um contêiner DI do Node.js. comparado a um simples require?

Prós:

  • melhor testabilidade: os módulos aceitam suas dependências como entrada
  • Inversão de controle: decida como conectar seus módulos sem tocar no código principal da sua aplicação.
  • um algoritmo personalizável para resolver módulos: dependências possuem identificadores "virtuais", geralmente não estão vinculados a um caminho no sistema de arquivos.
  • Melhor extensibilidade: ativada por IoC e identificadores "virtuais".
  • Outras coisas extravagantes possíveis:
    • Inicialização assíncrona
    • Gerenciamento do ciclo de vida do módulo
    • Extensibilidade do próprio contêiner DI
    • Pode implementar facilmente abstrações de nível superior (por exemplo, AOP)

Contras:

  • Diferente da "experiência" do Node.js.: não usar requiredefinitivamente parece que você está se desviando da maneira de pensar do Nó.
  • O relacionamento entre uma dependência e sua implementação nem sempre é explícito. Uma dependência pode ser resolvida em tempo de execução e influenciada por vários parâmetros. O código se torna mais difícil de entender e depurar
  • Tempo de inicialização mais lento
  • Maturidade (no momento): nenhuma das soluções atuais é realmente popular no momento; portanto, não há muitos tutoriais, nenhum ecossistema, nenhuma batalha testada.
  • Alguns contêineres DI não funcionam bem com os agrupadores de módulos como o Browserify e o Webpack.

Como em qualquer coisa relacionada ao desenvolvimento de software, a escolha entre DI ou requiredepende de seus requisitos, complexidade do sistema e estilo de programação.


3
Você acha que a situação mudou consideravelmente desde '09?
Juho Vepsäläinen

13
Você quer dizer desde 10 dias atrás? :)
Mario

2
Nããão. 9 de dezembro ... Deveria saber.
Juho Vepsäläinen

4
Eu "implementei" o DI usando o tipo de padrão module.exports = function (deps) {}. Sim, funciona, mas não é o ideal.
Juho Vepsäläinen

3
modules aceita suas dependências como entrada e dependências não são sons explícitos para mim como uma contradição.
Anton Rudeshko

53

Eu sei que esse tópico é bastante antigo neste momento, mas achei que iria concordar com meus pensamentos sobre isso. O TL; DR é que, devido à natureza dinâmica e sem tipo do JavaScript, você pode realmente fazer bastante sem recorrer ao padrão de injeção de dependência (DI) ou usar uma estrutura de DI. No entanto, à medida que um aplicativo se torna maior e mais complexo, o DI pode definitivamente ajudar na manutenção do seu código.

DI em C #

Para entender por que o DI não é tão necessário em JavaScript, é útil olhar para uma linguagem fortemente tipada como C #. (Desculpas para quem não conhece C #, mas deve ser fácil o suficiente.) Diga que temos um aplicativo que descreve um carro e sua buzina. Você definiria duas classes:

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

Existem alguns problemas ao escrever o código dessa maneira.

  1. A Carclasse está fortemente acoplada à implementação específica da buzina na Hornclasse. Se queremos mudar o tipo de buzina usada pelo carro, temos que modificar a Carclasse, mesmo que o uso da buzina não mude. Isso também dificulta o teste porque não podemos testar a Carclasse isoladamente de sua dependência, a Hornclasse.
  2. A Carclasse é responsável pelo ciclo de vida da Hornclasse. Em um exemplo simples como esse, não é um grande problema, mas em aplicativos reais as dependências terão dependências, que terão dependências etc. A Carclasse precisaria ser responsável por criar a árvore inteira de suas dependências. Isso não é apenas complicado e repetitivo, mas viola a "responsabilidade única" da classe. Deve se concentrar em ser um carro, não em criar instâncias.
  3. Não há como reutilizar as mesmas instâncias de dependência. Novamente, isso não é importante neste aplicativo de brinquedo, mas considere uma conexão com o banco de dados. Você normalmente teria uma única instância que é compartilhada em seu aplicativo.

Agora, vamos refatorar isso para usar um padrão de injeção de dependência.

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

Fizemos duas coisas importantes aqui. Primeiro, introduzimos uma interface que nossa Hornclasse implementa. Isso nos permite codificar a Carclasse para a interface, em vez da implementação específica. Agora o código pode levar qualquer coisa que seja implementada IHorn. Segundo, retiramos a instanciação da buzina Care a transmitimos. Isso resolve os problemas acima e deixa a função principal do aplicativo gerenciar instâncias específicas e seus ciclos de vida.

O que isto significa é que poderia introduzir um novo tipo de buzina para o carro usar sem tocar na Carclasse:

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

O main pode apenas injetar uma instância da FrenchHornclasse. Isso também simplifica drasticamente os testes. Você pode criar uma MockHornclasse para injetar no Carconstrutor para garantir que você está testando apenas a Carclasse isoladamente.

O exemplo acima mostra injeção de dependência manual. Normalmente, o DI é feito com uma estrutura (por exemplo, Unity ou Ninject no mundo C #). Essas estruturas farão toda a fiação de dependência para você, percorrendo seu gráfico de dependência e criando instâncias, conforme necessário.

A maneira padrão do Node.js

Agora vamos ver o mesmo exemplo no Node.js. Provavelmente dividiríamos nosso código em 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();

Como o JavaScript não é digitado, não temos o mesmo acoplamento rígido que tínhamos antes. Não há necessidade de interfaces (nem elas existem), pois o carmódulo apenas tentará chamar o honkmétodo em qualquer que seja a hornexportação do módulo.

Além disso, como o Node requirearmazena em cache tudo, os módulos são essencialmente singletons armazenados em um contêiner. Qualquer outro módulo que execute um requireno hornmódulo obterá exatamente a mesma instância. Isso facilita muito o compartilhamento de objetos singleton, como conexões com o banco de dados.

Agora ainda há o problema de o carmódulo ser responsável por buscar sua própria dependência horn. Se você quisesse que o carro usasse um módulo diferente para a buzina, seria necessário alterar a requiredeclaração no carmódulo. Isso não é algo muito comum, mas causa problemas com o teste.

A maneira usual pelas quais as pessoas lidam com o problema de teste é com o proxyquire . Devido à natureza dinâmica do JavaScript, o proxyquire intercepta chamadas para exigir e retorna todos os stubs / zombarias que você fornece.

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

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

// Now make test assertions on car...

Isso é mais do que suficiente para a maioria dos aplicativos. Se funcionar para o seu aplicativo, vá com ele. No entanto, na minha experiência à medida que os aplicativos se tornam maiores e mais complexos, manter um código como esse se torna mais difícil.

DI em JavaScript

O Node.js é muito flexível. Se você não estiver satisfeito com o método acima, poderá escrever seus módulos usando o padrão de injeção de dependência. Nesse padrão, todo módulo exporta uma função de fábrica (ou um construtor de classe).

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

Isso é muito análogo ao método C # anteriormente, pois o index.jsmódulo é responsável por ciclos de vida e fiação. O teste de unidade é bastante simples, pois você pode simplesmente passar zombarias / stubs para as funções. Novamente, se isso for bom o suficiente para o seu aplicativo, vá com ele.

Bolus DI Framework

Ao contrário do C #, não existem estruturas de DI padrão estabelecidas para ajudar no gerenciamento de dependências. Há várias estruturas no registro npm, mas nenhuma possui adoção generalizada. Muitas dessas opções já foram citadas nas outras respostas.

Eu não estava particularmente feliz com nenhuma das opções disponíveis, então escrevi meu próprio bolus . O Bolus foi projetado para funcionar com o código escrito no estilo DI acima e tenta ser muito SECO e muito simples. Usando exatamente o mesmo car.jse os horn.jsmódulos acima, você pode reescrever o index.jsmódulo com bolus como:

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

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

A idéia básica é que você crie um injetor. Você registra todos os seus módulos no injetor. Então você simplesmente resolve o que precisa. O Bolus percorrerá o gráfico de dependência e criará e injetará dependências, conforme necessário. Você não economiza muito em um exemplo de brinquedo como esse, mas em aplicativos grandes com árvores de dependência complicadas, as economias são enormes.

O Bolus suporta vários recursos interessantes, como dependências opcionais e globais de teste, mas há dois benefícios principais que eu já vi em relação à abordagem padrão do Node.js. Primeiro, se você tiver muitos aplicativos semelhantes, poderá criar um módulo npm privado para sua base que crie um injetor e registre objetos úteis nele. Em seguida, seus aplicativos específicos podem adicionar, substituir e resolver, conforme necessário, da mesma forma que o AngularJSinjetor funciona. Segundo, você pode usar o bolus para gerenciar vários contextos de dependências. Por exemplo, você pode usar o middleware para criar um injetor secundário por solicitação, registrar o ID do usuário, o ID da sessão, o registrador etc. no injetor, juntamente com quaisquer módulos, dependendo deles. Em seguida, resolva o que você precisa para atender solicitações. Isso fornece instâncias de seus módulos por solicitação e evita a necessidade de passar o criador de logs etc. para todas as chamadas de função do módulo.


1
também é verdade que há alternativa para proxyquirecomo o sinonque lhe permite fazer simulações muito concisas, por exemplo, let readFileStub = sinon.stub(fs, 'readFile').yields(new Error('something went wrong'));e, em seguida, chamadas subsequentes para fs.readFileretornará erro até que você reverter o stub via readFileStub.restore(). Pessoalmente, acho o DI de uso questionável porque sinto que quase requer o uso de classes / objetos, o que é uma suposição dúbia, considerando as inclinações funcionais do javascript.
Kevin Kevin

Obrigado por esta resposta boa e detalhada. Eu quase senti falta disso, quando li pela primeira vez o título DI em C # .
precisa saber é o seguinte

1
Ótima resposta. Eu estou querendo saber o que seus pensamentos estão em 2019. Para grandes projectos, como uma questão de preferência pessoal, o que você prefere - DI / COI em Node, ou apenas arrancar / zombando com jest, rewire, proxyquire, etc.? Obrigado.
Jamie Corkhill

Ótima resposta equilibrada! Obrigado.
Johnny Oshika

36

Também escrevi um módulo para fazer isso, chamado rewire . Basta usar npm install rewiree depois:

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

Eu fui inspirado pelo injectr de Nathan MacInnes, mas usei uma abordagem diferente. Eu não uso vmpara avaliar o módulo de teste, na verdade eu uso o próprio nó exige. Dessa forma, seu módulo se comporta exatamente como o uso require()(exceto suas modificações). Também a depuração é totalmente suportada.


7
A partir de meados de 2014 - npmjs.org/package/proxyquire torna trivial zombar de "exigir" dependências.
Arcseldon

proxyquire também é legal! Agora eles estão usando o módulo interno "module", que é muito melhor do que usar a VM do nó. Mas afinal é apenas uma questão de estilo. Eu gosto do meu módulo para usar o requisito original e trocar as dependências posteriormente. Além disso, a religação também permite substituir os globais.
Johannes Ewald

Muito interessante, procurando algo assim para usar no trabalho, este módulo também afeta os módulos downstream?
AKST

for proxyquire Diz-se na descrição que é usada para teste, 'Proxyies nodejs exigem para permitir a substituição de dependências durante o teste .' não para DI, certo?
Marwen Trabelsi

17

Eu construí o Electrolyte para esse fim. As outras soluções de injeção de dependência lá fora eram muito invasivas para o meu gosto, e mexer com o global requireé uma queixa particular minha.

O Electrolyte abrange módulos, especificamente aqueles que exportam uma função de "configuração" como você vê no middleware do Connect / Express. Essencialmente, esses tipos de módulos são apenas fábricas para algum objeto que eles retornam.

Por exemplo, um módulo que cria uma conexão com o banco de dados:

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

O que você vê na parte inferior é anotações , um bit extra de metadados que o Electrolyte usa para instanciar e injetar dependências, conectando automaticamente os componentes do aplicativo.

Para criar uma conexão com o banco de dados:

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

O eletrólito atravessa transitivamente o @require dependências e injeta instâncias como argumentos para a função exportada.

A chave é que isso é minimamente invasivo. Este módulo é completamente utilizável, independente do próprio eletrólito. Isso significa que seus testes de unidade podem testar apenas o módulo sob teste , passando objetos simulados sem a necessidade de dependências adicionais para reconectar os internos.

Ao executar o aplicativo completo, o Electrolyte entra no nível entre módulos, conectando as coisas sem a necessidade de globais, singletons ou encanamento excessivo.


1
Você esclareceria o que acontece no código que você postou quando uma chamada é lançada connect()? Mesmo que eu não esteja familiarizado com a API do MySQL para Nó, eu esperaria que esta chamada fosse assíncrona, portanto a ilustração não é clara.
Andrey Agibalov

atualmente usando eletrólito. Você alega que é fácil INJETAR módulos por meio de exportações ['@ require']. Mas se eu tiver que stub um dos módulos necessários, como isso é possível em eletrólitos? Atualmente, se exigirmos módulos, isso pode ser facilmente alcançado. Mas para eletrólitos, isso é um enorme negativo ... Você tem exemplos em que podemos usar a versão stubbed dos módulos e transmiti-la dinamicamente enquanto instanciação / ioc.use nos casos de teste. Então, basicamente, no teste de unidade, se podemos fazer ioc.create ( 'modulename') e, em seguida, fazer a injeção de módulos dependentes (mas os stubbed) seria o ideal ...
user1102171

1
Você não ligaria ioc.createde um teste de unidade. Um teste de unidade deve testar apenas o módulo em teste e não gerar outras dependências, incluindo Eletrólito. Seguindo esse conselho, você fariaobjToTest = require('modulename')(mockObj1, mockObj2);
Jared Hanson

8

Eu mesmo examinei isso. Não gosto de introduzir bibliotecas de utilitários de dependência mágica que fornecem mecanismos para seqüestrar minhas importações de módulos. Em vez disso, criei uma "diretriz de design" para minha equipe declarar explicitamente quais dependências podem ser zombadas ao introduzir uma exportação de função de fábrica em meus módulos.

Faço uso extensivo dos recursos do ES6 para parâmetros e desestruturação, a fim de evitar alguns clichês e fornecer um mecanismo de substituição de dependência nomeado.

Aqui está um exemplo:

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

E aqui está um exemplo de seu uso

import { factory } from './bar';

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

Desculpe a sintaxe do ES6 para quem não está familiarizado.


Muito engenhoso mesmo!
Arnold #

4

Recentemente, verifiquei esse tópico pelo mesmo motivo que o OP - a maioria das bibliotecas que encontrei reescreve temporariamente a instrução require. Eu tive vários graus de sucesso com esse método e, por isso, acabei usando a seguinte abordagem.

No contexto de um aplicativo expresso - envolvo app.js em um arquivo 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();

O mapa de objetos passado para o carregador se parece com o seguinte:

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

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

Em vez de chamar diretamente, é necessário ...

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

Se nenhum alias estiver localizado no carregador - o padrão será apenas um requisito regular. Isso tem dois benefícios: eu posso trocar em qualquer versão da classe e remover a necessidade de usar nomes de caminho relativos em todo o aplicativo (por isso, se eu precisar de uma lib personalizada abaixo ou acima do arquivo atual, não preciso percorrer , e require irá armazenar em cache o módulo na mesma chave). Também me permite especificar simulações em qualquer ponto do aplicativo, e não no conjunto de testes imediato.

Acabei de publicar um pequeno módulo npm por conveniência:

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


3

A realidade é que você pode testar o node.js sem o contêiner de IoC porque o JavaScript é uma linguagem de programação realmente dinâmica e você pode modificar quase tudo em tempo de execução.

Considere o seguinte:

import UserRepository from "./dal/user_repository";

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

export default UserController;

Assim, você pode substituir o acoplamento entre os componentes em tempo de execução. Eu gosto de pensar que devemos procurar desacoplar nossos módulos JavaScript.

A única maneira de obter desacoplamento real é removendo a referência ao UserRepository:

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

export default UserController;

Isso significa que em outro lugar você precisará fazer a composição do objeto:

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

export default new UserController(new UserRepository());

Eu gosto da ideia de delegar a composição do objeto em um contêiner de IoC. Você pode aprender mais sobre essa idéia no artigo O estado atual da inversão de dependência em JavaScript . O artigo tenta desmascarar alguns "mitos de container JavaScript IoC":

Mito 1: não há lugar para contêineres IoC em JavaScript

Mito 2: Não precisamos de contêineres IoC, já temos carregadores de módulos!

Mito 3: Inversão de dependência === injetar dependências

Se você também gosta da ideia de usar um contêiner de IoC, consulte o InversifyJS. A versão mais recente (2.0.0) suporta muitos casos de uso:

  • Módulos do kernel
  • Middleware do kernel
  • Use classes, literais de seqüência de caracteres ou símbolos como identificadores de dependência
  • Injeção de valores constantes
  • Injeção de construtores de classe
  • Injeção de fábricas
  • Fábrica de automóveis
  • Injeção de fornecedores (fábrica assíncrona)
  • Manipuladores de ativação (usados ​​para injetar proxies)
  • Injeções múltiplas
  • Ligações com tags
  • Decoradores de etiquetas personalizadas
  • Ligações nomeadas
  • Ligações contextuais
  • Exceções amigáveis ​​(por exemplo, dependências circulares)

Você pode aprender mais sobre isso no InversifyJS .


2

Para o ES6, desenvolvi este contêiner https://github.com/zazoomauro/node-dependency-injection

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

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

Depois, você pode definir, por exemplo, a escolha de transporte no contêiner:

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

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

Essa classe agora é muito mais flexível, pois você separou a opção de transporte da implementação e para o contêiner.

Agora que o serviço de mala direta está no contêiner, você pode injetá-lo como uma dependência de outras classes. Se você tem uma classe NewsletterManager como esta:

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

export default NewsletterManager

Ao definir o serviço newsletter_manager, o serviço de mala direta ainda não existe. Use a classe Reference para instruir o contêiner a injetar o serviço de correspondência quando inicializar o gerenciador de boletins:

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

Você também pode configurar o contêiner com arquivos de configuração, como arquivos Yaml, Json ou JS

O contêiner de serviço pode ser compilado por vários motivos. Esses motivos incluem a verificação de possíveis problemas, como referências circulares, e a eficiência do contêiner.

container.compile()

1

Depende do design do seu aplicativo. Obviamente, você pode fazer uma injeção como java onde cria um objeto de uma classe com a dependência passada no construtor como esta.

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

var cache = new Cache(mysqlStore);

Se você não estiver executando OOP em javascript, poderá criar uma função init que configure tudo.

No entanto, existe outra abordagem que você pode adotar, que é mais comum em um sistema baseado em eventos, como o node.js. Se você pode modelar seu aplicativo para agir (na maioria das vezes) apenas em eventos, tudo o que você precisa fazer é configurar tudo (o que eu costumo chamar chamando uma função init) e emitir eventos de um stub. Isso torna o teste bastante mais fácil e legível.


Obrigado pela sua resposta, mas não entendo completamente sua segunda parte da sua resposta.
Erik

1

Eu sempre gostei da simplicidade do conceito de IoC - "Você não precisa saber nada sobre ambiente, será chamado por alguém quando necessário"

Mas todas as implementações de IoC que vi fizeram exatamente o oposto - elas confundem o código com ainda mais coisas do que sem ele. Então, eu criei minha própria IoC que funciona como eu gostaria que fosse - ela fica oculta e invisível 90% do tempo .

É usado na estrutura da web MonoJS http://monojs.org

Estou falando de coisas simples, como compartilhar um objeto de conexão com o banco de dados até agora, mas não encontrei uma solução que me satisfaça.

É feito assim - registre o componente uma vez na configuração.

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

E use-o em qualquer lugar

app.db.findSomething()

Você pode ver o código completo de definição de componente (com DB Connection e outros componentes) aqui https://github.com/sinizinairina/mono/blob/master/mono.coffee

Este é o único lugar em que você precisa informar à IoC o que fazer, depois que todos esses componentes serão criados e conectados automaticamente e você não precisará mais ver o código específico da IoC em seu aplicativo.

O IoC em si https://github.com/alexeypetrushin/miconjs


6
Embora anunciado como DI, isso parece muito mais com um localizador de serviço.
precisa saber é o seguinte

2
Olha grande, vergonha é apenas em coffeescript
Rafael P. Miranda

1

Acho que ainda precisamos da Injeção de Dependências no Nodejs, porque isso afrouxa as dependências entre os serviços e torna a aplicação mais clara.

Inspirado no Spring Framework , também implemento meu próprio módulo para suportar a injeção de dependência no Nodejs. Meu módulo também é capaz de detectar ocode changes eauto reload os serviços sem reiniciar o aplicativo.

Visite meu projeto em: Buncha - container IoC

Obrigado!



0

Eu trabalhei com .Net, PHP e Java por muito tempo, então também queria ter uma injeção de dependência conveniente no NodeJS. As pessoas disseram que o DI interno no NodeJS é suficiente, como podemos obter com o Module. Mas isso não me satisfez bem. Eu queria manter um módulo não mais do que uma classe. Além disso, eu queria que o DI tivesse um suporte completo para o gerenciamento do ciclo de vida do módulo (módulo singleton, módulo transitório etc.), mas com o módulo Node, tive que escrever código manual com muita frequência. Por fim, eu queria facilitar o teste de unidade. Por isso, criei uma injeção de dependência para mim.

Se você estiver procurando por uma DI, tente. Pode ser encontrado aqui: https://github.com/robo-creative/nodejs-robo-container . Está totalmente documentado. Ele também aborda alguns problemas comuns com o DI e como resolvê-los da maneira OOP. Espero que ajude.


Sim, você está certo de que uma biblioteca de DI em seus projetos é importante para boas arquiteturas. Se você deseja ver um caso de uso para DI, consulte o leia-me deste repositório e também uma biblioteca de DI para o nó Jems DI .
Francisco Mercedes

-1

Eu criei recentemente uma biblioteca chamada circuitbox que permite usar injeção de dependência com o node.js. Realiza injeção de dependência em comparação com muitas das bibliotecas baseadas em pesquisa de dependência que eu já vi. O Circuitbox também suporta rotinas de criação e inicialização assíncronas. Abaixo está um exemplo:

Suponha que o código a seguir esteja em um arquivo chamado 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;

Suponha que o seguinte esteja no arquivo 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');
});

O Circuitbox permite definir seus componentes e declarar suas dependências como módulos. Uma vez inicializado, ele permite recuperar um componente. O Circuitbox injeta automaticamente todos os componentes exigidos pelo componente de destino e o fornece para uso.

O projeto está na versão alfa. Seus comentários, idéias e feedback são bem-vindos.

Espero que ajude!


-1

Acho que outras postagens fizeram um ótimo trabalho no argumento de usar DI. Para mim, as razões são

  1. Injete dependências sem conhecer seus caminhos. Isso significa que, se você alterar um local do módulo no disco ou trocá-lo por outro, não precisará tocar em todos os arquivos que dependem dele.

  2. Torna muito mais fácil zombar de dependências para teste sem a necessidade de substituir a requirefunção global de uma maneira que funcione sem problemas.

  3. Ajuda a organizar e raciocinar sobre sua aplicação como módulos fracamente acoplados.

Mas tive muita dificuldade em encontrar uma estrutura de DI que eu e minha equipe possamos adotar facilmente. Então, eu criei recentemente uma estrutura chamada deppie com base nesses recursos

  • API mínima que pode ser aprendida em alguns minutos
  • Não é necessário código / configuração / anotações extras
  • Mapeamento direto um para um require módulos
  • Pode ser adotado parcialmente para trabalhar com o código existente


-1

O Node.js requer DI tanto quanto qualquer outra plataforma. Se você estiver criando algo grande, o DI tornará mais fácil zombar das dependências do seu código e testá-lo completamente.

Os módulos da camada de banco de dados, por exemplo, não devem ser apenas necessários nos módulos de código comercial, porque, ao testar a unidade desses módulos de código comercial, os daos carregarão e se conectarão ao banco de dados.

Uma solução seria passar as dependências como parâmetros do módulo:

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

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

Dessa forma, as dependências podem ser ridicularizadas fácil e naturalmente, e você pode se concentrar em testar seu código, sem usar nenhuma biblioteca de terceiros complicada.

Existem outras soluções por aí (broadway, architect etc) que podem ajudá-lo com isso. embora eles possam fazer mais do que você deseja ou usar mais desorganização.


Quase pela evolução natural, acabei fazendo o mesmo. Eu passo uma dependência como parâmetros e funciona muito bem para testes.
Munkee

-1

Eu desenvolvi uma biblioteca que lida com a injeção de dependência de uma maneira simples, que diminui o código padrão. Cada módulo é definido por um nome exclusivo e uma função de controlador. Os parâmetros do controlador refletem as dependências do módulo.

Leia mais sobre KlarkJS

Breve exemplo:

KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
    return {
        log: function() { console.log('Hello from module myModuleName1') }
    };
});
  • myModuleName1 é o nome do módulo.
  • $nodeModule1é uma biblioteca externa de node_module. O nome resolve para node-module1. O prefixo $indica que é um módulo externo.
  • myModuleName2 é o nome de um módulo interno.
  • O valor de retorno do controlador é usado nos outros módulos internos, quando eles definem o parâmetro myModuleName1.

-1

Eu descobri essa pergunta ao responder a um problema no meu próprio módulo DI, perguntando por que alguém precisaria de um sistema DI para a programação do NodeJS.

A resposta estava claramente tendendo àquelas dadas neste tópico: depende. Existem trade-offs para ambas as abordagens e a leitura das respostas desta pergunta fornece uma boa forma delas.

Portanto, a resposta real para essa pergunta deve ser que, em algumas situações, você usaria um sistema DI, em outras não.

Dito isto, o que você deseja como desenvolvedor é não se repetir e reutilizar seus serviços em seus vários aplicativos.

Isso significa que devemos escrever serviços prontos para serem usados ​​no sistema DI, mas não vinculados às bibliotecas de DI. Para mim, isso significa que devemos escrever serviços 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
}

Dessa forma, seu serviço funciona, não importa se você o usa com ou sem uma ferramenta de DI.


-1

TypeDI é o mais doce de todos os mencionados aqui, veja este código em TypeDI

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

@Service()
class SomeClass {

    someMethod() {
    }

}

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

Veja este código também:

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;

}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.