概要
Node.jsのアプリを簡単に開発するツールとしてNode-REDは、とても素晴らしいと思います。これまでにも、GUIプログラミング・ツールは、たくさん登場してきており、Microsoft Visual Studio など 画面設計をGUIを参照しながら開発できて、必要な部分だけをコーディングするして完成させていくことができます。 一方、Node-RED は、予め定義された処理モジュールのアイコンを配置して線で繋いでいくことで、プログラムを組み立てていくことができます。
この種のツールには、設計思想に基づく得手不得手が存在するので、チャットWebアプリケーションを開発するケースで、Node-REDを利用する場合と Express.js を利用してコーディングする場合について、比較検証してみました。
Node-REDとは
今更「Node-REDとは」と言うのは、そんな事知ってるよ。。。 と笑われそうなんですが、自分の頭の整理のために、調べた内容で記載しておきます。
Node-REDは、Node.jsで開発された フローベースのプログラミング環境です。 IoT分野で広く使われており、簡単にAPIやサービスを統合して、イベント・ドリブンのアプリケーションを開発できる事を目指しています。
参考資料 https://js.foundation/projects/
Node-REDプロジェクトは、JS Foundation にホストされています。 JS Foundation は、JavaScriptにおけるオープンなエコシステムの発展や関連ツールの普及促進などを目的とした非営利団体で、Linux Foundation を母体としています。
現在のソフトウェア開発者は、クリティカルなアプリケーションを作成、テスト、展開するために、ますます多くのOSSに依存しています。 JS Foundationは、主要なJavaScriptソリューションと関連技術の幅広い採用と継続的な開発を推進することです。 JSFはまた、JavaScript開発コミュニティ内でのコラボレーションを促進し、長期的な持続可能性を提供する品質と多様な貢献基盤を維持するよう努めます。
参考資料 https://js.foundation/about/
Node-Redは、IBM Bluemix だけではなく、Linux, Windows, Raspberry Pi などの環境で動作させることが可能です。
https://nodered.org/docs/
Express.js とは
全てを手組みすると大変なので、Node.jsで開発された フレームワークを利用します。Express.jsフレームワークも、たいへん普及しているもので、Google検索で沢山の技術情報を入手することができます。
Express.js は、Node.jsで開発された Web アプリケーション・フレームワークです。 Express.jsは、Node.js Foundation のプロジェクトで、Linux Foundation の傘下の団体です。
参考資料 http://expressjs.com/
比較評価用のアプリケーションの設計
今回のスタディ用に開発するアプリケーションは、次の図の様に、複数のブラウザでメッセージを共有するチャット・ルームのアプリケーションです。 一つのブラウザからメッセージをインプットすると、他にアクセスするブラウザに一斉同報で同じメッセージを配信します。実行環境として、Node-REDで開発とExpress.jsで開発する二つのケースを試してみます。メッセージログの書込み先は、Bluemix Cloudant にします。 この部分は、MongoDB, Riak, CouchDBなど、RESTでアクセスできるKVS型データベースに置き換えても良いと思います。
もう少し具体的なChatルームアプリケーションの動作を設計しておきます。 ブラウザ上のJavaScriptのプログラムとサーバー側の実行環境上のコードとの連携を次の図に表します。 図の前半が、ブラウザからURLアドレスをアクセスして、HTMLとJavaScriptが読まれ、JavaScriptが起動して、その中のWebSocketがサーバーの実行環境に接続するまでのプロセスです。そして、図の後半が、JavaScriptのプログラム・コードから、WebSocketのサーバーに接続して、ブラウザのフォームに書き込まれたメッセージを送信します。また、他からのメッセージを受け取る部分の動作です。
Node-Redで開発する場合
Node-REDで開発する場合、前半のHTML+JavaScriptを送出する部分と、WebSocketの通信を処理する部分を 別々のフローで表します。次のNode-REDのGUI画面には、二つのフローがあり、上のフローが、HTML+JavaScriptの送信、下のフローが、WebSocketのメッセージを受け取って、全クライアントへメッセージを配信すると共に、Cloudantにメッセージを保存する処理です。
この処理ノードの一つ一つを詳しく見てみましょう。
上記左先頭の[Get]/chatの部分は、INPUTの をインスタンス化したもので、HTTPプロトコルで、GET /chat を受信した時に働きます。 このリクエストに応答するために、Function のインスタンス化された処理ノードを連結することで、HTTP要求に対する応答のHTML設定できます。 このテンプレートの中に、HTMLとJavaScript記述しておきます。以下に実際のTemplateの処理ノードのプロパティのスクリーンコピーの様に、Templateに書き込んでおきます。
Node-REDでブラウザのクライアント・サイドの処理を書くことができませんから HTMLの中に埋め込んだ JavaScript でチャットの処理が進むことになります。 HTMLファイルの全体を以下に添付して、簡単に解説します。
1 <html>
2 <head>
3 <title>WebSocket Chat</title>
4 <style>
5 * { margin: 0; padding: 0; box-sizing: border-box; }
6 body { font: 13px Helvetica, Arial; }
7 form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
8 form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
9 form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
10 #messages { list-style-type: none; margin: 0; padding: 0; }
11 #messages li { padding: 5px 10px; }
12 #messages li:nth-child(odd) { background: #eee; }
13 </style>
14 </head>
15
16 <body>
17 <ul id="messages"></ul>
18
19 <form name="form1" action="">
20 <input type="text" id="msg" autocomplete="off">
21 <button type="button" onclick="sendMsg();">Send</button>
22 </form>
23 <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
24
25 <script>
26 var wsUri = "wss://{{req.headers.host}}/ws/chat";
27 var ws = new WebSocket(wsUri);
28
29 function sendMsg() {
30 var text = document.getElementById('msg').value;
31 console.log("msg = " + text);
32 var msg = {
33 payload: text,
34 ts: (new Date()).getTime()
35 };
36
37 ws.send(JSON.stringify(msg));
38 document.getElementById('msg').value = "";
39 return;
40 }
41
42 ws.onopen = function(ev) {
43 console.log('[Connected]');
44 };
45
46 ws.onclose = function(ev) {
47 console.log('[Disconnected]');
48 }
49
50 ws.onmessage = function(ev) {
51 var msg = JSON.parse(ev.data);
52 console.log("ON Message", msg);
53 $('#messages').append($('<li>').text(msg.payload));
54 }
55
56 $(function(){
57 $("input"). keydown(function(e) {
58 if ((e.which && e.which === 13) || (e.keyCode && e.keyCode === 13)) {
59 sendMsg();
60 return false;
61 } else {
62 return true;
63 }
64 });
65 });
66 </script>
67 </body>
68 </html>
19行〜21行 チャットメッセージのインプット・フォームです。 送信ボタンを クリックすると JavaScript の sendMsg()をコールして、メッセージをNode-REDサーバーへ送信します。
26行〜27行 wsUrlのアドレスに、WebSocketを接続します。下記の{{req.headers.host}}部分は、jQueryの変数ではなく、Node-RED固有の変数置換ですから、Express.jsで利用する場合は、書き換えが必要です。
26 var wsUri = "wss://{{req.headers.host}}/ws/chat";
27 var ws = new WebSocket(wsUri);
29行〜40行 HTMLフォームに入力されたメッセージを JSON オブジェクトに格納して、文字列化して、Node-REDサーバーの送信します。 この中で、Node-REDに固有ルールとして、以下のJSON形式で payload に 内容をセットして送信することで、次の処理ノードが受け取れる様になります。以下のpayload にセットされる text は、チャットのメッセージです。
32 var msg = {
33 payload: text,
34 ts: (new Date()).getTime()
35 };
以下で、JSON形式を文字列に変換して、Node-REDの2番目のフローのWebSocketのインスタンス 処理ノードへ、メッセージを渡して行きます。
37 ws.send(JSON.stringify(msg));
次の50行から54行は、Node-REDの処理ノード から送信されるメッセージを受け取り、ブラウザ画面に書き込む処理です。 Node-REDから JSON形式の文字列で送られてきますから、JSONオブジェクトに変換して、テキストを取り出しブラウザに書き込みます。
50 ws.onmessage = function(ev) {
51 var msg = JSON.parse(ev.data);
52 console.log("ON Message", msg);
53 $('#messages').append($('<li>').text(msg.payload));
54 }
再び、Node-REDの後半のフローに戻ってきます。 以下は、WebSocketのメッセージを受け取り、メッセージを Cloudant の chatデータベースへ書き込むと共に、全てのクライアントに応答を返す流れになります。
ここで、[ws]/ws/chat のエンドポイントで受け取るメッセージは、前述の様にJSONオブジェクトになっていますから、Cloudantの処理ノードに繋ぐだけで、書き込みが可能です。さらに下記の様に、Function ノードの中では、JSONオブジェクトから、セッションIDを削除します。 これによって、送信元のブラウザだけでなく接続中の全ブラウザに返信します。
このNode-REDとHTMLテンプレートで実行されるチャットは、以下の様なスクリーンイメージになります。
Express.js と Javascript でコーディングする場合
Express.jsの場合、サーバー側のコードから先に見て行きます。 以下が Node-REDと同じ処理を Express.js で書いた場合の処理です。 5行〜18行までは、Cludantと連携するための認証と書込み共通処理になります。 20行〜23行が、ブラウザからアクセスされた場合に、HTMLファイルを応答する処理です。 HTMLファイルの相違点は後述します。 次に、25行〜36行は、WebSocketで送信されたメッセージをCloudant に保存して、全クライアントへ同報送信する処理です。
1 var express = require('express');
2 var app = express();
3 var expressWs = require('express-ws')(app);
4
5 // Cloudantへ認証と接続
6 var cred = require('./cloudant_credentials.json');
7 var Cloudant = require('cloudant')
8 var cloudant = Cloudant(cred.url);
9
10 // Cloudantへの書き込み処理
11 function save( msg, callback) {
12 var cdb = cloudant.db.use('chat');
13 cdb.insert(msg, function(err, result){
14 if (err) {
15 throw err;
16 }
17 });
18 }
19
20 // express HTTP GET処理
21 app.get('/', function(req, res){
22 res.sendFile(__dirname + '/index.html');
23 });
24
25 // express-ws ウェブソケット処理
26 app.ws('/ws/chat', function(ws, req) {
27 ws.on('message', function(msg) {
28 // データベースへ書き込み
29 save(JSON.parse(msg),function() {});
30 // 全接続クライアントに送信
31 var aWss = expressWs.getWss();
32 aWss.clients.forEach(function(client) {
33 client.send(msg);
34 });
35 });
36 });
37
38 app.listen(3000);
ブラウザのHTMLファイルの変更点は、以下です。 27行目で、Bluemix の Node-RED からアクセスする場合は、HTTPSが使われているので、wss:// でしたが、 暗号化なしの場合、ws:// となります。 次に、jQuery の仕様に従って、ホスト名の変数が、この 形式 ${location.host} に変更します。
25 <script src="http://code.jquery.com/jquery-1.11.1.js"></script>
26 <script>
27 var wsUri = `ws://${location.host}/ws/chat`;
28 var ws = new WebSocket(wsUri);
これ以外は、Node-RED と Express.js で同じです。
比較と考察
Node-REDのGUIを利用したプログラミングと テキスト・エディタで Node.js のコード書いていく場合の作業内容を比較して頂けたのではないかと思います。 主観的な定性的な評価なので、感想といった処ですが、Node-REDで開発する場合とExpress.js でコーディングする場合を比較すると次の表の様になりました。
比較項目 | Node-RED | Express.js |
---|---|---|
生産性 | ◎ | ○ |
保守性 | ○ | ○ |
機能拡張性 | △ | ○ |
デバッグ容易性 | △ | ○ |
複雑性 | △ | ○ |
生産性は、目的を達成と所用時間を生産性として考慮した場合の評価です。 Node-REDでは、処理モジュールがあれば、マニュアル等を参照せずに、直感的に処理を完成させることができます。このため◎としました。 一方、Express.js は、Node.jsマニュアル、JavaScript言語マニュアル, そして、とても沢山存在するバージョン、npmパッケージのドキュメントなど、沢山の資料を参照しなければ、作ることができません。 しかし、プログラム言語一般のことですから、○としました。
保守性は、不具合要因を修正したり,性能や使い易さといった特性を改善したり、当初に想定していなかった機能の追加や変更を少ない手間で対応できることです。 Node-REDでは、複雑さが増すと、細部をモジュールを展開してみなければ判別できず、可読性が良い様であまり良くない。このため◎とはならず○とした。 Express.jsでは、コードを見通し良く読めるが、特に他のプログラミング言語と比較しても大きな差がないため、○とした。
機能拡張性は、Node-REDとExpress.jsと、それぞれに新しい機能を追加する容易さを表す。 Node-REDの生産性は、処理モジュールの豊富さで決まりますが、この処理モジュールを開発することは、一般的でなく、スキルを必要とするので、△としました。 Express.jsは、Node.jsとしてnpm install を実行することで容易に追加できる点と、モジュールを書くことも、一般のプログラムを開発することを差がありません。 このため○としました。
デバッグ容易性は、デバッグ出力の容易さ、スタックトレースの取得の容易さ、ステップトレース実行の容易さなどを評価基準としました。Node-REDは、GUIにデバック出力のウィンドが装備されており、簡単にデバック出力を取得できます。しかし、デバッグモードによる実行、ステップトレース、例外発生時のスタックトレースなどの機能がありません。このため△としました。
複雑性は、構造的な複雑性、意味的な複雑性を評価対象として考えた。 言い換えると、目的を達成するために単純に解りやすく書けるかという事です。Node-REDは、ある単位の処理モジュールを線で結んで、フローを作るツールであるが、この線の意味が、処理モジュールによって暗黙の変化が起きる。 HTTP GETのモジュールとテンプレートのモジュール間の線では、単純な処理の順番を表しているが、INJECT処理モジュールとDEBUG処理モジュールでは、データの流れとなるが、この二つの違いは、GUI画面からだけでは判別できない。 このためNode-REDの評価を△とした。
まとめ
Node-REDは簡単に使って、手っ取り早く結果を得るためのツールとして素晴らしいツールではないかと思う。しかし、複雑な処理をNode-REDで記述すると、途端に良くない部分が顔を出すのではないかと考えられる。利用者は適材適所を考慮して使うのが良さそうである。