NodeJS에 의존성 주입이 필요합니까, 아니면 어떻게 처리해야합니까?


219

나는 현재 nodejs로 실험 프로젝트를 만들고 있습니다. Spring으로 많은 Java EE 웹 애플리케이션을 프로그래밍했으며 거기에 의존성 주입의 용이성을 높이 평가했습니다.

이제 궁금합니다. 노드로 의존성 주입을 어떻게합니까? 또는 : 필요합니까? 프로그래밍 스타일이 다르기 때문에 대체 개념이 있습니까?

지금까지 데이터베이스 연결 객체 공유와 같은 간단한 것들에 대해 이야기하고 있지만 나를 만족시키는 해결책을 찾지 못했습니다.


1
당신은 DI를 사용하기로 결정한 경우, OpenTable이는 최근에 라이브러리를 오픈 소스 : github.com/opentable/spur-ioc I의 한 (나는이 일을)를 사용하고 테스트하는 아주 간단하고 좋아요 말할 수 있습니다.
tybro0103

답변:


107

즉, C # / Java에서와 같이 종속성 주입 컨테이너 또는 서비스 로케이터가 필요하지 않습니다. Node.js는를 활용하므로 module pattern생성자 또는 속성 삽입을 수행 할 필요가 없습니다. 여전히 할 수 있지만.

JS의 가장 큰 장점은 원하는 것을 달성하기 위해 무엇이든 수정할 수 있다는 것입니다. 테스트 할 때 편리합니다.

내 절름발이 모범을 보아라.

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

모듈에 MyClass따라 어떻게 달라 fs집니까? @ShatyemShekhar가 언급했듯이 실제로 다른 언어와 마찬가지로 생성자 또는 속성 삽입을 수행 할 수 있습니다. 그러나 Javascript에는 필요하지 않습니다.

이 경우 두 가지 작업을 수행 할 수 있습니다.

fs.readdirSync메소드 를 스텁 하거나 호출 할 때 완전히 다른 모듈을 리턴 할 수 있습니다 require.

방법 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;

방법 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);

}

핵심은 Node.js와 Javascript의 힘을 활용하는 것입니다. 참고, 저는 CoffeeScript 사용자이므로 JS 구문이 어딘가에 잘못되었을 수 있습니다. 또한 이것이 최선의 방법이라고 말하지는 않지만 방법입니다. 자바 스크립트 전문가는 다른 솔루션과 함께 할 수도 있습니다.

최신 정보:

데이터베이스 연결과 관련된 특정 질문을 해결해야합니다. 데이터베이스 연결 논리를 캡슐화하기 위해 별도의 모듈을 만들었습니다. 이 같은:

MyDbConnection.js: (더 나은 이름을 선택하십시오)

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    
}

그런 다음 데이터베이스 연결이 필요한 모든 모듈에는 MyDbConnection모듈 만 포함됩니다 .

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. 

이 예제를 그대로 따르지 마십시오. module의존성을 관리하기 위해 패턴을 활용한다는 의사 소통을 시도하는 것은 끔찍한 예 입니다. 희망적으로 이것은 조금 더 도움이됩니다.


42
이것은 테스트와 관련하여 사실이지만 DI에는 다른 이점이 있습니다. DI를 사용하면 구현이 아닌 인터페이스로 프로그래밍 할 수 있습니다.
moteutsch

3
@moteutsch JS에 대부분의 정적 언어와 같은 인터페이스 개념이 없기 때문에 왜 그런지 잘 모르겠습니다. 문서화 된 "인터페이스"에 대해 사전 동의 된 일부를 사용하려는 경우에도 실제로 구현 한 것입니다.
JP Richardson

16
@JPRichardson 라이브러리에 의존하지 않고 로거를 사용하는 구성 요소를 작성하려면 어떻게해야합니까? I require('my_logger_library')인 경우 내 구성 요소를 사용하는 사람들은 자신의 라이브러리 사용 요구를 무시해야합니다. 대신, 사람들이 로거 구현을 컴포넌트 "constructor"또는 "init"메소드로 랩핑하는 콜백을 전달할 수 있습니다. 이것이 DI의 목적입니다.
moteutsch

4
2014 년 중반 현재 npmjs.org/package/proxyquire 는 "필수"종속성을 조롱하는 것이 쉽지 않습니다.
arcseldon

4
나는 그것을 얻지 못한다. 한 모듈에서 요구를 교체한다고해서 다른 모듈로 교체하지는 않는다. 테스트에서 함수에 require를 설정 한 다음 모듈을 테스트해야한다면 테스트 할 객체의 require 문을 테스트 모듈에 설정된 함수를 사용하지 마십시오. 이것은 어떻게 의존성을 주입합니까?
HMR

72

require이다 Node.js를 종속성을 관리하는 방법을 확실하게 그것은 직관적이고 효과적이지만, 그것은 또한 한계가있다.

내 충고는 Node.js에서 현재 사용할 수있는 Dependency Injection 컨테이너 중 일부를 살펴보고 장단점에 대한 아이디어를 얻는 것입니다. 그들 중 일부는 다음과 같습니다.

몇가지 말하자면.

이제 진짜 질문은 Node.js DI 컨테이너로 무엇을 달성 할 수 require있습니까?

장점 :

  • 더 나은 테스트 가능성 : 모듈은 의존성을 입력으로 받아들입니다.
  • Inversion of Control : 어플리케이션의 메인 코드를 건드리지 않고 모듈을 배선하는 방법을 결정하십시오.
  • 모듈을 해결하기위한 사용자 정의 가능한 알고리즘 : 종속성은 "가상"식별자를 가지며 일반적으로 파일 시스템의 경로에 바인딩되지 않습니다.
  • 확장 성 향상 : IoC 및 "가상"식별자로 가능합니다.
  • 가능한 다른 멋진 것들 :
    • 비동기 초기화
    • 모듈 라이프 사이클 관리
    • DI 컨테이너 자체의 확장 성
    • 더 높은 수준의 추상화 (예 : AOP)를 쉽게 구현할 수 있습니다

단점 :

  • Node.js의 "경험"과는 다릅니다. 사용하지 않는 require것은 Node의 사고 방식에서 벗어나는 느낌입니다.
  • 종속성과 구현 간의 관계가 항상 명시 적이지는 않습니다. 종속성은 런타임시 해결 될 수 있으며 다양한 매개 변수의 영향을받습니다. 코드를 이해하고 디버깅하기가 더 어려워집니다
  • 느린 시작 시간
  • (순간) 만기 : 현재 솔루션의 아무도는 정말 , 너무 많은하지 자습서, 시험없이 생태계가 아닌 전투 순간에 인기.
  • 일부 DI 컨테이너는 Browserify 및 Webpack과 같은 모듈 번 들러에서 제대로 작동하지 않습니다.

소프트웨어 개발과 관련하여 DI 중에서 선택하거나 require요구 사항, 시스템 복잡성 및 프로그래밍 스타일에 따라 달라집니다.


3
'09 년 이후 상황이 크게 변했다고 생각하십니까?
Juho Vepsäläinen

13
10 일 전부터 되었습니까? :)
Mario

2
누 12 월 9 일 ... 알고 있어야합니다.
Juho Vepsäläinen

4
나는 module.exports = function (deps) {} 종류의 패턴을 사용하여 DI를 "구현"했습니다. 예, 작동하지만 이상적이지는 않습니다.
Juho Vepsäläinen

3
모듈은 입력으로 종속성을 허용 하고 종속성은 명시 적없는 모순처럼 나에게 소리.
Anton Rudeshko

53

나는이 시점 에서이 실이 상당히 오래되었다는 것을 알고 있지만, 나는 이것에 대한 나의 생각에 차임 할 것이라고 생각했다. TL; DR은 JavaScript의 형식화되지 않은 동적 특성으로 인해 DI (종속성 주입) 패턴이나 DI 프레임 워크를 사용하지 않고도 실제로 많은 작업을 수행 할 수 있습니다. 그러나 응용 프로그램이 커지고 복잡 해짐에 따라 DI는 코드 유지 관리에 도움이 될 수 있습니다.

C #의 DI

DI가 JavaScript에서 그다지 필요하지 않은 이유를 이해하려면 C #과 같이 강력한 형식의 언어를 보는 것이 도움이됩니다. (C #을 모르는 사람들에게 사과하지만 따라 가기 쉬워야합니다.) 자동차와 경적을 설명하는 앱이 있다고 가정 해 보겠습니다. 두 개의 클래스를 정의합니다.

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

이런 식으로 코드를 작성하는 데는 몇 가지 문제가 있습니다.

  1. Car클래스는 단단히의 경적의 특정 구현에 연결되어 Horn클래스입니다. 자동차가 사용하는 혼 유형을 변경하려면 혼의 Car사용법이 변경되지 않더라도 클래스 를 수정해야합니다 . 또한 Car클래스의 종속성과는 별도로 클래스를 테스트 할 수 없기 때문에 테스트하기가 어렵습니다 Horn.
  2. Car클래스의 라이프 사이클에 대한 책임 Horn클래스입니다. 이와 같은 간단한 예제에서 큰 문제는 아니지만 실제 응용 프로그램에서 종속성에는 종속성이 있으며 종속성이 있습니다. Car클래스는 종속성의 전체 트리를 생성해야합니다. 이것은 복잡하고 반복적 일뿐만 아니라 수업의 "단일 책임"에 위배됩니다. 인스턴스가 아닌 자동차가되는 데 중점을 두어야합니다.
  3. 동일한 종속성 인스턴스를 재사용 할 방법이 없습니다. 다시 말하지만,이 장난감 응용 프로그램에서는 중요하지 않지만 데이터베이스 연결을 고려하십시오. 일반적으로 애플리케이션간에 공유되는 단일 인스턴스가 있습니다.

이제 의존성 주입 패턴을 사용하도록 이것을 리팩토링 해 봅시다.

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

우리는 여기서 두 가지 중요한 일을했습니다. 먼저, 우리 Horn클래스가 구현 하는 인터페이스를 소개했습니다 . 이를 통해 Car특정 구현 대신 인터페이스에 클래스를 코딩 할 수 있습니다 . 이제 코드는 구현하는 모든 것을 취할 수 있습니다 IHorn. 둘째, 우리는 혼 인스턴스화를 꺼내서 Car대신 전달했습니다. 이것은 위의 문제를 해결하고 특정 인스턴스와 수명주기를 관리하는 응용 프로그램의 주요 기능으로 남겨 둡니다.

이것이 의미하는 바는 Car클래스 를 건드리지 않고 자동차에 사용할 새로운 유형의 경적을 도입 할 수 있다는 것입니다 .

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

메인은 FrenchHorn대신 클래스 의 인스턴스를 주입 할 수 있습니다 . 또한 테스트를 크게 단순화합니다. MockHorn클래스를 생성 하여 Car생성자 에 주입 하여 Car클래스 만 격리 하여 테스트 할 수 있습니다 .

위의 예는 수동 종속성 주입을 보여줍니다. 일반적으로 DI는 프레임 워크 (예 : C # 세계의 Unity 또는 Ninject )로 수행됩니다. 이 프레임 워크는 종속성 그래프를 따라 가고 필요에 따라 인스턴스를 만들어 모든 종속성 배선을 수행합니다.

표준 Node.js 방식

이제 Node.js에서 같은 예제를 보자. 우리는 아마도 코드를 3 개의 모듈로 나눌 것입니다.

// horn.js
module.exports = {
    honk: function () {
        console.log("beep!");
    }
};

// car.js
var horn = require("./horn");
module.exports = {
    honkHorn: function () {
        horn.honk();
    }
};

// index.js
var car = require("./car");
car.honkHorn();

JavaScript는 형식이 지정되지 않았기 때문에 이전과 거의 동일한 긴밀한 결합이 없습니다. car모듈은 모듈이 내보낼 때마다 honk메소드 를 호출하려고 시도하기 때문에 인터페이스가 필요하지 않습니다 horn.

또한 노드 require가 모든 것을 캐시 하기 때문에 모듈은 기본적으로 컨테이너에 저장된 싱글 톤입니다. 을 수행하는 다른 모듈 require상의 horn모듈은 동일한 인스턴스를 얻을 것이다. 이를 통해 데이터베이스 연결과 같은 단일 객체를 매우 쉽게 공유 할 수 있습니다.

이제는 car모듈이 자체 의존성을 가져 오는 문제가 여전히 남아 horn있습니다. 자동차가 혼에 다른 모듈을 사용하도록하려면 모듈의 require명령문 을 변경 해야 car합니다. 이것은 매우 일반적인 일이 아니지만 테스트에 문제를 일으 킵니다.

사람들이 테스트 문제를 처리하는 일반적인 방법은 proxyquire 입니다. JavaScript의 동적 특성으로 인해 proxyquire는 요청에 대한 호출을 가로 채고 대신 제공하는 스텁 / 모형을 반환합니다.

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

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

// Now make test assertions on car...

이것은 대부분의 응용 프로그램에 충분합니다. 앱에서 작동하면 함께 진행하십시오. 그러나 내 경험에 따르면 응용 프로그램이 커지고 복잡 해짐에 따라 이와 같은 코드를 유지 관리하는 것이 어려워집니다.

JavaScript의 DI

Node.js는 매우 유연합니다. 위의 방법에 만족하지 않으면 종속성 주입 패턴을 사용하여 모듈을 작성할 수 있습니다. 이 패턴에서 모든 모듈은 팩토리 함수 (또는 클래스 생성자)를 내 보냅니다.

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

이는 index.js모듈이 인스턴스 수명주기 및 배선을 담당 한다는 점에서 C # 방법과 매우 유사합니다 . 단위 테스트는 모의 / 스텁을 함수에 전달할 수 있으므로 매우 간단합니다. 다시 말하지만 이것이 응용 프로그램에 충분하다면 함께 가십시오.

볼 루스 DI 프레임 워크

C #과 달리 종속성 관리에 도움이되는 표준 DI 프레임 워크는 없습니다. npm 레지스트리에는 여러 프레임 워크가 있지만 널리 채택 된 프레임 워크는 없습니다. 이러한 옵션 중 많은 부분이 다른 답변에서 이미 인용되었습니다.

나는 사용 가능한 옵션에 특히 만족하지 않았기 때문에 bolus 라는 내 자신을 썼습니다 . Bolus는 위의 DI 스타일로 작성된 코드로 작동하도록 설계되었으며 매우 건조 하고 매우 단순합니다. 위와 똑같은 모듈을 사용하면 다음 car.jshorn.js같이 index.jsbolus를 사용 하여 모듈을 다시 작성할 수 있습니다 .

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

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

기본 아이디어는 인젝터를 작성하는 것입니다. 인젝터에 모든 모듈을 등록합니다. 그런 다음 필요한 것을 간단히 해결합니다. Bolus는 종속성 그래프를 살펴보고 필요에 따라 종속성을 작성하고 주입합니다. 이와 같은 장난감 예제에서는 그다지 절약하지 않지만 복잡한 종속성 트리가있는 대규모 응용 프로그램에서는 크게 절약 할 수 있습니다.

Bolus는 선택적 종속성 및 테스트 전역과 같은 멋진 기능을 지원하지만 표준 Node.js 접근 방식과 비교하여 볼 수있는 두 가지 주요 이점이 있습니다. 먼저 유사한 응용 프로그램이 많은 경우 인젝터를 생성하고 유용한 객체를 등록하는 기본 용 개인 npm 모듈을 만들 수 있습니다. 그런 다음 AngularJS 와 같은 방식으로 특정 앱이 필요에 따라 추가, 재정의 및 해결할 수 있습니다.인젝터 작동합니다. 둘째, bolus를 사용하여 다양한 종속성 컨텍스트를 관리 할 수 ​​있습니다. 예를 들어, 미들웨어를 사용하여 요청마다 하위 인젝터를 작성하고 인젝터에 사용자 ID, 세션 ID, 로거 등을 모듈과 함께 등록 할 수 있습니다. 그런 다음 요청을 처리하는 데 필요한 것을 해결하십시오. 이것은 요청마다 모듈의 인스턴스를 제공하며 모든 모듈 함수 호출에 로거 등을 전달하지 않아도됩니다.


1
그것은에 대안이있는 것 또한 사실입니다 proxyquire같은 sinon당신은 매우 간결 망신 시켰습니다, 예를 들어, 수행 할 수있는 let readFileStub = sinon.stub(fs, 'readFile').yields(new Error('something went wrong'));위해 다음 후속 호출을 fs.readFile당신을 통해 스텁을 되돌릴 때까지 오류를 반환합니다 readFileStub.restore(). 개인적으로 나는 클래스 / 객체의 사용이 거의 필요하다고 느끼기 때문에 의심스러운 사용의 DI를 발견합니다. 이는 자바 스크립트의 기능적 기대가 주어지면 모호한 가정입니다.
Kevin

이 좋은 + 자세한 답변 주셔서 감사합니다. C #에서 헤드 라인 DI를 처음 읽었을 때 거의 그것을 놓쳤습니다 .
Konstantin A. Magg

1
좋은 대답입니다. 나는 당신의 생각은 당신이 선호 않는, 개인적인 취향의 문제로, 큰 프로젝트의 경우 2019 년에 무엇인지 궁금하네요 - DI / IOC의 노드에서, 또는 단지와 조롱 / 스텁 jest, rewire, proxyquire, 등? 감사.
Jamie Corkhill 2016 년

균형 잡힌 답변! 감사합니다.
Johnny Oshika

36

또한 이것을 수행하기 위해 모듈을 작성했습니다 . 이것을 rewire 라고 합니다. 사용 npm install rewire하고 다음을 수행하십시오.

var rewire = require("rewire"),
    myModule = rewire("./path/to/myModule.js"); // exactly like require()

// Your module will now export a special setter and getter for private variables.
myModule.__set__("myPrivateVar", 123);
myModule.__get__("myPrivateVar"); // = 123


// This allows you to mock almost everything within the module e.g. the fs-module.
// Just pass the variable name as first parameter and your mock as second.
myModule.__set__("fs", {
    readFile: function (path, encoding, cb) {
        cb(null, "Success!");
    }
});
myModule.readSomethingFromFileSystem(function (err, data) {
    console.log(data); // = Success!
});

나는 Nathan MacInnes의 인젝터 에서 영감을 얻었 지만 다른 접근법을 사용했습니다. vm테스트 모듈을 평가하는 데 사용하지 않으며 실제로 노드 자체 요구 사항을 사용합니다. 이런 식으로 모듈은 사용하는 것처럼 정확하게 동작 require()합니다 (수정 제외). 또한 디버깅이 완전히 지원됩니다.


7
2014 년 중반 현재 npmjs.org/package/proxyquire 는 "필수"종속성을 조롱하는 것이 쉽지 않습니다.
arcseldon

proxyquire도 멋지다! 그들은 이제 노드의 vm을 사용하는 것보다 훨씬 나은 내부 "모듈"-모듈을 사용하고 있습니다. 그러나 결국 그것은 단지 스타일의 문제입니다. 내 모듈이 원래 요구 사항을 사용하고 나중에 종속성을 교체하는 것이 좋습니다. 또한 rewire는 전역을 재정의 할 수도 있습니다.
Johannes Ewald

직장에서 사용할 이와 같은 것을 매우 흥미로 웠습니다.이 모듈이 다운 스트림 모듈에도 영향을 줍니까?
akst

대한 proxyquire 그것은이 테스트를 위해 사용되는 것으로 설명했다있어, '프록시 nodejs는 동안 종속 관계를 무시 있도록하기 위해 필요로 테스트 .' DI가 아닌가?
Marwen Trabelsi

17

나는 이 목적을 위해 전해질 을 만들었습니다 . 거기에있는 다른 의존성 주입 솔루션은 내 취향에 너무 침습적이며, 세계 require를 망친다는 것은 내 불만입니다.

전해질은 모듈, 특히 Connect / Express 미들웨어에서 볼 수있는 것처럼 "설정"기능을 내보내는 모듈을 포함합니다. 기본적으로 이러한 유형의 모듈은 반환하는 일부 객체의 팩토리 일뿐입니다.

예를 들어 데이터베이스 연결을 생성하는 모듈은 다음과 같습니다.

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

맨 아래에 표시되는 주석은 Electrolyte가 종속성을 인스턴스화하고 주입하여 응용 프로그램 구성 요소를 자동으로 연결하는 데 사용하는 추가 메타 데이터 인 주석 입니다.

데이터베이스 연결을 작성하려면 다음을 수행하십시오.

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

전해질은 @require'd 의존성을 전 이적으로 통과하고 인스턴스를 내 보낸 함수의 인수로 주입합니다.

열쇠는 이것이 최소 침습적이라는 것입니다. 이 모듈은 전해질 자체와 관계없이 완전히 사용할 수 있습니다. 즉, 단위 테스트는 테스트 중인 모듈 만 테스트 하여 내부를 다시 배선하기 위해 추가 종속성없이 모의 객체를 전달할 수 있습니다 .

전체 응용 프로그램을 실행할 때 전해질은 모듈 간 레벨에 들어가서 전역, 단일 톤 또는 과도한 배관이 없어도 함께 배선합니다.


1
connect()던지기 호출시 게시 한 코드에서 어떤 일이 발생하는지 설명 하시겠습니까 ? MySql API for Node에 익숙하지 않더라도이 호출은 비동기식 일 것으로 예상되므로 그림이 명확하지 않습니다.
Andrey Agibalov 2016 년

현재 전해질을 사용하고 있습니다. 내보내기 [ '@require']를 통해 모듈을 쉽게 주입 할 수 있다고 주장합니다. 그러나 필요한 모듈 중 하나를 스터브 해야하는 경우 전해질에서 어떻게 달성 할 수 있습니까? 현재 모듈이 필요한 경우 쉽게 달성 할 수 있습니다. 그러나 전해질의 경우 이것은 큰 부정적인 것입니다 .... 스텁 된 버전의 모듈을 사용하고 테스트 사례에서 인스턴스화 / IOC 사용 중에 동적으로 전달할 수있는 예제가 있습니까? 따라서 기본적으로 단위 테스트에서 ioc.create ( 'modulename')을 수행 한 다음 종속 모듈 (스텁 된 모듈)을 삽입하는 것이 이상적입니다 ...
user1102171

1
ioc.create단위 테스트 에서는 전화하지 않습니다 . 단위 테스트는 테스트 중인 모듈 테스트해야하며 전해질을 포함한 다른 종속성을 가져 오지 않아야합니다. 이 조언에 따라, 당신은 할 것objToTest = require('modulename')(mockObj1, mockObj2);
Jared Hanson

8

나는 이것을 직접 보았다. 모듈 가져 오기를 가로채는 메커니즘을 제공하는 매직 종속성 유틸리티 라이브러리를 도입하는 것을 싫어합니다. 대신 모듈에 팩토리 함수 내보내기를 도입하여 어떤 종속성을 조롱 할 수 있는지 명시 적으로 명시 할 수 있도록 "디자인 지침"을 마련했습니다.

일부 상용구를 피하고 명명 된 종속성 재정의 메커니즘을 제공하기 위해 매개 변수 및 구조 해제에 ES6 기능을 광범위하게 사용합니다.

예를 들면 다음과 같습니다.

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

그리고 여기 그 사용법의 예가 있습니다.

import { factory } from './bar';

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

익숙하지 않은 사람들을 위해 ES6 구문을 실례합니다.


참으로 독창적입니다!
arnold

4

최근에 OP와 거의 같은 이유로이 스레드를 확인했습니다. 내가 만난 대부분의 lib는 require 문을 일시적으로 다시 씁니다. 나는이 방법으로 여러 가지 성공을 거두었으므로 다음과 같은 접근법을 사용했습니다.

빠른 응용 프로그램의 맥락에서-bootstrap.js 파일에 app.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();

로더에 전달 된 오브젝트 맵은 다음과 같습니다.

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

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

그런 다음 직접 전화하는 것보다는 ...

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

로더에 별칭이 없으면 기본 요구 사항으로 기본 설정됩니다. 여기에는 두 가지 이점이 있습니다. 모든 버전의 클래스에서 바꿀 수 있으며 응용 프로그램 전체에서 상대 경로 이름을 사용할 필요가 없습니다 (따라서 현재 파일 아래 또는 현재 파일 위에 사용자 정의 라이브러리가 필요한 경우 트래버스 할 필요가 없습니다) require는 동일한 키에 대해 모듈을 캐시합니다). 또한 즉각적인 테스트 스위트가 아닌 앱의 어느 시점에서나 mock을 지정할 수 있습니다.

편의를 위해 작은 npm 모듈을 방금 게시했습니다.

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


3

실제로 JavaScript는 동적 프로그래밍 언어이므로 거의 모든 것을 런타임에 수정할 수 있기 때문에 IoC 컨테이너없이 node.js를 테스트 할 수 있습니다.

다음을 고려하세요:

import UserRepository from "./dal/user_repository";

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

export default UserController;

따라서 런타임시 구성 요소 간의 연결을 무시할 수 있습니다. JavaScript 모듈을 분리하는 것을 목표로하고 싶습니다.

실제 디커플링을 달성하는 유일한 방법은 다음에 대한 참조를 제거하는 것입니다 UserRepository.

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

export default UserController;

이것은 다른 곳에서 객체 구성을 수행해야 함을 의미합니다.

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

export default new UserController(new UserRepository());

객체 구성을 IoC 컨테이너에 위임한다는 아이디어가 마음에 듭니다. 이 아이디어에 대한 자세한 내용은 JavaScript의 현재 종속성 반전 상태 기사를 참조하십시오 . 이 기사는 "자바 스크립트 IoC 컨테이너 신화"에 대해 설명하려고한다.

오해 1 : JavaScript에는 IoC 컨테이너를위한 장소가 없다

오해 2 : IoC 컨테이너가 필요하지 않습니다. 이미 모듈 로더가 있습니다!

오해 3 : 의존성 반전 === 의존성 주입

IoC 컨테이너 사용 아이디어가 마음에 들면 InversifyJS를 살펴볼 수 있습니다. 최신 릴리스 (2.0.0)는 많은 사용 사례를 지원합니다.

  • 커널 모듈
  • 커널 미들웨어
  • 클래스, 문자열 리터럴 또는 기호를 종속성 식별자로 사용
  • 상수 값 주입
  • 클래스 생성자 주입
  • 공장 주입
  • 자동 공장
  • 공급자 주입 (비 동기화 공장)
  • 활성화 처리기 (프록시를 주입하는 데 사용)
  • 다중 주사
  • 태그 바인딩
  • 맞춤 태그 데코레이터
  • 명명 된 바인딩
  • 상황에 맞는 바인딩
  • 친근한 예외 (예 : 순환 종속성)

이에 대한 자세한 내용은 InversifyJS 에서 확인할 수 있습니다 .


2

ES6의 경우이 컨테이너를 개발했습니다 https://github.com/zazoomauro/node-dependency-injection

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

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

그런 다음 컨테이너에서 운송 선택을 예를 들어 설정할 수 있습니다.

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

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

이 클래스는 구현과 컨테이너로의 전송 선택을 분리함으로써 훨씬 유연 해졌습니다.

이제 메일러 서비스가 컨테이너에 있으므로 다른 클래스의 종속성으로이를 주입 할 수 있습니다. 다음과 같이 NewsletterManager 클래스가있는 경우 :

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

export default NewsletterManager

newsletter_manager 서비스를 정의 할 때 메일러 서비스가 아직 없습니다. 컨테이너가 뉴스 레터 관리자를 초기화 할 때 메일러 서비스를 주입하도록 지시하려면 Reference 클래스를 사용하십시오.

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

Yaml, Json 또는 JS 파일과 같은 구성 파일을 사용하여 컨테이너를 설정할 수도 있습니다.

서비스 컨테이너는 여러 가지 이유로 컴파일 될 수 있습니다. 이러한 이유에는 순환 참조와 같은 잠재적 문제를 확인하고 컨테이너를보다 효율적으로 만드는 것이 포함됩니다.

container.compile()

1

응용 프로그램의 디자인에 따라 다릅니다. 생성자와 같은 의존성을 가진 클래스의 객체를 생성하는 주입과 같은 자바를 분명히 할 수 있습니다.

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

var cache = new Cache(mysqlStore);

자바 스크립트에서 OOP를 수행하지 않는 경우 모든 것을 설정하는 init 함수를 만들 수 있습니다.

그러나 node.js와 같은 이벤트 기반 시스템에서 더 일반적인 다른 접근 방식이 있습니다. 응용 프로그램을 이벤트에 대해서만 (대부분의 시간) 행동으로 모델링 할 수 있다면 모든 것을 설정하고 (일반적으로 init 함수를 호출하여 수행) 스텁에서 이벤트를 생성하면됩니다. 따라서 테스트가 훨씬 쉽고 읽기 쉽습니다.


답변 해 주셔서 감사합니다. 답변의 두 번째 부분을 완전히 이해하지 못했습니다.
Erik

1

저는 항상 IoC 개념의 단순함을 좋아했습니다. "환경에 대해 알 필요가 없습니다. 필요할 때 누군가에게 전화를 걸 것입니다."

그러나 내가 본 모든 IoC 구현은 정반대였습니다. 코드가 없으면 코드가 훨씬 더 복잡해졌습니다. 그래서 나는 내가 원하는대로 작동하는 나만의 IoC를 만들었습니다. 그것은 숨겨지고 보이지 않는 시간의 90 %입니다 .

MonoJS 웹 프레임 워크에서 사용됩니다 http://monojs.org

지금까지 데이터베이스 연결 객체 공유와 같은 간단한 것들에 대해 이야기하고 있지만 나를 만족시키는 해결책을 찾지 못했습니다.

구성에 한 번 등록하십시오.

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

어디서나 사용

app.db.findSomething()

https://github.com/sinizinairina/mono/blob/master/mono.coffee 에서 전체 구성 요소 정의 코드 (DB 연결 및 기타 구성 요소 포함)를 볼 수 있습니다.

이것은 IoC에 무엇을해야하는지 알려 주어야 할 유일한 장소입니다. 그 후에 모든 구성 요소가 자동으로 생성되고 연결되며 더 이상 응용 프로그램에서 IoC 관련 코드를 볼 필요가 없습니다.

IoC 자체 https://github.com/alexeypetrushin/miconjs


6
DI로 광고되었지만 서비스 로케이터와 훨씬 비슷합니다.
KyorCode

2
멋지다, 부끄러운 것만 coffescript에있다
Rafael P. Miranda

1

Nodejs에 의존성 주입이 여전히 필요하다고 생각합니다. 서비스 간의 의존성을 완화하고 응용 프로그램을 명확하게하기 때문입니다.

Spring Framework 에서 영감을 얻어 Nodejs의 종속성 주입을 지원하는 자체 모듈도 구현합니다. 내 모듈은 응용 프로그램을 다시 시작하지 않고도 code changesauto reload서비스 를 감지 할 수 있습니다.

내 프로젝트 방문 : Buncha-IoC 컨테이너

감사합니다!



0

.Net, PHP 및 Java로 오랫동안 작업했기 때문에 NodeJS에서도 편리한 Dependency Injection을 원했습니다. 사람들은 NodeJS에 내장 된 DI만으로도 모듈과 함께 사용할 수 있다고 말했다. 그러나 그것은 나를 만족시키지 못했습니다. 모듈을 클래스 이상으로 유지하고 싶었습니다. 또한 DI가 모듈 수명주기 관리 (단일 모듈, 과도 모듈 등)를 완전히 지원하기를 원했지만 노드 모듈을 사용하면 수동 코드를 매우 자주 작성해야했습니다. 마지막으로, Unit Test를 더 쉽게 만들고 싶었습니다. 그래서 내가 직접 의존성 주입을 만들었습니다.

DI를 찾고 있다면 시도해보십시오. https://github.com/robo-creative/nodejs-robo-container 에서 찾을 수 있습니다 . 완전히 문서화되었습니다. 또한 DI와 관련된 몇 가지 일반적인 문제와 OOP 방식으로 해결하는 방법도 설명합니다. 도움이 되길 바랍니다.


그렇습니다. 프로젝트의 DI 라이브러리는 올바른 아키텍처에 중요합니다. DI의 사용 사례를보고 싶다면이 저장소의 readme 및 Jems DI 노드의 DI 라이브러리도 참조하십시오 .
Francisco Mercedes

-1

최근에 node.js와의 의존성 주입을 사용할 수있는 circuitbox 라는 라이브러리를 만들었습니다. 그것은 내가 본 의존성 검색 기반 라이브러리의 많은 것에 대한 진정한 의존성 주입을 수행합니다. Circuitbox는 비동기 생성 및 초기화 루틴도 지원합니다. 아래는 예입니다.

다음 코드가 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;

다음이 파일 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를 사용하면 구성 요소를 정의하고 해당 종속성을 모듈로 선언 할 수 있습니다. 초기화되면 구성 요소를 검색 할 수 있습니다. Circuitbox는 대상 구성 요소에 필요한 모든 구성 요소를 자동으로 주입하여 사용하도록합니다.

프로젝트는 알파 버전입니다. 귀하의 의견, 아이디어 및 의견을 환영합니다.

그것이 도움이되기를 바랍니다!


-1

DI 사용에 대한 주장에서 다른 게시물이 큰 역할을했다고 생각합니다. 나에게 이유는

  1. 경로를 모른 채 의존성을 주입하십시오. 즉, 디스크에서 모듈 위치를 변경하거나 다른 모듈로 교체 할 경우 해당 모듈에 의존하는 모든 파일을 건드릴 필요가 없습니다.

  2. require문제없이 작동하는 방식으로 전역 함수 를 재정의하지 않고도 테스트를 위해 종속성을 훨씬 쉽게 조롱 할 수 있습니다.

  3. 응용 프로그램을 느슨하게 결합 된 모듈로 구성하고 추론하는 데 도움이됩니다.

그러나 저는 팀과 제가 쉽게 채택 할 수있는 DI 프레임 워크를 찾는 데 어려움을 겪었습니다. 그래서 최근에는 이러한 기능을 기반으로 deppie라는 프레임 워크를 만들었습니다.

  • 몇 분 안에 배울 수있는 최소 API
  • 추가 코드 / 구성 / 주석이 필요하지 않습니다
  • require모듈 에 일대일 직접 매핑
  • 기존 코드로 작업하기 위해 부분적으로 채택 가능

-1

다음과 같이 유연하고 간단해야합니다.

var MyClass1 = function () {}
var MyClass2 = function (myService1) {
    // myService1.should.be.instanceof(MyClass1); 
}


container.register('myService1', MyClass1);
container.register('myService2', MyClass2, ['myService1']);

node.js에서 Dependency Injection에 대한 기사를 작성했습니다.

나는 이것이 당신을 도울 수 있기를 바랍니다.


-1

Node.js에는 다른 플랫폼만큼 DI가 필요합니다. 큰 것을 구축하는 경우 DI는 코드의 종속성을 모의하고 코드를 철저히 테스트하기 쉽게 해줍니다.

예를 들어 데이터베이스 코드 모듈은 비즈니스 코드 모듈에만 필요하지 않아야합니다. 이러한 비즈니스 코드 모듈을 단위로 테스트 할 때 daos가 데이터베이스에로드되어 연결되기 때문입니다.

한 가지 해결책은 종속성을 모듈 매개 변수로 전달하는 것입니다.

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

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

이러한 방식으로 종속성을 쉽고 자연스럽게 조롱 할 수 있으며 까다로운 타사 라이브러리를 사용하지 않고도 코드 테스트에 계속 집중할 수 있습니다.

이를 도울 수있는 다른 솔루션 (브로드 웨이, 건축가 등)이 있습니다. 그들은 당신이 원하는 것보다 더 많은 일을하거나 더 많은 혼란을 줄 수 있지만.


거의 자연적인 진화를 통해 나는 똑같은 일을 마쳤습니다. 매개 변수로 종속성을 전달하면 테스트에 효과적입니다.
munkee

-1

간단한 방법으로 의존성 주입을 처리하는 라이브러리를 개발하여 상용구 코드를 줄였습니다. 각 모듈은 고유 한 이름과 컨트롤러 기능으로 정의됩니다. 컨트롤러의 매개 변수는 모듈의 종속성을 반영합니다.

KlarkJS대해서 더 읽어보세요.

간단한 예 :

KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
    return {
        log: function() { console.log('Hello from module myModuleName1') }
    };
});
  • myModuleName1 모듈의 이름입니다.
  • $nodeModule1의 외부 라이브러리입니다 node_module. 이름은로 확인됩니다 node-module1. 접두사 $는 외부 모듈임을 나타냅니다.
  • myModuleName2 내부 모듈의 이름입니다.
  • 컨트롤러의 반환 값은 매개 변수를 정의 할 때 다른 내부 모듈에서 사용됩니다 myModuleName1.

-1

나는 왜 자신의 DI 모듈에서 NodeJS 프로그래밍을 위해 DI 시스템이 필요한지 묻는 질문에 대답 하면서이 질문을 발견했습니다 .

그 답은이 글에서 주어진 것들에 분명하게 영향을 미쳤습니다. 두 가지 접근법 모두에 대한 절충점이 있으며이 질문의 대답을 읽으면 좋은 모양을 얻을 수 있습니다.

따라서이 질문에 대한 진정한 대답은 어떤 상황에서는 DI 시스템을 사용하고 다른 상황에서는 사용하지 않아야한다는 것입니다.

즉, 개발자로서 원하는 것은 자신을 반복하지 않고 다양한 응용 프로그램에서 서비스를 재사용하는 것입니다.

즉, DI 시스템에서 사용할 수 있지만 DI 라이브러리에 연결되지 않은 서비스를 작성해야합니다. 나에게 이것은 다음과 같은 서비스를 작성해야 함을 의미합니다.

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
}

DI 도구를 사용하거나 사용하지 않고 서비스를 사용하는 경우 서비스가 작동하는 방식은 중요하지 않습니다.


-1

TypeDI 는 여기에 언급 된 것 중 가장 달콤합니다. TypeDI에서이 코드를보십시오

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

@Service()
class SomeClass {

    someMethod() {
    }

}

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

이 코드도보십시오 :

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;

}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.