Node.js。 設計および開発パターン

こんにちは親愛なる読者。



私たちは、「 Node.js Design Patterns 」という本に興味を持っています。この本は、存在年にわたって非常に肯定的なレビューを集めており、重要な情報を提供しています。







ロシア市場ではNode.jsの本は文字通り指で数えることができることを考慮して、この本の著者による記事をよく理解することをお勧めします。 その中で、Cassiaro氏は彼の作品に非常に有益なエクスカーションを行い、また、素材Node.jsの「パターン」自体の現象の微妙さを説明します。



調査に参加してください。







Node.jsの専門家は、私たち全員が他のプラットフォームや他の言語で学び、使用しなければならなかった古典的なデザインパターンに加えて、JavaScript言語とプラットフォーム自体のプロパティに起因するコードでそのようなテクニックとパターンを実装する必要があります



お知らせ



もちろん、正しいギャングを作成するには、ギャングフォーフォー、ギャングオブフォーによって記述されたデザインパターンが依然として必要です。 しかし、JavaScriptが他の言語で学んだほとんどすべての規則に違反することは秘密ではありません。 デザインパターンも例外ではありません。JavaScriptで古いルールを再考し、新しいルールを考案する必要があることに注意してください。 従来のJavaScriptデザインパターンはさまざまな方法で実装でき、通常のプログラミングトリックはパターンのステータスにまで拡大する可能性があります。 また、認識されているアンチパターンの一部がJavaScript / Node.jsで広く使用されていることにも驚かないでください(たとえば、正しいカプセル化は見過ごされがちです。これは入手が難しく、しばしば「 オブジェクト破損 」につながる可能性があり、公共モロゾフ。」



一覧



以下は、Node.jsアプリケーションで使用される一般的なデザインパターンの短いリストです。 ObserverまたはLonerがJavaScriptでどのように実装されているかを再度説明するつもりはありませんが、Node.jsで使用される典型的なトリックに焦点を当てたいと思います。



Node.jsアプリケーションを作成し、同僚のコードを研究したとき、私は自分の練習に基づいてこのリストをコンパイルしたので、完全または最終のふりをしません。 アドオンは大歓迎です。

そして、これらのパターンのいくつかを既に見たり、使用したりしても驚くことはありません。



ディレクトリの要件(擬似プラグイン)



このパターンは間違いなく最も人気のあるパターンの1つです。 ディレクトリのすべてのモジュールが必要です。それだけです。 そのシンプルさのために、これは最も便利で一般的なトリックの1つです。 Npmには、このパターンを実装する多くのモジュールがあります:少なくともrequire-allrequire-manyrequire-treerequire-namespacerequire-dirrequire-directoryrequire-fu



使用方法に応じて、ディレクトリの要件は単純な補助機能またはプラグインシステムの一種として解釈できます。依存関係は必要なモジュールにハードコードされず、ディレクトリのコンテンツから埋め込まれます。



簡単な例



var requireDir = require('require-all'); var routes = requireDir('./routes'); app.get('/', routes.home); app.get('/register', routes.auth.register); app.get('/login', routes.auth.login); app.get('/logout', routes.auth.logout);
      
      







より複雑な例(接続性の低下、拡張性)



 var requireFu = require('require-fu'); requireFu(__dirname + '/routes')(app);
      
      







/routes





/routes





独自のURLルートを定義する関数です。



 module.exports = function(app) { app.get("/about", function(req, res) { //  }); }
      
      







2番目の例では、必要なモジュールを変更せずに、新しいモジュールを作成するだけで新しいルートを追加できます。 この方法は明らかに強力であり、さらに、必要なモジュールと必要なモジュールの間の接続性が低下します。



アプリケーションオブジェクト(即興の依存性注入)



このパターンは他の言語/他のプラットフォームでも非常に一般的ですが、JavaScriptの動的な性質のため、このパターンはNode.jsで非常に効果的(そして一般的)です。 この場合、 アプリケーション全体のバックボーンとして機能する1つのオブジェクトを作成します 。 通常、このオブジェクトはアプリケーションの入り口でインスタンス化され、さまざまなアプリケーションサービスの接着剤として機能します。 Facadeに非常に似ていると思いますが、Node.jsでは、依存性注入用の非常に原始的なコンテナの実装でも広く使用されています。



このパターンの典型的な例: App





オブジェクトがあります App





(またはアプリケーション自体と同じ名前のオブジェクト)、および初期化後のすべてのサービスがこのラージオブジェクトにアタッチされます。







 var app = new MyApp(); app.db = require('./db'); app.log = new require('./logger')(); app.express = require('express')(); app.i18n = require('./i18n').initialize(); app.models = require('./models')(app); require('./routes')(app);
      
      







その後、必要に応じてApp object



を渡して、他のモジュールで使用できるようにするか、関数引数の形式をとるか、または require









アプリケーションの依存関係のほとんどがこのコアオブジェクトにアタッチされると、実際には依存関係はそれを使用するモジュールに外部から注入されます



ただし、注意してください:読み込まれた依存関係を抽象化レベルで提供せずにこのパターンを使用すると、維持が難しく、原則としてすべての点でアンチパターンの神オブジェクトに似た全知のオブジェクトになる可能性があります。



幸いなことに、この問題に対処するのに役立つライブラリがいくつかあります。たとえば、このパターンの非常に洗練されたバージョンを実装するアーキテクチャフレームワークであるBroadwayは、優れた抽象化を提供し、サービスライフサイクルをより適切に制御できます。







 var app = new broadway.App(); app.use(require("./plugins/helloworld")); app.init(...); app.hello("world"); // ./plugins/helloworld exports.attach = function (options) { // "this" –    ! this.hello = function (world) { console.log("Hello "+ world + "."); }; };
      
      







機能の傍受(モンキーパッチとAOP)



関数フックは、JavaScriptのような動的言語に典型的な別のデザインパターンです-ご想像のとおり、Node.jsで非常に人気があります。 それは、 その(彼女の)パフォーマンスを傍受することによって、関数(またはメソッド)の動作を補完することから成ります 。 通常、この手法を使用すると、開発者は実行前(プリフック)または実行後(ポストフック)に通話を傍受できます。 微妙な点は、Node.jsがモンキーパッチと組み合わせて使用​​されることが多いことです。この手法は非常に強力ですが、同時に危険です。







 var hooks = require('hooks'), Document = require('./path/to/some/document/constructor'); //   : `hook`, `pre` `post` for (var k in hooks) { Document[k] = hooks[k]; } Document.prototype.save = function () { // ... }; //   ,     'save' Document.post('save', function createJob (next) { this.sendToBackgroundQueue(); next(); });
      
      







Mongooseを使用したことがある場合、このパターンが実際に動作しているのは間違いありません。 そうでない場合-npmでは、すべての好みに合わせてそのようなモジュールがたくさんあります 。 しかし、それだけではありません。Node.jsコミュニティでは、「アスペクト指向プログラミング」(AOP)という用語は、関数フックと同義と見なされることがよくあります。npmを見てください。 誰もが本当にそれをAOPと呼ぶことができますか? 私の答えはNOです。 AOPでは、特定の動作を単一の関数(または関数のセット)に手動で付加するのではなく、エンドツーエンドの責任をスライスに適用する必要があります。 一方、 Node.jsの仮想AOPソリューションでは、フックを適切に適用できます。その場合、アドバイスは、たとえば、正規表現を使用して定義された単一のスライスによって結合された多くの関数に拡張されます。 この表現に一致するようにすべてのモジュールがスキャンされます。



コンベヤー(中間コード)



これがNode.jsの本質です。 コンベヤはどこにでも存在し、形式、目的、ユースケースが異なります。 原則として、コンベアは互いに接続された一連の処理モジュールであり、1つのモジュールの出力が別のモジュールの入力として機能します。 Node.jsでは、これは多くの場合、プログラムが次の形式の多くの機能を持つことを意味します。



 function(/* input/output */, next) { next(/* err and/or output */) }
      
      







このようなものを中間コードConnectまたはExpressを参照を呼び出すことに慣れているかもしれませんが、このパターンを使用する境界ははるかに広いです。 たとえば、 フックは、すべての事前/事後機能を(中間の)パイプラインに結合して「最大の柔軟性を提供」するフック(上記)の一般的な実装です。



通常、このパターンはasync.waterfallasync.auto 、またはpromiseシーケンスを使用して何らかの方法で実装されます。さらに、実行のフローを制御できるだけでなく、アプリケーションの1つまたは別の部分の拡張性も提供できます。



例:非同期



 async.waterfall([ function(callback){ callback(null, 'one', 'two'); }, function(arg1, arg2, callback){ callback(null, 'three'); } ]};
      
      







別の一般的なNode.jsコンポーネントにはパイプライン機能があります。 ご想像のとおり、いわゆるフローについて説明していますが、 伝達できない場合のフローとは何ですか? 一般に中間コードと関数チェーンは実行フローと拡張性を制御するための普遍的なソリューションですスレッドはバイトまたはオブジェクトの形式で送信されたデータを処理するのにより適しています



例:スレッド



 fs.createReadStream("data.gz") .pipe(zlib.createGunzip()) .pipe(through(function write(data) { //...     ... this.queue(data); }) //    .pipe(fs.createWriteStream("out.txt"));
      
      







結論



Node.jsは、その性質上、開発者が特定のパターンと反復的なテクニックを使用することを推奨します。 それらのいくつかを調べ、正しく適用された場合に一般的な問題を効果的に解決する方法を示しました 。 さらに、実装によってパターンがどのように見えるかを確認しました。



All Articles