4

私は .NET の世界から来た NodeJs 開発に不慣れです。Javascript で DI / DIP を再構築するベスト プラクティスを Web で検索しています。

.NET では、コンストラクターで依存関係を宣言しますが、javascript では、require ステートメントを介してモジュール レベルで依存関係を宣言するという一般的なパターンが見られます。

私にとっては、 require を使用すると、特定のファイルに結合されているように見えますが、コンストラクターを使用して依存関係を受け取る方がより柔軟です。

JavaScript のベスト プラクティスとして何をすることをお勧めしますか? (IOC 技術ソリューションではなく、アーキテクチャ パターンを探しています)

Web を検索すると、このブログ投稿にたどり着きました (コメントで非常に興味深い議論が行われています): https://blog.risingstack.com/dependency-injection-in-node-js/

それは私の対立をかなりうまく要約しています。私が話していることを理解できるように、ブログ投稿のコードを次に示します。

// team.js
var User = require('./user');

function getTeam(teamId) {  
  return User.find({teamId: teamId});
}

module.exports.getTeam = getTeam; 

簡単なテストは次のようになります。

 // team.spec.js
    var Team = require('./team');  
    var User = require('./user');

    describe('Team', function() {  
      it('#getTeam', function* () {
        var users = [{id: 1, id: 2}];

        this.sandbox.stub(User, 'find', function() {
          return Promise.resolve(users);
        });

        var team = yield team.getTeam();

        expect(team).to.eql(users);
      });
    });

VS DI:

// team.js
function Team(options) {  
  this.options = options;
}

Team.prototype.getTeam = function(teamId) {  
  return this.options.User.find({teamId: teamId})
}

function create(options) {  
  return new Team(options);
}

テスト:

// team.spec.js
var Team = require('./team');

describe('Team', function() {  
  it('#getTeam', function* () {
    var users = [{id: 1, id: 2}];

    var fakeUser = {
      find: function() {
        return Promise.resolve(users);
      }
    };

    var team = Team.create({
      User: fakeUser
    });

    var team = yield team.getTeam();

    expect(team).to.eql(users);
  });
});
4

2 に答える 2

8

あなたの質問に関して: JS コミュニティには一般的な慣習があるとは思いません。私は実際に両方のタイプを見てきましたが、変更 ( rewireproxyquire など) とコンストラクターの挿入 (多くの場合、専用の DI コンテナーを使用) が必要です。ただ、個人的にはDIコンテナを使わない方がJSとの相性が良いと思います。それは、JS が第一級市民としての機能を備えた動的言語だからです。それを説明しましょう:

DI コンテナーを使用すると、すべてのコンストラクターの注入が強制されます。次の 2 つの主な理由により、構成のオーバーヘッドが大きくなります。

  1. 単体テストでモックを提供する
  2. 環境について何も知らない抽象的なコンポーネントを作成する

最初の引数について: 単体テストのためだけにコードを調整することはありません。コードがよりクリーンでシンプルになり、汎用性が高く、エラーが発生しにくくなる場合は、試してみてください。しかし、あなたの唯一の理由が単体テストである場合、私はトレードオフを取りません。必要な変更とモンキーパッチを使用すると、かなり遠くまで行くことができます。そして、あまりにも多くのモックを作成していることに気付いた場合は、単体テストを作成するのではなく、統合テストを作成する必要があります。Eric Elliott は、この問題について素晴らしい記事を書いています。

第二引数について:有効な引数です。インターフェイスのみを考慮し、実際の実装は考慮しないコンポーネントを作成する場合は、単純なコンストラクター インジェクションを選択します。しかし、JS はすべてにクラスを使用することを強制するわけではないので、なぜ関数だけを使用しないのでしょうか?

関数型プログラミングでは、ステートフル IO を実際の処理から分離することが一般的なパラダイムです。たとえば、フォルダー内のファイルの種類をカウントすることになっているコードを書いている場合、次のように書くことができます (特に、どこにでもクラスを強制する言語から来ている場合):

const fs = require("fs");

class FileTypeCounter {
    countFileTypes(dirname, callback) {
        fs.readdir(dirname, function (err) {
            if (err) return callback(err);
            // recursively walk all folders and count file types
            // ...
            callback(null, fileTypes);
        });
    }
}

それをテストしたい場合は、偽のfsモジュールを挿入するためにコードを変更する必要があります。

class FileTypeCounter {
    constructor(fs) {
        this.fs = fs;
    }
    countFileTypes(dirname, callback) {
        this.fs.readdir(dirname, function (err) {
            // ...
        });
    }
}

ここで、クラスを使用しているすべての人がfsコンストラクターに注入する必要があります。これは退屈であり、依存関係グラフが長くなるとコードがより複雑になるため、開発者は DI コンテナーを発明しました。DI コンテナーは、構成するだけで、DI コンテナーはインスタンス化を把握します。

しかし、純粋な関数を書くだけではどうでしょうか?

function fileTypeCounter(allFiles) {
    // count file types
    return fileTypes;
}

function getAllFilesInDir(dirname, callback) {
    // recursively walk all folders and collect all files
    // ...
    callback(null, allFiles);
}

// now let's compose both functions
function getAllFileTypesInDir(dirname, callback) {
    getAllFilesInDir(dirname, (err, allFiles) => {
        callback(err, !err && fileTypeCounter(allFiles));
    });
}

これで、すぐに使用できる 2 つの非常に用途の広い関数が用意されました。1 つは IO を実行し、もう 1 つはデータを処理します。fileTypeCounter純粋な関数であり、テストが非常に簡単です。は不純ですが、非常に一般的なタスクであり、他の人が統合テストを作成したnpmgetAllFilesInDirで既に見つかることがよくあります。少しの制御フローで関数を構成するだけです。これは、アプリケーション全体が正しく動作することを確認する統合テストの典型的なケースです。getAllFileTypesInDir

IO とデータ処理の間でコードを分離することにより、何も注入する必要がまったくありません。何も注射する必要がなければ、それは良い兆候です。純粋な関数はテストが最も簡単で、プロジェクト間でコードを共有する最も簡単な方法です。

于 2016-06-17T10:36:58.760 に答える