NodeJSで依存性注入が必要ですか、それともどう対処すればよいですか?


219

現在、nodejsを使用していくつかの実験プロジェクトを作成しています。私は多くのJava EE WebアプリケーションをSpringでプログラミングしており、依存関係の注入が容易であることを高く評価しています。

今私は興味があります:ノードで依存関係注入をどのように行うのですか?または:私も必要ですか?プログラミングスタイルが異なるため、置き換えの概念はありますか?

これまでは、データベース接続オブジェクトの共有などの簡単なことについて話していましたが、満足できる解決策が見つかりませんでした。


1
あなたはDI、そのためのがopentable最近オープンソースのライブラリを使用することを決定する必要がありますgithub.com/opentable/spur-ioc私のしましたが(私はそこに働く)それを使用し、そしてそれはテストのための非常にシンプルかつ素晴らしいことだと言うことができます。
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では必要ありません。

この場合、2つのことを行うことができます。

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構文がどこかで間違っている可能性があります。また、これが最善の方法であると言っているわけではありませんが、それは方法です。Javascriptの達人は、他のソリューションと連携できるかもしれません。

更新:

これは、データベース接続に関する特定の質問に対処する必要があります。データベース接続ロジックをカプセル化するために、別のモジュールを作成します。このようなもの:

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 2012

3
@moteutsch JSにはほとんどの静的言語のようなインターフェースの概念がないので、なぜそうするのかわかりません。文書化された「インターフェース」で事前に合意されたものを使用したい場合でも、実際に持っているのは実装だけです。
JP Richardson

16
@JPRichardson 1つのライブラリに依存せずに、ロガーを使用するコンポーネントを作成するにはどうすればよいですか?私の場合require('my_logger_library')、私のコンポーネントを使用している人々は、自分のライブラリを使用するための要件をオーバーライドする必要があります。代わりに、ロガーの実装をコンポーネントの「コンストラクタ」または「init」メソッドにラップするコールバックを渡せるようにすることができます。それがDIの目的です。
moteutsch 2012

4
2014年半ば以降-npmjs.org/package/proxyquire は、「必須」依存関係のモックアウトを簡単にします。
arcseldon 2014

4
わかりません。1つのモジュールでrequireを置き換えても、別のモジュールでは置き換えられません。テストで関数にrequireを設定し、モジュールをテストする必要がある場合、テストするオブジェクトのrequireステートメントは、テストモジュールで設定された関数を使用しません。これはどのように依存関係を注入しますか?
HMR

72

requireあるNode.jsの中の依存関係を管理する方法と確かにそれは、直感的かつ効果的であるが、それはまた、その制限があります。

私のアドバイスは、Node.jsがその長所/短所を理解するために現在利用可能な依存性注入コンテナのいくつかを確認することです。それらのいくつかは:

ほんの数例を挙げると。

さて、本当の問題は、単純なものと比較して、Node.js DIコンテナーで何を達成できるrequireかです。

長所:

  • テスト容易性:モジュールは依存関係を入力として受け入れます
  • 制御の反転:アプリケーションのメインコードに触れずにモジュールを配線する方法を決定します。
  • モジュールを解決するためのカスタマイズ可能なアルゴリズム:依存関係には「仮想」識別子があり、通常はファイルシステム上のパスにバインドされていません。
  • 拡張性の向上:IoCおよび「仮想」識別子によって有効化されます。
  • 可能な他の豪華なもの:
    • 非同期初期化
    • モジュールのライフサイクル管理
    • DIコンテナー自体の拡張性
    • より高いレベルの抽象化(AOPなど)を簡単に実装できます

短所:

  • Node.jsの「経験」とは異なりrequireます。使用しないことは、Nodeの考え方から逸脱しているように感じます。
  • 依存関係とその実装の間の関係は、常に明示的ではありません。依存関係は実行時に解決され、さまざまなパラメーターの影響を受ける可能性があります。コードの理解とデバッグが難しくなる
  • 起動時間が遅い
  • 成熟度(現時点):現在のところ、現時点で実際に人気のあるソリューションはありません。そのため、それほど多くのチュートリアル、エコシステム、戦闘テストは行われていません。
  • 一部のDIコンテナーは、BrowserifyやWebpackなどのモジュールバンドルとうまく連携しません。

ソフトウェア開発に関連するものと同様に、DIを選択requireするか、要件、システムの複雑さ、およびプログラミングスタイルに依存します。


3
09年以降、状況は大きく変わったと思いますか?
JuhoVepsäläinen2013

13
10日前からですか?:)
マリオ

2
いやー。12月9日...知っている必要があります。
JuhoVepsäläinen2013

4
module.exports = function(deps){}種類のパターンを使用して、DIを「実装」しました。はい、機能しますが、理想的とは言えません。
JuhoVepsäläinen2013

3
モジュールは、入力としてその依存関係を受け付けし、依存関係は明示されない矛盾のように私に聞こえます。
アントンルデシュコ2014年

53

このスレッドは今のところかなり古いことは知っていますが、私はこれについて自分の考えを理解していると思います。TL; DRは、JavaScriptの型付けされていない動的な性質により、依存性注入(DI)パターンに頼ったり、DIフレームワークを使用したりせずに、実際に多くのことができるということです。ただし、アプリケーションが大きく複雑になるにつれて、DIはコードの保守性を確実に支援します。

C#のDI

JavaScriptでDIがそれほど必要ではない理由を理解するには、C#のような強く型付けされた言語を調べると役立ちます。(C#を知らない人にはお詫びしますが、簡単に理解できるはずです。)車とそのホーンを説明するアプリがあるとします。次の2つのクラスを定義します。

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

ここでは2つの重要なことを行いました。最初に、Hornクラスが実装するインターフェースを導入しました。これによりCar、特定の実装ではなく、インターフェイスにクラスをコーディングできます。これで、コードはを実装するものなら何でも取ることができますIHorn。次に、ホーンのインスタンス化を外しCarて、代わりに渡しました。これにより、上記の問題が解決され、特定のインスタンスとそのライフサイクルを管理するアプリケーションのメイン機能に任されます。

これが意味することは、Carクラスに触れずに車が使用できる新しいタイプのホーンを導入できるということです。

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

メインはFrenchHorn代わりにクラスのインスタンスを注入するだけで済みます。これにより、テストも大幅に簡素化されます。コンストラクターMockHornに注入するクラスを作成して、クラスのみを個別にCarテストしていることを確認できますCar

上記の例は、手動による依存性注入を示しています。通常、DIはフレームワーク(C#の世界ではUnityNinjectなど)で行われます。これらのフレームワークは、依存関係グラフをたどり、必要に応じてインスタンスを作成することにより、すべての依存関係の配線を行います。

標準の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

さらに、Node requireはすべてをキャッシュするため、モジュールは基本的にコンテナに格納されたシングルトンです。モジュールで実行する他requirehornモジュールは、まったく同じインスタンスを取得します。これにより、データベース接続などのシングルトンオブジェクトの共有が非常に簡単になります。

今でも、carモジュールが独自の依存関係を取得する責任があるという問題がありますhorn。車のホーンに別のモジュールを使用する場合requireは、carモジュールのステートメントを変更する必要があります。これはあまり一般的ではありませんが、テストで問題が発生します。

テスト問題を処理する通常の方法は、proxyquireを使用することです。JavaScriptの動的な性質により、proxyquireはrequireの呼び出しをインターセプトし、代わりに提供するスタブ/モックを返します。

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スタイルで記述されたコードで動作するように設計されており、非常にDRYで非常にシンプルにしようとしています。上記car.jsとまったく同じhorn.jsモジュールを使用して、index.jsボーラスでモジュールを次のように書き換えることができます。

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

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

基本的な考え方は、インジェクターを作成することです。すべてのモジュールをインジェクターに登録します。次に、単に必要なものを解決します。ボーラスは依存関係グラフをたどり、必要に応じて依存関係を作成して挿入します。このようなおもちゃの例ではあまり節約できませんが、複雑な依存関係ツリーを持つ大規模なアプリケーションでは、節約量は膨大です。

Bolusは、オプションの依存関係やテストグローバルなどの多くの気の利いた機能をサポートしていますが、標準のNode.jsアプローチと比較して、私が見た2つの主要な利点があります。最初に、類似したアプリケーションがたくさんある場合は、ベース用のプライベートnpmモジュールを作成して、インジェクターを作成し、それに便利なオブジェクトを登録できます。次に、特定のアプリが、AngularJSの方法と同様に、必要に応じて追加、オーバーライド、および解決できます。インジェクターは動作します。次に、ボーラスを使用して、依存関係のさまざまなコンテキストを管理できます。たとえば、ミドルウェアを使用して、リクエストごとに子インジェクターを作成し、インジェクターにユーザーID、セッションID、ロガーなどを、それらに依存するモジュールとともに登録できます。次に、リクエストを処理するために必要なものを解決します。これにより、リクエストごとにモジュールのインスタンスが提供され、すべてのモジュール関数呼び出しにロガーなどを渡す必要がなくなります。


1
たとえば、非常に簡潔なモックを実行できるproxyquireなどの代替手段があることも事実ですsinon。たとえばlet readFileStub = sinon.stub(fs, 'readFile').yields(new Error('something went wrong'));fs.readFileを介してスタブを元に戻すまで、後続の呼び出しはエラーを返しますreadFileStub.restore()。個人的に私は疑わしい使用のDIを見つけました。それは、クラス/オブジェクトの使用がほとんど必要であるように感じるからです。
ケビン

この良い+詳細な回答に感謝します。C#で見出しDIを最初に読んだので、ほとんど見逃していました。
Konstantin A. Magg 2017年

1
すばらしい答えです。私は、個人的な好みの問題として、あなたの考えは、大規模なプロジェクトのために2019年にあるか思ったんだけど、あなたは好きですた-ノードにおけるDI / IoCのか、単にでモック/スタブjestrewireproxyquire、など?ありがとう。
ジェイミーコークヒル

素晴らしいバランスのとれた答え!ありがとうございました。
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のインジェクターに触発されましたが、別のアプローチを使用しました。私はvmtest-moduleの評価に使用していません。実際、私はノード自身のrequireを使用しています。このようにして、モジュールは使用方法とまったく同じように動作しrequire()ます(変更を除く)。また、デバッグは完全にサポートされています。


7
2014年半ば以降-npmjs.org/package/proxyquire は、「必須」依存関係のモックアウトを簡単にします。
arcseldon 2014

proxyquireもクールです!ノードのVMを使用するよりもはるかに優れた内部「モジュール」モジュールを使用しています。しかし結局のところ、それは単なるスタイルの問題です。私は自分のモジュールが元のrequireを使用し、後で依存関係を交換するのが好きです。さらに、再配線により、グローバルをオーバーライドすることもできます。
ヨハネスエヴァルド14

このようなものを仕事で使用することを非常に興味深く探していましたが、このモジュールはダウンストリームモジュールにも影響しますか?
2014年

以下のためproxyquireそれはそれはテストのために使われているという説明で言われています、「プロキシのnodejsは時の依存関係をオーバーライドできるようにするために必要とテスト」。DI用ではないですよね?
Marwen Trabelsi 2016

17

私はこの目的のためだけに電解液を作りました。他の依存性注入ソリューションは、私の好みにはあまりにも侵襲的であり、グローバルをrequireいじるのは私の私の不満です。

Electrolyteはモジュール、特に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');

Electrolyteは@require'dの依存関係を推移的に横断し、インスタンスを引数としてエクスポートされた関数に注入します。

重要なのは、これが最小限の侵襲性であるということです。このモジュールは完全に使用可能で、電解質自体とは無関係です。つまり、単体テストではテスト対象のモジュールのみをテストでき、内部を再配線するための追加の依存関係を必要とせずにモックオブジェクトを渡すことができます。

完全なアプリケーションを実行すると、電解質はモジュール間レベルで介入し、グローバル、シングルトン、または過度の配管を必要とせずに、相互に配線します。


1
呼び出しがconnect()スローされたときに投稿したコードで何が起こるかを明確にしますか?MySql API for Nodeに慣れていない場合でも、この呼び出しは非同期であると予想されるため、図は明確ではありません。
Andrey Agibalov 2015年

現在電解液を使用しています。あなたはexports ['@ require']を介してモジュールを簡単にインジェクトでき​​ると主張しています。しかし、必要なモジュールの1つをスタブ化する必要がある場合、それは電解液でどのように達成できますか。現在、モジュールが必要な場合、これは簡単に実現できます。しかし、電解質の場合、これは大きな欠点です...モジュールのスタブバージョンを使用して、インスタンス化/ioc.useの間にテストケースから動的に渡すことができる例はありますか?したがって、基本的に単体テストでは、ioc.create( 'modulename')を実行してから、依存モジュール(ただし、
スタブされた

1
ioc.create単体テストから呼び出すことはありません。単体テストでは、テスト対象のモジュールのみをテストし、電解質などの他の依存関係は含めないでください。このアドバイスに従って、あなたはそうするでしょうobjToTest = require('modulename')(mockObj1, mockObj2);
Jared Hanson

8

私はこれを自分で調べました。私は、モジュールのインポートをハイジャックするメカニズムを提供するmagic dependency utilsライブラリを導入するのが嫌いです。代わりに、モジュール内にファクトリー関数エクスポートを導入することで、どの依存関係をモックできるかをチームが明確に述べるための「設計ガイドライン」を思いつきました。

一部のボイラープレートを回避し、名前付きの依存関係オーバーライドメカニズムを提供するために、パラメーターとデストラクチャに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の構文に不慣れな方は、すみません。


本当に独創的です!
アーノルド

4

私は最近、OPとほぼ同じ理由でこのスレッドをチェックしました-私が遭遇したほとんどのライブラリーは、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...
};

次に、直接requireを呼び出すのではなく...

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

ローダーにエイリアスがない場合は、デフォルトで通常のrequireになります。これには2つの利点があります。クラスのどのバージョンでも入れ替えることができ、アプリケーション全体で相対パス名を使用する必要がなくなります(現在のファイルの下または上にカスタムライブラリが必要な場合は、トラバースする必要がありません。 、およびrequireは同じキーに対してモジュールをキャッシュします)。また、即時テストスイートではなく、アプリの任意の時点でモックを指定できます。

便宜上、小さな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での依存関係の逆転の現在の状態」をご覧ください。この記事では、「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

これは、アプリケーションの設計によって異なります。このようなコンストラクターで渡される依存関係を持つクラスのオブジェクトを作成するインジェクションのようなjavaを明らかに実行できます。

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

var cache = new Cache(mysqlStore);

JavaScriptでOOPを実行していない場合は、すべてを設定するinit関数を作成できます。

ただし、node.jsなどのイベントベースのシステムでより一般的な方法を使用することもできます。アプリケーションをモデル化して(ほとんどの場合)イベントを処理するだけの場合は、すべてを設定して(通常はinit関数を呼び出して)、スタブからイベントを発行するだけです。これにより、テストがかなり簡単で読みやすくなります。


回答ありがとうございます。回答の2番目の部分は完全には理解できません。
エリック

1

IoCコンセプトのシンプルさを常に気に入っていました-「環境について何も知る必要がなく、必要なときに誰かから呼び出されます」

しかし、私が見たすべてのIoC実装は、正反対のことをしました。コードは、コードがない場合よりもさらに多くのものが散らかっています。そこで、自分のIoCを作成しました。期待どおりに機能し、90%の時間、非表示になり、見えなくなります

MonoJS Webフレームワークhttp://monojs.orgで使用されています

これまでは、データベース接続オブジェクトの共有などの簡単なことについて話していましたが、満足できる解決策が見つかりませんでした。

これはこのように行われます-コンポーネントを設定に一度登録します。

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

そしてどこでもそれを使用して

app.db.findSomething()

完全なコンポーネント定義コード(DB接続とその他のコンポーネントを含む)は、https://github.com/sinizinairina/mono/blob/master/mono.coffeeで確認できます。

これは、IoCに何をすべきかを指示する必要がある唯一の場所です。その後、これらのすべてのコンポーネントが自動的に作成および配線され、アプリケーションにIoC固有のコードを表示する必要がなくなります。

IoC自体https://github.com/alexeypetrushin/miconjs


6
DIとしてアドバタイズされますが、これはサービスロケータのようなものです。
KyorCode 2014年

2
見栄えがいい、恥ずかしがってるだけだよ
ラファエル・P・ミランダ

1

Nodejsでの依存性注入は、サービス間の依存関係を緩和し、アプリケーションをより明確にするため、依然として必要だと思います。

Spring Frameworkに触発されて、Nodejsでの依存性注入をサポートするための独自のモジュールも実装します。私のモジュールは、アプリケーションを再起動せずにcode changesおよびauto reloadサービスを検出することもできます。

Buncha-IoCコンテナで私のプロジェクトにアクセスしてください

ありがとうございました!



0

私は長い間.Net、PHP、Javaで作業していたので、NodeJSにも便利な依存性注入を用意したかったのです。NodeJSに組み込まれているDIで、モジュールでそれを取得できるので十分だと人々は言っています。しかし、それは私を十分に満足させませんでした。モジュールはクラスにすぎないようにしたかったのです。さらに、DIでモジュールライフサイクル管理(シングルトンモジュール、一時モジュールなど)を完全にサポートする必要がありましたが、ノードモジュールでは非常に頻繁に手動でコードを記述する必要がありました。最後に、単体テストを簡単にしたかった。それが、私が自分のためにDependency Injectionを作成した理由です。

DIをお探しの場合は、ぜひお試しください。これは、https//github.com/robo-creative/nodejs-robo-containerにあります。完全に文書化されています。また、DIに関するいくつかの一般的な問題と、それらをOOPで解決する方法についても説明します。それが役に立てば幸い。


はい、あなたは正しいです。プロジェクトのDIライブラリは、優れたアーキテクチャにとって重要です。DIのユースケースを見たい場合は、このリポジトリのreadmeでノードJems DIの DIライブラリも参照してください。
フランシスコメルセデス

-1

私は最近と呼ばれるライブラリを作成circuitboxあなたはnode.js.と依存関係注入を使用することができます 私が見た依存関係ルックアップベースのライブラリの多くに対して、それは真の依存関係注入を実行します。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対1の直接マッピング
  • 既存のコードを操作するために部分的に採用できます

-1

これは、次のように柔軟でシンプルでなければなりません。

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


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

node.jsでの依存性注入に関する記事を書きました。

私はそれがこれであなたを助けることを望みます。


-1

Node.jsには、他のプラットフォームと同じくらいDIが必要です。大きなものを構築する場合、DIを使用すると、コードの依存関係を模擬し、コードを徹底的にテストすることが容易になります。

たとえば、データベースレイヤーモジュールは、ビジネスコードモジュールで必要になるだけではありません。これらのビジネスコードモジュールをユニットテストすると、DAOSが読み込まれ、データベースに接続します。

1つの解決策は、依存関係をモジュールパラメータとして渡すことです。

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

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

このようにして、依存関係を簡単かつ自然に模倣でき、トリッキーなサードパーティライブラリを使用せずにコードのテストに集中できます。

他にも、ブロードウェイ、アーキテクトなど、さまざまな解決策があります。彼らはあなたが望むよりも多くのことをするかもしれないし、より多くの混乱を使うかもしれません。


ほとんど自然な進化によって、私は同じことをしてしまいました。依存関係をパラメーターとして渡しますが、テストには最適です。
マンキー2017年

-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

NodeJSプログラミングにDIシステムが必要になる理由を尋ねる自分の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.