Skip to content

Node Development Guideline

Kazuhito Yokoi edited this page Nov 15, 2018 · 5 revisions

はじめに

ノード開発ガイドラインの目的

ノード開発ガイドラインの目的は、Node-REDのユーザーが、独自のサービスをNode-REDで利用するために、サービスを操作するためのノードを開発したり、 独自の汎用的なノードを開発する際の指針や注意事項を解説することである。 Node-REDのノード開発や、ノードとサービスの連携・I/Fの推奨等を提示する。 また、TipsやNode-REDの環境を踏まえた制約事項なども、適宜取り上げる。

ノード開発ガイドラインの位置づけは、Node-REDでのソリューション開発の中で、より高度な利用をする場合に用いられるものとしており、 対象読者は、Node-REDのシステム概要やNode-REDの基本的な利用方法を習熟したエンジニアとしている。

ガイドライン 概要
フロー開発ガイドライン Node-REDを活用してNode-REDのフローを開発する際のガイドライン
ノード開発ガイドライン Node-REDに標準で用意されたノードのみでは実現できない機能を開発するための、ノード開発におけるガイドライン

ノード開発の目的と本ガイドラインの対応

ノードを開発することで、任意の処理を再利用して用いやすくなる。 functionノードを用いても、任意の処理を記述できるが、独自にノードを開発することで、フロー開発者がノードの設定を付けてノードを利用することができるため、functionノードよりも再利用性・汎用性が高い。

独自ノードの開発は、主に以下の要求がある時に行う。

  • 自身のサービス開発で有用で汎用的な処理ロジックを、ノードとして用意し繰り返し使用したい
  • 自身が管理するサービスを、Node-REDから利用できるようにしたい

汎用的な処理ロジックの再利用・共有

1点目では、標準的なNode-REDのノード開発を行うだけで良い。 その際に気を付けるべき事項は、コーディング、スタイル、品質向上の章を参照されたい。

Node-REDからの独自サービスの利用

2点目では、1点目でのNode-REDのノード開発に加え、Node-REDにおけるサービスとの連携方法を理解する必要がある。

また、サービスは、HTTPのエンドポイントを持つ必要があり、独自ノードはサービスの操作をHTTPリクエストによって行う。

サービスを操作する独自ノードは、以下の2通りの開発方法がある。

  1. Node generatorを利用した半自動開発
  2. 独自ノードフレームワークを用いた手動開発

1点目は、Node generatorを利用した方法であり、簡潔に作成することができる。 独自ノードの開発者は、Swaggerで作成したHTTP APIの仕様を用意し、Node generatorに入力として与えることで、ノードが生成できる。 これらのサービスの前提や、独自ノードとサービスの連携方法、Node generatorに関する詳細は、サービス開発の章を参考にされたい。

2点目の独自ノードフレームワークとは、独自ノードを開発するための開発フレームワークである。 独自ノードフレームワークは、Node generator内でも利用されている。

基本的には、1点目の方が簡潔に開発できるため推奨方法は1点目である。 また、独自サービスをサービス化することを見据えたとして、Node generatorで作っておけばAPIゲートウェイ経由にも対応できるので良い。 ただし、何らかの理由によりSwaggerやNode generatorを利用しない場合は、独自ノードフレームワークを用いる開発を取ることもできる。

対象範囲

ノード開発に関係するNode-REDの構成

独自ノードを開発するユーザーは、まずはローカル環境でノードを開発する。 開発後は、ノードのコード類をGitHubやnpmリポジトリにアップロードし、Node-REDにロードする。

また、Node-REDが提供していない独自のサービスをユーザーが自ら自身の環境に用意し、Node-REDからアクセスできるようにすることが可能である。 この独自に用意するサービスを独自サービスと呼ぶ。 独自サービスがランタイムからの処理リクエストを受けるためのプロトコルには、任意のものを用いてよい。 ただし、Node-REDのサービスはHTTPで処理リクエストを受け付ける仕様であるため、将来的にNode-REDのサービスとする計画がある場合は、HTTPを意識してエンドポイントの設計をしておくと良い。

なお、独自サービスの用意に関して、本ガイドの以降の章では、独自サービスをサービスにすることを見据えることを前提に説明をする。

フロー開発者は、独自ノードを用いたフローの開発後、フローをランタイムにデプロイし実行する。 ランタイムで実行される独自ノードは、独自のサービスに対応するノードである場合は、ノード内で指定されたHTTPエンドポイントにリクエストを送信する。

Node-REDであらかじめ用意されたサービスを利用する場合は、サービスを操作するためのノード、独自ノードをフロー内に追加し、 実行時にはAPIゲートウェイを経由してHTTPリクエストがサービスに送信される。

Request/responseの内容は、独自サービスの動作を制御する制御信号(APIリクエスト・レスポンスなど)と、処理対象のデータ自体の両方を含む。 ただし、膨大なデータを扱う場合は、他のDBにデータを入れ、そのデータへの参照情報のみを受け渡すなどの工夫をするべきである。

ガイドラインのカバーする内容

独自のサービスとNode-REDを連携する場合は、サービスに求める要件は提示するものの、サービス自体の開発・テストに関しては、本ガイドの対象ではない。 外部サービスの操作も本ガイドの対象ではない。

サービス間でのデータ共有方法は、独自ノード間のデータ連携を参照されたい。

対象読者

ノード開発ガイドラインの対象読者は、以下の通りである。

  • Node-REDユーザーのうち、独自ノード開発が必要なソリューションエンジニア

    • (例) JavaScriptライブラリなどを利用した複雑な処理を必要とする、大規模なフローを開発するエンジニア
  • サービス開発者

    • (例) サービスを展開し、ソリューション化の機会を増やしたい開発者
  • JavaScript (Node.js) 開発の基礎知識

  • Node-REDでのフロー開発経験

  • Node-REDに関する基礎的な知識 (Node-REDの利用方法、システム概要等)

前提環境

Node-REDを利用する際のブラウザは、Google ChromeまたはFirefoxを推奨する。

用語の定義

このマニュアルで使用している略語・用語の一覧を次に示す。

略語・用語 意味
独自ノード 汎用的に有用な処理ロジックを行うノード (開発方法は本ガイドの対象範囲内)
独自ノード Node-REDから、サービス(独自サービスも含む)を操作するためのノード (開発方法は本ガイドの対象範囲内)
サービス Node-REDから、処理命令を受けられるインターフェースを備えたサービス
APIゲートウェイ Node-REDから、Node-RED外のシステムを操作する際にAPIリクエストを中継するコンポーネント
独自サービス Node-REDで提供されておらず、開発者が独自に用意するサービス

設計

独自ノードの設計指針

以下では、独自ノードの設計指針について述べる。 なお、本章ではノードに関する用語を次のように規定する。

  • メッセージ: ノード間でやり取りするJavaScriptオブジェクト形式のデータ
  • ワイヤ: ノードの入出力を結ぶ結線
  • ノード名: ノードの名称。ノード上に表示される。
  • ノードアイコン: ノード左/右端に表示されるノードのアイコン
  • 入力端子(ポート): ノード左端に表示され、メッセージ入力ワイヤを結合する端子
  • 出力端子(ポート): ノード右端に表示され、メッセージ出力ワイヤを結合する端子
  • ステータス: ノード下部に表示され、ノードの実行状態などを表示する

関連ノード群の設計

一連の関連した処理がある場合、それをフロー内の部品として利用可能な形に切り分けて関連ノードとしてまとめる。関連ノード群は、フロー定義を行うユーザから見た使いやすさの観点を優先して定義する。 この際、類似機能は一つのノードにまとめ、設定で切り替えられるようにするなどノード数をむやみに増やさないことも重要である。 既存のREST APIなどが存在する場合に、それを直接関連ノードに分解してノード化することは一般にノード数の増加や、フロー中で使いにくいノード定義となるため留意されたい。

ノード定義

エディタ上に表示されるノードの体裁は、プログラムの視認性を向上する上で重要である。ノードの体裁に関し、以下を規定する。

  • ノード名
  • ノードの所属カテゴリ
  • アイコン
  • ノードの色

推奨するスタイルについては、スタイルを参照のこと。

ノード設定

ノードの設定インターフェイス(設定パネル)を定義する。ノードの設定は複雑にしすぎないことが望ましい。デフォルト設定は一般的に使われる設定とすること。

実行時にノードの動作を動的に変化させたいケースでは、設定用の入力メッセージを用いることを検討するとよい。

ノード機能の設計

入出力

ノードの入力メッセージと出力メッセージの関係を定義する。

Node-REDのノードは0もしくは1つの入力端子、複数(0~N)の出力端子をワイヤによって接続し、メッセージ処理を行うため、対象とする問題をそのようにマッピングする。このとき、入出力の関係をノード名やカテゴリ名から類推しやすいこと、ノード設定や処理履歴にかかわらず入出力の関係の一貫性が保たれることが望ましい。

ノード設計においては、メッセージの設計も重要である。入出力メッセージの設計については、フロー開発ガイドラインを参照のこと。メッセージに関しては、次のような定義を推奨する。

  • payload: 主たる処理対象となるデータ
  • topic: メッセージ/処理の種別
  • その他: テンポラリ/関連データ

実行時インターフェイス

長時間もしくは定期的に実行を行うノード、もしくは、外部との接続など状態を持つノードに関しては、実行時にノードの実行状態が判別できるようステータス表示の利用を検討する。

具体的には、以下を推奨する。

  • 長時間実行ノード
    • 実行中の状態を**緑、準備中を黄、エラーを赤、実行不能状態をグレー**で表示し、エラーや内部状態を表す簡単なテキストを付加する。
  • 外部接続ノード
    • 接続中の状態を**緑、準備中を黄、エラーを穴あき状態の赤、実行不能状態を穴開き状態のグレー**で表示し、エラーや内部状態を表す簡単なテキストを付加する。

機能

ノードで実現すべき処理を規定する。 機能設計に際しては、メッセージ処理の部品として用いることを踏まえ、入力メッセージに対する処理を規定する。

エラー処理およびログ出力

ノードが何らかのエラーを生じる可能性がある場合、ノード内でエラーを検出しNode-REDの定義するエラー処理を用いてエラー処理およびログ出力を行う。詳細については、JavaScriptコードの作成を参照のこと。

非機能要件の設計

性能

Node-REDの実行基盤であるNode.jsはイベント処理を中核とした単一スレッドによるインタープリタ実行を採用している。Node-RED自身もメッセージ処理による実行を採用しており、他の言語による同等システムと比較すると性能面で劣る可能性が高い。そのため、高い性能が必要とされる処理をサービスによって分離するなどシステム構成を考えるとよい。

また、単一スレッドによる実行のため、あるノードの処理が長時間ループ処理を行うとNode-REDシステム自体が停止する可能性がある。ノード開発を行う上では、このような点にも留意する必要がある。

リソース

CPU、メモリ、外部リソースの枯渇を招かないように留意する。 CPUリソースに関しては、上述のような点を考慮すること。 メモリリソースに関しては、JavaScriptはガーベージコレクション(GC)による自動メモリ管理を採用しているが、大量/大規模のオブジェクトの作成、未使用オブジェクトの長時間保持はメモリの枯渇やGC頻発による応答性の低下を招く可能性がある。これらが生じないよう、ノード処理作成時に留意する。 外部リソースに関しては、一般的なアプリケーションと同様、ファイルなど外部リソースを作成する場合に不要リソースの削除が漏れないよう留意する。

セキュリティ

ノード中に個人情報(パスワードなど)を埋め込まないようにする。 また、外部との通信を行うようなノードに関しては、接続認証を適切に行うなどセキュリティに留意する。

信頼性

エラー処理を適切に行い、障害発生時にノードの利用フローで適切な対処ができるよう考慮する。また、障害原因の追求のため、適切なエラーログを出力する。

独自サービスの設計指針

独自ノードの設計

独自ノードが開発が可能な独自サービスの要件

[1.必須要件]

  1. 案件ごとにSIが必要な処理や設定は、サービスのソフトウェア内には組み込まず、Node-RED側から処理に関するコードや設定(環境変数など)を投入するか、外部のストレージに設置し、それらをサービスから呼び出す形式にすること
  2. サービスの一つの処理に対して、一つのAPIエンドポイントが提供されていること
  3. (1-2)に記載の"一つの処理"の規模は、ユーザーが一つの処理としてサービスに求める処理の規模と一致していること
  4. 個々の処理の内容は、同じ入力値に対して、常に同じ出力が出るものであること(冪等性のある処理であること)
  5. (1-4)に記載の"入力値"は、JSONフォーマットでNode-REDから渡されるため、JSONフォーマットの入力値を受け付けることができること

[2.オプション要件]

  1. 基本的には、個々の処理の間で、処理順序の前提を置かないこと(ユーザーに対し、APIで呼び出す処理の順序に関する理解と、適切な順序での処理の呼び出しを求めないこと)
  2. (2-1)に対し、やむを得ず、個々の処理の間に順序がある場合は、順序のある複数の処理を一つの処理としてまとめ、一つのAPIで提供すること

独自ノードのクラス定義

独自ノードは、Node-REDとは別環境で動作するサービスや独自サービスと連携するノードである。 ユーザーはNode-REDの画面上でこの独自ノードを操作することにより、対応するサービスを操作できる。

独自ノードはユーザに対して下記の2つの機能を提供する。

  • ドラッグ&ドロップによるフロー定義
    • 各サービス・独自サービスに対するパイプライン呼び出しの順序をGUI上で定義することができる
    • さらに各サービスの製品ロゴなどをアイコン表示することにより、フローの処理内容の理解を支援する
  • 各サービス・独自サービスのパラメータ設定
    • サービス・独自サービスの実行に必要な入力パラメータを指定することができる

また、独自ノードでユーザーが設定できるパラメータは主に下記の5つとする。

  • Location: サービスのインスタンスにアクセスするためのURI(IPアドレスやホスト名など)を入力する
  • Function: サービスがREST APIで公開する機能を選択する
  • 入力データのアクセス先: サービスに入力するデータのアクセス先URI(ファイルパスなど)を入力する
  • 出力データの格納先: サービスが出力するデータのアクセス先URI(ファイルパスなど)を入力する
  • 入力パラメータ: サービスの実行時に必要な設定情報(ログインアカウントなど)を入力する

なお、上記5つの設定パラメータは必須ではなく、入力パラメータが必要でないサービスやの設定ファイルで指定するなど別の入力手段があるものは、実装する必要はない。

APIの設計

API設計における独自サービスの要件

[1.必須要件]

  1. サービスによる処理とサービスの管理は、Node-REDからのREST API (Application Programming Interface)によるリクエストによって実行可能であること (※1)

[2.オブション要件]

  1. サービスが用意したAPIは、API管理システムであるSwaggerで規定したAPIの定義ファイルで表現できること(※2)

※1: Node-REDでは、コンソールアプリなどの、REST APIを受け付ける機能がないサービスでもNode-REDと連携できるように、サービス側にREST APIサーバーを構築する方法を用意している。 ただし、その場合でもサービスのソフトウェア設計が、(2-3)(2-4)を実現できる構造である必要がある。

※2: Swaggerのドキュメントは、こちらを参照。

REST API設計の基本方針

Node-REDでのREST API定義は下記の指針を参考にする。

REST APIの粒度

独自ノードはNode-REDのパレットに一覧表示されるため、複数機能を持つサービスがそれぞれの機能ごとに独自ノードを定義するなど細粒度で定義するとノード数が膨大になり必要な機能の発見が困難になる。 一方で、「分析ノード」のように汎用的な粗粒度のノードを1つにしてFunctionで切り替えるように定義すると、フローを見てもユーザは何を実行しているのか理解できなくなってしまう。 そのため、独自ノードの粒度設計が重要となる。

そこで、下記の基本方針で独自ノードの粒度設計を行うこととしている。

  • サービスや独自サービスは、製品レベルのまとまり、もしくはサービス・独自サービスを表し、独自ノードはこのEngine単位で生成することとする
  • Functionは、実装したアルゴリズムなど、それぞれの製品が持つ機能、REST APIはこのFunction単位で生成することとする

また、独自ノードのクラス定義については下記の方針で設計する。

  • 独自ノード Class: 独自ノードとしてNode-RED(のパレット)に登録されているノード
  • 独自ノード Instance: Node-REDのフロー上に配置され、設定パラメータが入力されたノード
    • Locationが指定されることによりサービスのインスタンスと対応付けられ、Functionが指定されることによりサービスのREST APIと対応付けられる
    • なお、サービス側にFunctionを共有する機能がある場合でも、Node-RED上では別のインスタンスとして管理する

独自ノード間のデータ連携

前提の考え方

データの扱い方に関して、独自サービスは以下の点に気をつけた設計にするべきである。

  1. サービスの処理における入出力値が大規模なデータである場合、値渡しは行わず、データのロケーション情報のみを受け渡し、データの実体はファイルやデータベースなどで受け渡せるようにすること
  2. データの実体は、サービスが稼動するサーバーと物理的に異なる箇所(DB、ネットワークストレージなど)に格納されることを前提にすること

独自ノード間でのデータ連携方式

独自ノード間でのデータ連携については、大きく下記の3つの方式がある。

  • コントロールフロー: Node-REDのフローで記述する,独自ノードを定義するJavaScriptの引数と返り値を対応付けることによりNode-RED上でデータの送受信を実現する。
  • メッセージ送信パス: データをHTTPリクエスト(REST API)として、ネットワークを介した送受信する。Node-RED上では別フローとなるため、URIで暗黙的にデータのやり取り関係を規定することとなる。
  • データ送信パス: URIで規定する,入出力データの受け渡し

以下、それぞれのデータ連携方式について、詳細に説明する。

コントロールフロー

Node-REDのコントロールフローとしては下記の2つのバリエーションがある

  • パイプライン連携: Functionの返り値をそのまま引数として受け取るデータの受け渡し
  • アクセスパス連携: アクセスパス(URIやファイルパス)情報を引数として受け取るデータの受け渡し

本方式によるデータ連携はNode-REDのプロセス上で処理が完結するため、性能面やハードウェア障害時の影響を受けにくいなどのメリットはある。 ただし、ノード間で引数が厳密に一致しているなどの制限が厳しくなるため、やり取りするデータをJSONやXMLのような共通フォーマットに限定し、受信側で想定のデータかどうかをエラー判定するなどの処理が必要になる。 そうしないと、Node-REDの、自由にノードを付け替えられる、という疎結合の世界観とは背反してしまうため注意が必要である。

メッセージ送信パス

HTTPリクエストを受け付ける別フローの呼び出しやREST APIを公開している外部サービスとの連携するためのI/Fで下記の2つのバリエーションがある。

  • リクエスト呼出し:連携するデータはHTTPメッセージのボディに記載する
  • データの受け渡し:連携するデータは引数のJSONファイルに記載する
データ送信パス

データ送信パスとしては、基本的にはNode-REDのファイル参照ノードの振る舞いを踏襲する。 Node-RED標準のファイル入力ノードは、ファイルパスを指定することによりNode-RED外部のデータストアからデータを取得する機能を提供している。 独自ノードでもこの振る舞いを踏襲し、ファイルパスやsqlクエリの指定を受付け、外部のデータストアやRDBを介したデータ連携を実現する。

具体的なデータ送信パスの実現方式として、下記の2つのバリエーションがある。

  • パラメータ指定:フロー設計時のノードの設定パラメータで指定
  • メッセージ指定:フロー上の入力データの引数にURLを指定

このデータ送信パスの経路として、Node-RED Flow Editor, Node-RED ランタイム, APIゲートウェイは経由しないため、以下の転送経路は、すでに蓄積されているデータの分析処理などの大量のデータを読み書きする際にNode-RED環境に負荷をかけないための推奨構成である。

外部とのデータのやり取り
  • データ入力
    • 基本形 (運用フェーズ向け)
      • データ保護及び再実行処理の観点から,データストアを介してノード間のデータ授受する①②構成を推奨
      • Node-REDへの負荷低減の観点から,②パス通知パターンを推奨
    • 簡易形 (デモフェーズ,Try&Errorフェーズ向け)
      • システム構成及びフローの簡素化の観点から,データサイズが小さい場合は③直通パターンでもよい。

実行環境の設計

[1.必須要件]

  1. サービスはソフトウェアとして提供されていること
  2. サービスは、WindowsまたはLinux OS上で稼動すること
  3. サービスを実行する環境の構築が、スクリプトまたはプログラムで自動化できること
  4. サービスを実行する環境の構築において、環境ごとに他社ソフトウェアのライセンス購入を必要としないこと

[2.起動・終了に関する必須要件]

  1. サービスの起動は、(1-3)に記載した環境構築時か、障害からの復旧時にNODE-RED側からの命令により自動的に行われるものであること
  2. 起動と終了時に、案件ごとに異なる処理を必要としないこと
  3. サービスの終了時に、そのサービスが稼動していたサーバーのメモリまたはHDDにデータが残されていた場合、それらが全て消去されても問題ないようにすること

運用の設計

[1.エラー処理に関する必須要件]

  1. サービスが実行時のログを出力し続け、Node-REDからの要求に応じて送信できること(※3)
  2. サービスの各処理においてエラーが発生した場合は、サービスがNode-REDに対し、エラーが発生した旨を通知すること(※4)

[2.エラー処理に関するオプション要件]

  1. エラー発生後に、特別な回復処理が必要な場合は、回復のための処理とその処理を呼び出すREST APIを用意すること (※5)
  2. エラー発生後の回復処理において、Node-RED側の情報が必要な場合は、(5-2)に記載したNode-REDへの通知の中に、回復のために必要な情報を記載すること
  3. 回復のための処理が行われる前に、サービスの処理の要求が届いた場合は、エラー発生中であることを通知すること
  4. (2-3)に記載の通知の中に、サービスの回復処理に関するステータスを含めること(※6)

※3: 現状の要件では、ログの内容や保存期間について、具体的に定義しないが、必要に応じて後から変更できる必要がある。 ※4: Node-RED側へエラーを通知するためには、サービスがREST APIのレスポンスメッセージの中にエラー情報を載せる。なお、レスポンスメッセージに含めるデータのフォーマットは、リクエストと同様に、JSONフォーマットである。 ※5: 回復に必要な情報は、回復のための処理を呼び出すREST APIでJSONフォーマットのデータとして受け付ける。 ※6: ステータスでは、回復処理が行われる前か、回復処理を行っている最中か、が分かるようにする。

コーディング

Node-RED公式ドキュメント

Node-REDにおいて新規ノードを作成するための方法については、Node-RED公式サイトのDocumentation/Creating Nodesに説明されている。また、日本語による解説はNode-RED User Group JapanのNodeの作成にある。ただし、日本語版の情報は古い可能性があることに注意されたい。

開発の流れ

一般的なノード開発の流れは次のようになる。

  1. インターフェイス設計
  2. コード作成
  3. テスト
  4. 多言語化対応
  5. パッケージング

以下では、それぞれの概要について述べる。詳細については、公式ドキュメントを参照のこと。

なお、本章ではノードに関する用語を次のように規定する。

  • メッセージ: ノード間でやり取りするJavaScriptオブジェクト形式のデータ
  • ワイヤ: ノードの入出力を結ぶ結線
  • ノード名: ノードの名称。ノード上に表示される。
  • ノードアイコン: ノード左/右端に表示されるノードのアイコン
  • 入力端子(ポート): ノード左端に表示され、メッセージ入力ワイヤを結合する端子
  • 出力端子(ポート): ノード右端に表示され、メッセージ出力ワイヤを結合する端子
  • ステータス: ノード下部に表示され、ノードの実行状態などを表示する

設計については、独自ノードの設計指針を参照のこと。

コード作成

Node-REDのノードは、JavaScriptのパッケージ配布に利用されるnpm形式のモジュールとして定義する。なお、開発に利用するJavaScriptバージョンは規定されていないが、Node-RED本体はNode.jsバージョン6以上で動作することを想定しているため、バージョン6での動作を保証することを推奨する。

パッケージング

その実装は、エディタインターフェイスを定義するHTMLファイル、バックエンドでのメッセージ処理を定義するJavaScriptファイル、および、関連ファイルから構成する。

ファイル 概要
package.json パッケージ定義
README.md ノードの説明(Markdown形式)
LICENSE ライセンス情報
<ディレクトリ>/<ファイル名>.html HTMLコード
<ディレクトリ>/<ファイル名>.js JavaScriptコード
  • package.json

    パッケージの定義情報をJSON形式で記述する。以下のプロパティを定義する。必須の項目を以下に示す。詳細はここを参照。

名前 形式 概要 例
name 文字列 モジュール名(node-red-contrib-を前置する) "node-red-contrib-samplenode"
version 文字列 バージョン番号 "0.0.1"
descriptions 文字列 モジュールの説明 "A sample node for node-red"
dependencies オブジェクト このモジュールが利用する他モジュールの定義(モジュール名:バージョン定義) {"xyz": "^0.1.2"}
keywords 文字列の配列 検索用キーワード(フローライブラリに登録する場合は"node-red"を含めること) [ "node-red" ]
node-red オブジェクト 定義するノードの情報。"nodes"の値にノード名をキー、対応するJavaScriptファイルの相対パスを値とするオブジェクトを指定する。 { "nodes": { "sample": "sample/sample.js" }}
  • README.md

    ノードの説明をMarkdown形式で記述する。

  • LICENCE

    ライセンス情報をテキスト形式で記述する。

HTMLコードの作成

ノードのNode-REDエディタ上のインターフェイスを定義する。 HTML定義は、ノード情報の登録、ノード設定インターフェイス、(英語版)ヘルプ情報からなる。 一つのファイル内に複数のノード定義を含めてもよい。HTMLコードはエディタを実行するブラウザ上で実行されることに注意すること。

ノード情報の登録

<script>タグ内にノード情報を登録するための関数(RED.nodes.registerType)呼び出しを記述する。

<script type="text/javascript">
    RED.nodes.registerType(<ノード型>, <ノード定義>)
</script>

「ノード型」はノードの内部的な種別を表す文字列である(例: "samplenode")。後述するJavaScriptコードでも同じ「ノード型」を用いる。 異なるノードが同じ「ノード型」を持ってはならない。

「<ノード定義>」はノードの詳細を指定するオブジェクトである。以下のプロパティを指定可能。

名前 型 必須 概要
category 文字列 ○ ノードをパレット内に表示するカテゴリ名
defalts オブジェクト ○ ノードの設定可能なプロパティ
credentials オブジェクト 認証情報プロパティ
inputs 整数 ○ ノードの入力メッセージ数(0もしくは1)
outputs 整数 ○ ノードの出力メッセージ数(0以上)
color 文字列 ○ ノードの背景色(RRGGBB形式で指定。よく利用する色の定義はここを参照)
paletteLabel 文字列もしくは関数 パレットに表示する文字列(関数を指定した場合はその評価値)
label 文字列もしくは関数 ○ ワークスペースに表示する文字列(関数を指定した場合はその評価値)
labelStyle 文字列もしくは関数 ○ ラベルの表示スタイル(関数を指定した場合はその評価値。よく利用するスタイルの定義はここを参照)
inputLabels 文字列もしくは関数 マウスオーバ時に表示する入力端子の名前(関数を指定した場合はその評価値)
outputLabels 文字列もしくは関数 マウスオーバ時に表示する出力端子の名前(関数を指定した場合はその評価値)
icon 文字列 ○ ノードのアイコン指定(よく利用するアイコンの指定はここを参照)
align 文字列 ○ アイコンとラベルの表示位置
oneditprepare 文字列 設定ダイアログ作成時に呼び出す関数
oneditsave 文字列 編集ダイアログのOKを押した際に呼び出す関数
oneditcancel 文字列 編集ダイアログのキャンセルを押した際に呼び出す関数
oneditdelete 文字列 編集ダイアログの削除を押した際に呼び出す関数
oneditresize 文字列 編集ダイアログをリサイズした際に呼び出す関数
onpaletteadd 文字列 パレットにノードを追加する際に呼び出す関数
onpaletteremove 文字列 パレットからノードを削除した際に呼び出す関数

設定インターフェイス

ノード設定インターフェイスをscriptタグ内にHTMLで定義する。scriptタグのtype指定は"text/x-red"とする。また、data-template-name属性にノード型を指定する。詳細はこちらを参照。

例:

<script type="text/x-red" data-template-name="ノード型">
    HTML定義
</script>

HTML定義中にはJavaScriptコードも記述可能。

HTMLコード上で様々なデータ型のデータ入力を容易にするためのインターフェイスとして、Node-REDではtypedInputと呼ばれるモジュールを提供している。インターフェイスの一貫性を保つため、可能な限りこれを用いることを推奨する。

ヘルプ情報

エディタの情報タブに表示する英語のヘルプテキストをscriptタグ内にHTMLで定義する。scriptタグのtype指定は"text/x-red"とする。また、data-help-name属性にノード型を指定する。詳細はこちらを参照。

例:

<script type="text/x-red" data-help-name="ノード型">
    <p>ノード概要</p>
    <h3>入力</h3>
        <dl class="message-properties">
        <dt>入力プロパティ名
            <span class="property-type">型</span>
            プロパティの説明
        </dt>
    <h3>出力</h3>
        <dl class="message-properties">
        <dt>出力プロパティ名
            <span class="property-type">型</span>
            プロパティの説明
        </dt>
    <h3>詳細</h3>
    <p>ノードの詳細説明</p>
</script>

ノードのヘルプ情報には、以下を記載すること。

  • ノード概要: ノードの概要。最初の<p>タグに記入。パレット内のノードをマウスオーバした際にも表示される。
  • 入力メッセージ形式(存在する場合): 入力メッセージが含むプロパティとその説明
  • 出力メッセージ形式(存在する場合): 出力メッセージが含むプロパティとその説明
  • 詳細: ノードの機能、設定項目、などに関するノードの詳細説明

JavaScriptコードの作成

バックエンド側でのノードの処理をJavaScriptコードで定義する。ノード定義の基本形は次の通り。

function ノード関数(config) {
    RED.nodes.createNode(this, config);
    // ノード固有処理の定義
}

RED.nodes.registerType(ノード型, ノード関数);

メッセージの受信

メッセージの受信はノードに対するinputイベントとして、ノード定義する。

this.on('input', function(msg) {
    // 入力メッセージmsgに対する処理
});

メッセージの送信

send関数によりメッセージを出力する。1つ目の端子に対して出力を行う場合は、メッセージオブジェクトをsendの引数として渡せばよい。

node.send(msg);

複数の端子に出力する場合、要素が各出力端子に対応する配列をsendに渡す。

node.send([ msg1, msg2 ]);

この例では、msg1が1番目の出力端子へ、msg2が2番目の出力端子へ送られる。メッセージがnullの場合にはメッセージの出力は行わない。また、配列を渡すと複数のメッセージを出力できる。

ノードの終了処理

コネクションの切断などノードの終了処理を行う場合は、closeイベントに対する処理としてノード固有処理に記述する。

this.on('close', function() {
    // 終了処理
})

終了処理を非同期に行う必要がある場合、コールバック関数が引数を受け取るようにし、非同期処理が完了した時点でその引数を呼び出すようにする。

this.on('close', function(done) {
    // 非同期な終了処理
    done();
})

その他の詳細はこちらを参照。

エラーハンドリング

ノードは利用者の誤入力やシステム障害などによるエラーに対して適切な処理を行うようにする必要がある。
利用者へ再入力や再実行を促したい場合は、下記ログ出力機能のerror, warn関数を利用すること。
エディタのデバッグタブにメッセージが表示され、利用者がエラーを検知することができる。

またNode-REDでは、利用者がCatchノードを利用することで、自身でフロー内にエラーハンドリング処理を定義する事ができる。 利用者にCatchノードでエラーを補足させたい場合はerror関数を呼び、第二引数にメッセージオブジェクトを必ず渡すこと。
渡さなかった場合は、errorレベルのログ出力・デバッグタブ表示のみとなり、Catchノードで補足する事はできなくなる。

エラーハンドリングでの注意事項
  • 非同期処理内の例外は必ず捕捉する
    非同期処理内で例外が発生した場合、try...catchで捕捉していないとuncaughtExceptionとして処理され Node-REDが停止 してしまう。 ノードで非同期処理を行う場合は、必ず例外を補足できるようにしておくこと。

ログ出力

Node-REDではログ機能を提供しており、ログレベルに対応したerror, warn, log, debug, trace関数でコンソールへのログ出力を行うことができる。 warnおよびerrorはエディタのデバッグタブにも出力される。

this.error("ログメッセージ" [, msg] ); // 前述のCatchノードに捕捉させる場合は、第二引数にメッセージオブジェクトを渡す
this.warn("ログメッセージ");
this.log("ログメッセージ");  // infoレベルで出力する
this.debug("ログメッセージ");
this.trace("ログメッセージ");

ログ機能を利用することでNode-REDで出力されるログを一元管理する事が可能になるため、console.log()や独自にファイル出力を行う事は推奨しない。

ノードの状態(処理状況など)をユーザに表示させたい場合は、ステータス表示の利用を推奨する。

なお、Node-REDではデフォルトでコンソールに出力するロガーを利用している。 管理者が独自のロガーを作成し設定することで、ファイル等に出力先を変更することも可能である。

ログレベル

出力されるログの重要度を指定する事が出来る。 ログレベルにより出力先が異なるため、出力する内容を踏まえ、どのログレベルで出力するか決定する。 なお、Node-REDではデフォルトでinfoレベル以上のみが出力される。

ログレベル 説明 エディタのデバッグタブでの表示 catchノードでの検知
error 致命的とみなされるエラー時に利用。エディタ上でも表示されるため、利用者に対応を促すことができる。catchノードで捕捉する事が可能なため、フロー作成者にエラー時の処理を記述させることができる。 ○ ○
warn 致命的ではないエラー時に利用。エディタ上でも表示されるため、利用者に対応を促すことができる。 ○ ×
info(log) 情報として出力したい時に利用。エディタ上では表示されないため、主にシステム管理者向けの情報となる。 × ×
debug infoより詳細な情報を出力したい時に利用。 × ×
trace debugよりさらに詳細な情報を出力したい時に利用。 × ×

ログフォーマット

一般的にログを出力する際には、ログメッセージの他に出力時の日時等の情報を付与する。
Node-REDでは、メッセージ以外の情報はロガーにて付与しているため、ノードから渡すメッセージ内にそれら情報を付与する必要はない。

本項では、コンソールに出力するデフォルトのロガーのフォーマットについて説明する。

// デフォルトのロガーのフォーマット
日 月 時刻 - [ログレベル] [ノード型:ノードID] ログメッセージ
項目 説明
日 出力した日が数字で表示される
月 出力した月が次の通り英語の省略形で表示される。Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
時刻 出力した時刻が表示。時:分:秒の形式(hh:mm:ss)で表示される。
ログレベル ログレベルが表示される。
ノード型 出力したノードのノード型が表示される。ノード型についてはこちらを参照。
ノードID 出力したノードのノードIDが表示される。IDはNode-REDにて自動的に割り振られる。
ログメッセージ ノードから渡されたメッセージが表示される。

例:2018年1月1日 12時34分56秒 に functionノード(IDはc3a2285.f617ed8)で this.log("エラーです") を実行した時にコンソールに出力されるログは以下の通り。

1 Jan 12:34:56 - [info] [function:c3a2285.f617ed8 ] エラーです

ステータス表示

status関数でエディタのワークスペース上のノードに状態を表示できる。

this.status({fill:"red", shape:"ring", text:"切断"})

fill属性にマーカの色(red,green,yellow,blur,greyのいずれか)、shape属性にマーカの形式(ringもしくはdot)、text属性にメッセージを指定する。詳細はこちらを参照。

ノード属性値

ノード設定ダイアログで指定した値などは、HTML定義のRED.nodes.registerTypeに渡すオブジェクトのdefaultsプロパティに指定することで、ノードの属性値として参照できる。

defaults: {
    名前: {value: 初期値},
    ...
}

HTMLコード内では、node-input-名前というIDでinputタグの入力対象に指定可能。詳細はこちらを参照。

Node generator

Node generatorを利用することで、単純なノードの作成やノードの雛形の作成を行うことができる。

テスト

テストについては、品質向上を参照。

多言語化

ヘルプテキストとメッセージカタログを言語毎に用意することでノードの多言語化を行うことができる。これらのファイルは、package.jsonのnode-redプロパティに指定したノードのJavaScriptファイルの配置ディレクトリを<DIR>とすると、

<DIR>/locales/言語/ノード名.json
<DIR>/locales/言語/ノード名.html

となる。

メッセージカタログはJSON形式でメッセージを定義したものであり、JavaScript中ではRED._("JSONメッセージ参照")、エディタ中では、以下の形式で参照できる。

<span data-i18n="JSONメッセージ参照"><span>
<input type="text" data-i18n="[placeholder]JSONメッセージ参照">
<a href="..." data-i18n="[title]JSONメッセージ参照"></a>

詳細はこちらを参照。

コーディング規則

Node-REDでは以下のコーディング規則を定めている。ノードの開発においてはこれに準じることを推奨する。

  • インデントはスペース4つで行う
  • タブを使用しない
  • if/for/functionの開き括弧は同一行に置き、閉じかっこは1つの行とする

スタイル

Node-RED公式ドキュメント

ノードの体裁については、Node appearanceに説明されている。

命名規則

カテゴリやノードの名前は、他のコミュニティのノードと重複するとそれぞれ識別できなかったり、あるいはカテゴリとノードともに重複する場合はいずれか1つのノードしか表示されなくなるなど問題となるため、ユニークさが求められる。また、長過ぎる名前の場合は視覚性が下がる。本節では、このあたりを考慮した命名規則の指針を説明する。

基本方針

  • カテゴリ名・ノード名と合わせて一意に判別可能な名称にする。
    • アイコンやカラーはアイキャッチとして使う。視認性の向上には有効なので積極的に使うべきだが、知っている人にしか分からない暗黙的な表現なので、アイコンやカラーに頼った名称にしない。
  • 一覧性向上のためノード名はできるだけ短い名称にする

ノード名

ノード名については、以下のいずれかで命名すると良い。先頭に製品名などのPrefixを付けても良い。

  • 動詞のみ(update)
  • 名詞のみ(server status)
  • 動詞 + 名詞(stop job)

また、以下を考慮すること。

  • 単語の頭文字は小文字で始める
  • 単語と単語の間には半角スペースを設ける

カテゴリ分類・カテゴリ名

ノードは、いずれかのカテゴリに属する必要がある。以下の基準で、どのカテゴリに入れるか決めると良い。

  • 汎用性が高く、どのユーザでも広く使えるようなノードについては、Node-REDのデフォルトカテゴリ(input, function等)に格納する。
  • 一方、サービスや製品に紐づくような特定の用途向けのノードの場合は、独自にカテゴリを設けて、製品カットで分類して格納する。

独自にカテゴリを設ける場合、カテゴリ名に製品名を入れることとする。以下は、その例。

  • Hitachi service
  • Pentaho

以下は、Node-REDが定義しているカテゴリ。このカテゴリを指定する場合は、汎用的なノードであることを確認すること。また、「subflows」はサブフロー用のカテゴリであるため、ノードのカテゴリにこれを指定してはいけない。

  • input
  • output
  • function
  • social
  • storage
  • analysis
  • advanced
  • Raspberry Pi(Raspberry Pi上でのみ表示される)

カテゴリ名とノード名の組み合わせ

前述のノード名およびカテゴリ名の命名規則を基に、以下組み合わせのケースを列挙する。この例では、一番右のケースを推奨する。

デザイン

ノードは、基本的にアイコン・色・名前から識別される。アイコンと名前はユーザが後から変えることもできるが、基本的にはそのまま使われるため、これらを決めることはノードの識別に非常に重要な検討項目となる。

ノードのアイコン

アイコンを作成する基準としては、以下のパターンが考えられる。アピールしたいレベルに応じて、適宜選択すること。

  • node-redが提供する既存のアイコンを利用する。ただし、左記にはnode-error.pngなど、ノードのアイコンでないものも含まれているため、注意すること。
  • 製品・機能セットごとにアイコンを作成する。
  • ノードごとにアイコンを作成する。
    • アイコンは、背景を透過色とした白色で、サイズが20x30のもので作成する。ただし、カラーでアイコンを作成しているノードも存在するため、製品のイメージカラーを用いるかどうか検討すると良い。

ノードの色

  • ノードの色は、Background Colourに列挙された色を選択すると良い。ただし、製品が固有のカラーを持っている場合は、それを指定して良い。

以下は、参考情報。

ユーザによる変更が可能な項目

ノードは、ノードのいくつかの項目で識別できる。 ただし、項目によっては、ワークスペースに配置後にユーザによって変更され、後から識別できなくなる可能性がある。 例えば、アイコンのみが異なる2つのノードがあった場合、ユーザにアイコンを変更されてしまうと区別が付かなくなってしまう。

ノードの識別に使える項目と、ユーザによる変更が可能かを整理した表を以下に示す。

項目 ユーザによる変更が可能か
ノード名 ノードにより許可が可能
アイコン 可能
ノードの色 不可能
入力端子数 不可能
出力端子数 ノードにより許可が可能

サービス開発

Node-REDとの連携

本章の説明対象

対象とするシチュエーション

本章は、以下の2つの状況を対象として、独自サービスとNode-REDとのシステム的な連携について説明する。

① コンソールアプリなどREST API機能のないアプリケーションをサービス化する場合

  • REST APIサーバから外部プロセス呼び出し等を用いてアプリケーションを呼び出す

② WEBアプリなどREST API機能はあるものの、サービスとして公開するAPI名やパラメータを変更したい場合

  • REST APIサーバからさらにREST APIによりアプリケーションにアクセスする
  • アプリケーションとREST APIサーバそれぞれ別のDockerコンテナとして作成する

説明範囲

以下を説明の範囲内とする。

  • REST APIサーバーの構築手順
  • REST APIサーバーからアプリケーションの呼び出し
  • サービスをNode-REDに登録する方法
  • サービスをDocker化する方法

開発の流れ

サービスとNode-REDとの連携機能の開発の流れは次のようになる。

  1. 開発環境の用意
  2. Swagger環境の構築
  3. REST APIサーバーのAPI仕様定義の作成
  4. REST APIサーバーからアプリケーションを呼び出す処理の作成
  5. サービスのDockerfileの作成
  6. Node-RED向けノード(独自ノード)定義の作成

以下では、それぞれの実行手順について述べる。

1.開発環境の用意

以下を前提環境とする。

  • 必須
    • ブラウザ
    • 下記ソフトがインストールされたLinuxもしくはMac環境
    • ZIP解凍ソフト
    • テキストエディタ
    • Docker Engine
  • 開発の際Linux上にあると望ましい環境
    • node.js
    • npm
    • アプリケーションを動作させるための環境(java, gccなど)
    • REST API疎通テストが可能なソフト(curlなど)

2.Swagger環境の構築 (Swagger-Editorをダウンロードする)

Swagger-Editorとは、REST API標準規格に沿ったREST API仕様を定義するためのWebエディタである。 Swagger-Editorを用いることで、定義したREST API仕様からREST APIサーバーの雛形コードを生成できる(Swagger-codeGen機能)。

Swagger-Editorを利用するために、ローカル環境でSwagger-Editorを使用できるよう、ファイル一式をダウンロードする。 また、Swagger-Editorはサーバーを立てることも可能である(参考:DockerHubページ)。

以降では、Swagger-Editorを利用可能にするまでの手順を説明する。

手順1. SwaggerのZipファイルを取得する

GitHubのSwagger-Editorページにアクセス後、Download ZIPボタンを押し、ZIPファイルを取得する(手順書作成時のSwagger-Editorはversion3.011)  

※ Swagger-EditorはApache2.0再配布条件に準拠

手順2. ZIPファイルを解凍し、index.htmlファイルを任意のブラウザにドラック&ドロップ

ZIPファイルを解凍し、index.htmlファイルを任意のブラウザにドラック&ドロップする。 ブラウザにドラック&ドロップをすると、Swagger-Editorが使用可能となる。

3.REST APIサーバーのAPI仕様定義の作成

手順1. Swagger-Editorの画面左のyamlファイルを編集し、サービスとして公開するREST APIを定義する

TIPS

  • 画面右には定義したREST APIのマニュアルがリアルタイムに作成される他、簡易的なAPIテストが可能である
  • APIのバージョンごとにtagを作るとAPIのバージョン管理をしやすい
  • 各APIのoperationIdパラメータがAPIに対応するバックエンド(アプリケーション)側の関数名となる

REST API設計の参考リンク

手順2. Swagger-EditorでREST APIサーバーのコードを生成する

Swagger-Editor画面上部のGenerate Serverから定義したREST APIのサーバー用コードを生成する。 生成したコードはZIPファイルとしてダウンロード可能である。 様々な言語のサーバー用コードを生成可能であり、アプリケーションに合ったREST APIサーバーを選択する。 ここでは標準的なNode.jsを用いた手順を記載している。 Node.jsの場合、下記構成のコードが生成される。

nodejs-server-server-generated.zip
 |-api                   # REST APIのフロントエンドの定義(触らなくて良い)
 |  |- swagger.yaml
 |
 |-conntrollers          # REST APIのバックエンドの定義
 |  |-Version0.js        # Swagger-Editorでtagsに設定した値がファイル名になる(本例では'Version0')
 | |-Version0Service.js # バックエンド処理を実装するファイル
 |
 |-index.js              # Mainプログラム。ポート番号やCORSの設定に使用
 |-package.json          # ライブラリなどを管理する(触らなくて良い)
 |-README.md

手順3. REST APIサーバーを動作可能にする

手順4-2でダウンロードしたZIPファイルをDockerコンテナが作成可能なマシン(Linuxなど)に移動後、解凍する。

unzip nodejs-server-server-generated.zip  # nodejs-server-serverディレクトリが生成される

必須作業ではないが、解凍したディレクトリ内でnpm startとすると、mockサーバーを起動できる。 mockサーバーは、curlなどでAPIをテストできる他、ブラウザでhttp://<mockサーバーのIPアドレス>:8080/docsにアクセスすると、定義したREST APIをSwagger UI画面で確認することができる。

cd nodejs-server-server
npm start    # 必要なライブラリをインストールし、tcp 8080ポートでREST APIサーバを起動

(別のターミナルやSSHセッション)
curl -X <メソッド名> "http://localhost:8080/<basePath>/API名"
# 例:curl -X GET "http://localhost:8080/api/v0/templates20170316JetDataGen_A/options"

Q&A

Q1: npm WARN Invalid version: “0” と出てnpmが止まる。
A1: REST APIのバージョンを0にしたことが原因なので、package.jsonのversionを0以外に書き換える必要がある。

Q2: 起動するポート番号を変更したい。
A2: index.js内のserverPortを変更する。

Q3: npmのインストールが止まる。
A3: プロキシが原因である可能性がある。npmのproxyとregistry設定等をご参照頂きたい。

4.REST APIサーバーからアプリケーションを呼び出す処理の作成

REST APIのバックエンド処理としてアプリケーションを呼び出す処理を作成する。 手順3でmockサーバーを起動した場合はCtrl-Cで停止させる。

本処理は、手順3で解凍したファイルのcontrollers/<タグ名>Service.js(例: controllers/Version0Service.js)内に実装する。

Controllers/<タグ名>Services.jsファイルには、手順1で設定した各APIのoperationIdに対応した関数の雛形が生成されている。 この関数に各REST APIのバックエンドを実装する。 関数における引数と(args)と返り値(res)の扱い方を下記に示す。

バックエンド処理の引数と返り値の扱い方

  • 引数の取得(argsオブジェクト)
    • ユーザーがpath, qurty, bodyに設定した値はargs.<パラメータ名>.valueで取得できる
    • パラメータ名はSwagger-EditorでAPIのparametaersとして設定した値である
    • データ型はSwagger-Editorで設定した形式(objectやbooleanなど)に変換されている
  • 返り値の設定(resオブジェクト)
 /*返り値の設定でよく使用する関数群*/
 res.writeHead(statusCd, otherHeaders)  // ステータスコード及びヘッダ群を設定する。
                                        //JSONデータを返す場合、Context-Typeとしてapplication/jsonというMIMEタイプを指定する。
 res.writeHead(statusCd)                //ステータスコードのみ設定する
 res.setHeader(key, value)              //単一のヘッダを設定する
 res.end(body)                          //bodyとしてstringデータを渡しhttpレスポンスを返す
 res.end()                              //bodyは空のままhttpレスポンスを返す

関数内でアプリケーションを呼び出す処理について

  • 使用するモジュール
    • コンソールアプリの場合 ⇒ child_processモジュール
    • REST APIを有するサーバアプリの場合 ⇒ requestモジュール

requestモジュールは別途インストールする必要がある。 下記コマンドでインストールできる。 なお、--saveオプションはpackage.jsonにrequestモジュールの情報を追加するために使用する。

 npm install --save request

呼び出し方のサンプル

  • コンソールアプリの場合(requestのbodyのデータを分析するアプリの例)
var execSync = require('child_process').execSync;                          //外部プロセスを使用するモジュール
var appPath = process.env.APP_PATH || '/opt/resources/solver.jar';
var process = 'java';

exports.runConsoleAppPost = function(args, res, next) {
    try{
       var input = args.body.value.data;
       var result = execSync(`${process} ${appPath} ${input}`).toString(); //外部プロセス呼び出し
       
       res.writeHead(200, {'Content-Type': 'application/json'});           //Success
       res.end(body);
    }
    catch(err) {
        res.writeHead(500, {'Content-Type': 'application/json'});          //Internal Sever Error
        var body = { mmessage: err.message};
        res.end(body);
    }
}
  • サーバアプリの場合(funcNameの機能をそのまま呼び出す場合の例)
var request = require('request-promise');                                  //http操作用モジュール(マイナー)
var appURL = process.env.APP_URL || 'http://localhost:9092/api/'; 

exoprts.runServerAppPOST = function(args, res, next) {
    var options = {
        uri: appURL + args.funcName.value,
        method: 'POST',
        resolveWithFullResponse: true
    };
    request(options).then(function (response) {                            // サーバ呼び出し
       res.writeHead(respoinse.statusCode, {'Content-Type': 'application/json'});
       res.end(JSON.stringify(response.body));                             //アプリケーションカラン結果をobjectからStringに変換
   })
   .catch(function (err){
       res.writeHead(err.statusCode, {'Content-Type': 'application/json'});
       res.end(err.message);
   };
        
}

処理実装後は、再びmockサーバーを起動しcurlなどでAPIをテストすること。

cd nodejs-server-server
npm start      # 必要なライブラリをインストールし、tcp 8080ポートでREST APIサーバを起動

5.Dockerfileの作成

Linux環境でREST APIの処理が成功したら、次はサービスのDockerfileを作成し、サービスをDocker化する。

Dockerの注意点

  • 起動に必要な情報やサービスごとに一意の情報は環境変数で設定できるようにすること
    • Ex: 接続先URL
  • Docker Imageは小さい方が望ましい
    • ベースイメージはalpine系列が望ましい。node.jsやjavaなど言語ごとにalpineイメージが提供されている
    • 不要なキャッシュは極力排除すること
      • 例えば、新規パッケージをインストールする時は、下記のようにキャッシュを削除する

[Ubuntuの場合]

RUN apt-get update \
    && apt-get clean \
    && apt-get install -y <PACKAGE名>  --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

[Alpineの場合]

RUN apk --no-cache add <PACKAGE名>

Dockerfile作成手順

Dockerの形態は作成するサービス化するアプリケーションによって異なる。

  1. コンソールアプリなどREST API機能のないアプリケーションの場合 ⇒ REST APIサーバーとアプリケーションをあわせて1つのDockerにする
  2. WEBアプリなどアプリケーションにREST API機能があるものの、サービスとして公開するAPI名やパラメータを変更したい場合 ⇒ REST APIサーバーとアプリケーションをそれぞれ別のDockerにする

コンソールアプリなどREST API機能のないアプリケーションの場合での、Dockerfileの作成のポイントは、以下の通りである。

  • Dockerfileでアプリケーションの動作に必要な環境を構築する
    • 例えば、javaアプリの場合はDockerfileにjreのインストール処理を記述する必要がある
  • アプリをDocker内にコピーする
  • アプリがjavaの時のDockerfileの例を下記に示す
FROM node: 6-alpine
ENV APP_ROOT /opt
WORKDIR $APP_ROOT           # カレントディレクトリを$APP_ROOT(今回は/opt)に変更する

#==========================
# Install JRE8
#==========================
# Default to UTF-8 file.encording
ENV LANG C.UTF-8

# add a simple script that can aut-detect the appropriate JAVA_HOME value
# based on whether the JDK or only the JRE is installed
RUN { \
        echo '#!/bin/sh'; \
        echo 'set -e'; \
        echo; \
        echo 'dirname "$(dirname "$(readlink -f "$(which javac || which java)")")"'; \
    } > /usr/local/bin/docker-java-home \
    && chmod +x /usr/local/bin/docker-java-home
ENV JAVA_HOME /usr/lib/jvm/java-1.8-openjdk/jre
ENV PATH $PATH:/usr/lib/jvm/java-1.8-openjdk/jre/bin:/user/lib/jvm/java-1.8-openjdk/bin

ENV JAVA_VERSION 8u131
ENV JAVA_ALPINE_VERSION 8.131.11-r1

RUN set -x \
    && apk add --no-cache \
        openjdk8-jre="$JAVA_ALPINE_VERSION" \
    && [ "$JAVA_HOME" = "$(docker-java-home)" ]

#==========================
#Copy Application
#==========================
COPY package.json $APP_ROOT
RUN npm install && npm cache clean          # 依存パッケージのインストール
COPY . $APP_ROOT                            # ソースコードのコピー
EXPOSE 8080                                 # RESTサーバで使うポートを開ける
CMD ["npm", "start"]                        # docker run時にRESTサーバを起動する

6.Node-RED向けノード(独自ノード)定義の作成 (Node generator)

本ドキュメントでは、Node generatorの利用方法について説明する。 Node generatorとは、1コマンドで独自ノードを自動生成できるツールである。 以下の通り、コマンドの引数にソースのファイルを指定すると、自動的にパッケージングされた独自ノードのファイル群を出力する。

node-red-nodegen \<source file\> -> パッケージ形式で独自ノードのファイル群を自動的に出力

本ツールを用いることで、独自ノードを構成する以下のファイルを自動的に生成できるため、ノード開発者は大幅にノード開発の工数を削減できる。

  • 独自ノード(ノードのjsファイル、htmlファイル、ノードアイコンファイル、言語ファイル)
  • 独自ノードのパッケージングに必要なファイル(README.md、package.json、LICENSE)
  • テストケース

Node generatorの最新情報については、Node generatorのGitHubプロジェクトで取得できる。

Swaggerファイルから独自ノードを開発する流れ

Swaggerファイルからノードを生成する際の流れは、以下の通りである。

  1. Node generatorのコマンドで独自ノードを生成
  2. ノードの説明文、テストケース等を追加

以下でSwaggerファイルから独自ノードを開発する手順を説明する。

1. Node generatorのコマンドで独自ノードを生成

以下のコマンドを実行することで、独自ノードを生成できる。 この例ではHTTPサーバ上のSwagger定義を直接取得しているが、ローカルファイルシステムに保存されたSwaggerの定義ファイルを指定することも可能である。

node-red-nodegen http://petstore.swagger.io/v2/swagger.json

指定できるコマンドライン引数は、以下の通りである。

モジュール名

Node generatorは、モジュール名のデフォルトのプレフィックスとして、「node-red-contrib-」を用いる。 したがって、ノードの名が「swagger-petstore」であるとき、モジュール名は「node-red-contrib-swagger-petstore」となる。 もしデフォルトのモジュール名を変更したい場合は、--moduleオプションによりモジュール名を指定できる。 プレフィックスのみ変更したい場合は、--prefixオプションによりプレフィックスのみ指定することも可能である。

node-red-nodegen http://petstore.swagger.io/v2/swagger.json --module node-red-node-swagger-petstore
node-red-nodegen http://petstore.swagger.io/v2/swagger.json --prefix node-red-node
ノード名

デフォルトのノード名を変えたい場合は、--nameオプションを用いることで、ノード名を指定できる。

node-red-nodegen http://petstore.swagger.io/v2/swagger.json --name new-node-name
バージョン

Node generatorはswagger内に定義されたバージョンをノードのバージョンとして使用する。 モジュールのバージョンを更新する際は、--versionオプションを用いて指定する必要がある。 特に、npmライブラリ上のモジュールを更新する際は、過去に登録したモジュールと同じバージョン番号を用いてnpmに登録できないため、本オプションによってバージョン番号を上げる必要がある。

node-red-nodegen http://petstore.swagger.io/v2/swagger.json --version 0.0.2
キーワード

キーワードは、npmリポジトリ上でモジュールを検索する際に用いる情報である。 例えば、キーワードとして「petstore」を指定したい場合は、--keywordsオプションで本単語を指定すれば良い。

node-red-nodegen http://petstore.swagger.io/v2/swagger.json --keywords petstore

2つ以上のキーワードを追加したい場合は、コンマで区切りで複数のキーワードを指定する。

node-red-nodegen http://petstore.swagger.io/v2/swagger.json --keywords petstore,hitachi
カテゴリ

Node generatorが生成する独自ノードは、デフォルトでフローエディタのパレット上で機能カテゴリに入る様になっている。 本カテゴリを変更したい場合は--categoryオプションで指定できる。 例えば「分析」カテゴリに入れたい場合は、以下の様に指定すれば良い。

node-red-nodegen http://petstore.swagger.io/v2/swagger.json --category analysis

2. ノードの説明文、テストケース等を追加

情報タブに表示するノードの情報、README

Swagger定義から独自ノードを生成する場合は、情報タブやREADME.mdに記載する情報はSwagger定義の内容から自動的に生成される。 そのため、必要に応じてnode.htmlやREADME.mdを修正すると良い。

README

どの様なノードかを利用者が分かる様するためには、README.mdファイルにノードの説明を記載する必要がある。 このREADME.mdは、ノードをパブリックのnpmjsに公開した際、npmjsのサイトで参照できるようになる。 Node generatorはREADME.mdのテンプレートを出力するため、本ファイルを編集すればよい。

node-red-contrib-swagger-petstore/README.md

node-red-contrib-swagger-petstore
=====================

Node-RED node for swagger petstore

Install
-------

Run the following command in your Node-RED user directory - typically `~/.node-red`

        npm install node-red-contrib-swagger-petstore
テストケース

テストケースは、各エンドポイント毎にテストケースのテンプレートが生成される。 Node generatorは、生成されたディレクトリの中の"test/node_spec.js"にテストケースのテンプレートを出力するため、本ファイルを編集すればよい。 入力メッセージ、出力メッセージに加えてノードのプロパティ値を設定する。 例えば下記の場合は、findPetsByStatus_statusのの部分に、プロパティ値を記載する。

node-red-contrib-swagger-petstore/test/node_spec.js

    it('should handle findPetsByStatus()', function (done) {
        var flow = [
            { id: "n1", type: "swagger-petstore", name: "swagger-petstore", wires: [["n2"]],
                method: "findPetsByStatus",
                findPetsByStatus_status: "<node property>", // (1) define node properties
                service: "n3" },
            { id: "n2", type: "swagger-petstore-service" },
            { id: "n3", type: "helper" }
        ];
        helper.load(node, flow, function () {
            var n3 = helper.getNode("n3");
            var n1 = helper.getNode("n1");
            n3.on("input", function (msg) {
                try {
                    msg.should.have.property('payload', '<output message>'); // (3) define output message
                    done();
                } catch (e) {
                    done(e);
                }
            });
            n1.receive({ payload: "<input message>" }); // (2) define input message
        });
    });

テストケースを実行したい場合は、生成されたディレクトリの下で「npm test」を実行すると、テストケースが走る。

cd node-red-contrib-lower-case
npm install
npm test
多言語対応

Node generatorは、英語、日本語、中国語に対応するための言語ファイルのテンプレートが自動的に生成される。 各ファイル内のparameters内の値を翻訳すれば良い。もし全ての言語を翻訳できない場合は、上位のディレクトリ毎削除する(中国語の言語ファイルを作成しない場合はzh-CNディレクトリを削除する)。

node-red-contrib-swagger-petstore/locales/ja

{
    "SwaggerPetstore": {
        "label": {
            "service": "サービス",
            "method": "メソッド",
            "host": "ホスト",
            "header": "ヘッダ",
            "value": "値",
            "isQuery": "クエリ"
        },
        "status": {
            "requesting": "要求中"
        },
        "parameters": {
            "addPet": "addPet",
            "body": "body",
            "updatePet": "updatePet",
            "findPetsByStatus": "findPetsByStatus",
            ...
            "optionalParameters": "任意項目"
        }
    }
}

品質向上

はじめに

本章では、ノード開発における品質向上として主にノードのテストについて説明する。
対象範囲は、開発した 独自ノードのみ とする。すなわち、独自ノードから呼び出される外部モジュールや独自サービスなどは対象外となる。

テスト

ノードの品質を確保することは重要である。何故ならノードはあらゆるフローで利用される可能性があり、ノードの品質がフローの品質へ大きく影響するからである。 本項では、ノードの品質を向上させるために、必要なテスト(単体レベル相当まで)について説明する。
フローの品質については、フロー開発ガイドラインの信頼性・品質向上を参照のこと。

テスト範囲と観点

ノードは大きくJavaScriptコードとHTMLコードの二つで構成されており、それらはNode-RED起動時に読込まれて実行される。

JavaScriptコードはNode-RED ランタイム(以下ランタイムと表記)で実行され、HTMLコードはNode-RED Flow Editor(以下エディタと表記)で実行される。すなわち独自ノードでは大きく分けて以下を対象としたテストを行う必要がある。

# 対象 確認内容
1 ランタイム JavaScriptコードで定義したコードが、ランタイム上で仕様通り正しく動作しているかを確認する。
2 エディタ HTMLコードで定義したコードが、エディタ上で仕様通り正しく表示・動作しているかを確認する。
3 ランタイム・エディタの連携 エディタとランタイム間のデータ連携が仕様通り正しいかを確認する。

ノードのテスト観点としては、一般的なアプリケーション開発におけるテスト観点とノード開発固有のテスト観点があり、それらの観点を基にテストケースを作成する必要がある。 一般的なアプリケーション開発におけるテスト観点は以下を参照。

ノード開発固有のテスト観点は以下に述べる。上記と合わせてテストケース作成の参考としていただきたい。

1. ランタイム

JavaScriptコードで定義したコードが、ランタイム上で仕様通り正しく動作しているかを確認する。

# 大項目 中項目 確認内容
1 入出力確認 入力 メッセージやプロパティ等の入力項目にどのような値が渡されても正常に処理が実行されているか
2 入出力確認 出力 期待する形式・値のデータが出力されているか
3 動作確認 クローズ処理 コネクションなどがクローズされているか
4 動作確認 ステータス処理 ステータスが正しく表示されているか
5 動作確認 例外処理 例外の対応ができているか
6 動作確認 その他 連続実行されても正常に処理が実行されているか

2. エディタ

HTMLコードで定義したコードが、エディタ上で仕様通り正しく表示・動作しているかを確認する。

# 大項目 中項目 確認内容
1 画面表示 パレット上の表示確認 カテゴリやノードの表示が正しいか確認する
2 画面表示 ワークスペース上の表示確認 ノードの表示が正しいか確認する
3 画面表示 情報タブ上の表示確認 ヘルプ情報などの文言が正しいか確認する
4 画面表示 設定ダイアログ上の表示確認 入力項目のアイコンや文言、入力形式などが正しいか確認する
5 画面表示 設定ノードの表示確認 設定ノードの入力項目のアイコンや文言、入力形式などが正しいか確認する
6 画面動作 フローのインポート/エクスポート 独自ノードを含むフローのインポート/エクスポートが正しく動作するか確認する
7 画面動作 カスタムスクリプト oneditprepareなどで設定したカスタムスクリプトが正しく動作するか確認する

3. ランタイム・エディタの組み合わせ

エディタとランタイム間のデータ連携が仕様通り正しいかを確認する。

# 大項目 中項目 確認内容
1 インタフェース プロパティ エディタで設定したプロパティが正しくランタイム側に渡されているか確認する
2 画面表示 ログ ランタイムで出力したログがエディタ側で正しく表示しているか確認する
3 画面表示 ステータス ランタイムでのステータス処理がエディタで正しく表示されているか確認する

テストの実行方法

ノードのテストを行う方法としては以下のテスト方法がある。

  1. 静的解析
  2. Node-REDの実操作による手動テスト
  3. テストコードによる自動テスト

1. 静的解析

静的解析ツールを利用し、ソースコード誤りやコード実行によるテストでは確認できないコーディング基準違反を検知することができる。 また、静的解析ツールとの連携が可能なコードエディタを利用することで、コーディング時から上記の作り込みを防ぐことも可能となる。

Node-REDの本体の開発では、静的解析ツールとしてJSHintを利用している。 ただ、世間一般ではルールの拡張が容易なESLintの利用が多い。
独自ノードや独自ノードの開発では、どちらを用いても良い。

Node-REDのコーディング基準はコーディング規則を参照。

2. Node-REDの実操作による手動テスト

Node-REDに独自ノードをインストールし、Node-RED上で実行させることで動作を確認する。
確認点としては主に「2. エディタ」と「3. ランタイム・エディタの組み合わせ」の観点について確認する。

Node-REDの実操作による手動テストの注意事項

  • 前提条件

    • 開発したノードをNode-REDにインストールするには、npmパッケージ化されている必要がある。 パッケージ化についてはコーディングを参照のこと。
  • 多言語対応

    • ノードが多言語対応している場合、ブラウザの言語設定を変更することで表示を変更する事ができる。 ただしv0.18時点のNode-REDでは、一番最初にアクセスしたブラウザの言語設定をキャッシュとして持ち、以降のアクセスはどの言語設定のブラウザでアクセスしてもキャッシュを表示してしまう。 そのため、別言語の設定確認した場合は、キャッシュをクリアするために一度Node-REDの再起動を行う必要がある。

3. テストコードによる自動テスト

テストコードを作成し、自動でテストを行う。 テスト効率化や回帰テストを考慮して、可能な限りテストコードによる自動テストでテストを行えるようにするのが望ましい。

なお、本項では、JavaScriptコードを対象としたテストコードの記述・実行方法について説明を行う。

ノードはNode-RED上で稼働するため、ノードのHTLMやJavaScriptのコード単体では処理が実行できずテストが行えない。 また手動テストの場合、クリーン処理のために1テスト毎にNode-REDの起動・終了を行う必要がありテスト実行に時間が掛かってしまう。 そこでNode-REDでは、ノード向けテストフレームワークとしてNode Test Helperを開発している(以下helperと表記)。このフレームワークを利用することでノードをコード上で容易に実行する事が可能になる。

またNode-REDでは、テスト用ライブラリとして以下を利用しており、ノードのテストコードによるテストにおいても、これらライブラリの使用を推奨する。 テストコードの作成を行う上で、これらライブラリの仕様を把握しておく必要があるため、それぞれの利用方法を確認しておくこと。

# ライブラリ名 用途 URL
1 Mocha テストフレームワーク https://mochajs.org
2 Should.js アサーション https://shouldjs.github.io
3 Sinon.js テストダブル http://sinonjs.org
4 Nock テストダブル https://github.com/nock/nock
5 Istanbul カバレージ https://github.com/gotwarlost/istanbul

テストコード作成手順

テストコードの作成手順は下記の通り。

  1. 環境準備
  2. テストコード作成
  3. テストコード実行

1. 環境準備

独自ノードを開発しているディレクトリに、テストに必要なモジュールをnpmを利用してインストールする。

npm install node-red-node-test-helper should mocha --save-dev

必要に応じて、sinon、nockなど必要なモジュールをインストールする。

2. テストコード作成

テスト用のディレクトリとテストコードを記述するspecファイルを作成する。
一般的なフォルダ構成・命名規則としてpackage.jsonと同ディレクトリにtestディレクトリを作成し、<テスト対象のファイル名>_spec.jsというファイルを作成する。
[作成例]

.
├── test-node.js
├── test-node.html
├── package.json
└── test
    └── test-node_spec.js  // test-node.jsのspecファイルなのでtest-node_spec.js

テストコードの処理の流れは基本的に下記の通り。

  1. ライブラリ読み込み
  2. helper初期化
  3. helper起動
  4. helperでテスト用フロー読込
  5. helperでテスト用フロー実行
  6. 処理結果の評価
  7. helper終了

helperの説明例を参考にしてテストコードの作成を説明する。
テストコードはMochaを利用しているが、Mochaの記述方法についてはここでは説明しないため、上記URL等で確認していただきたい。

// 1.ライブラリ読み込み
var should = require("should");
var helper = require("node-red-node-test-helper");
var lowerNode = require("../lower-case.js");

// 2.helper初期化
helper.init(require.resolve('node-red'));

describe('myNode', function () {

  // 3.helper起動
  beforeEach(function (done) {
      helper.startServer(done);
  });

  // 7. helper終了
  afterEach(function (done) {
      helper.unload();
      helper.stopServer(done);
  });

  // テストケース
  it('should make payload lower case', function (done) {
    // テストフロー定義
    var flow = [{ id: "f1", type:"tab"},
                { id: "n1", z:"f1", type: "lower-case", wires:[["n2"]] },
                { id: "n2", z:"f1", type: "helper" }];
    // 4. helperでテスト用フロー読込
    helper.load(lowerNode, flow, function () {
      var lowerCase = helper.getNode("n1");
      var helperNode = helper.getNode("n2");
      // 6. 処理結果の評価
      helperNode.on("input", function (msg) {
        msg.should.have.property('payload', 'uppercase');
        done();
      });
      // 5. helperでテスト用フロー実行
      lowerCase.receive({ payload: "UpperCase" });
    });
  });

  it('should do something', function (done) {
    // Write test codes here ...
  });
});
1.ライブラリ読込

必要最低限の読込む必要があるライブラリは以下の3つとなる。

  1. should.js
  2. node test helper
  3. テスト対象のノード(例ではlower-case.js)

必要に応じてsinon.js等のライブラリを追加する。

2.helper初期化

helper自体には、ノードを動作する機能を持っておらず、Node-RED本体のコードを参照するようになっている。 helper.init()へNode-REDのパスを渡して実行することで本体のコードを読込む処理を行う。これは必ず実行すること。

3.helper起動 & 7.helper終了

helperを起動し複数のテストケースを実行させた場合、一つのテストケースの実行結果がhelper内に残り、他のテストケースの実行結果に影響を及ぼす可能性がある。
そのため、1テストケース毎にhelperを起動・停止して初期化を行う。
helperの起動・停止をそれぞれbeforeEachとafterEach内で呼び出すことで対応している。

4.helperでテスト用フロー読込

テストケースで実行するテスト用フローを定義しhelperに読込ませる。
独自ノードが出力端子を持ち、その出力結果を評価したい場合は、 helperノード を利用してフローを作成することを推奨する。 helperノードは、helperが提供しているmockノードであり、テスト対象ノードの後続ノードにhelperノードを接続したフローを作成することで、テスト対象ノードの出力結果を確認することができる。

なお、テスト用フローには、tabの定義も行う事を推奨する(例で{ id: "f1", type:"tab"},と記載されている箇所)。 tab定義は省略しても動作する事は可能だが、省略した場合内部的にglobalというtabにデプロイされるため、 catchノードやflow contextなど同一フロー内を対象とした処理が正常に動作しない。 より実際に近い動作を行わせるために、tab定義を記述する事を推奨する。

ノードがクレデンシャル情報を利用する場合は、以下のAPIの通りに引数に渡すことで対応可能である。
Version 0.1.7時点でのNode Test HelperのloadのAPI

load(testNode, testFlows, testCredentials, cb)
# 引数 必須 説明
1 testNode ○ テスト対象のノード
2 testFlows ○ helperに実行させるフロー定義
3 testCredentials × helperに渡すクレデンシャル情報
4 cb ○ フロー読込後に実行させるコールバック関数
5. helperでテスト用フロー実行

注:テストコード内では処理順序上、上記コード例の通り「6. 処理結果の評価」の後に実行するようにすること。

多くのノードは、前ノードからのメッセージ受信や何らかのアクションを契機に処理の実行を行う。
そのため、ノードを実行させるための処理をテストケース内で記述する必要がある。 入力端子を持つノード(前ノードからメッセージを受け取って処理を行うノード)の場合は、以下のようにreceiveを利用してmsgを渡すことができる。

lowerCase.receive({ payload: "UpperCase" });

入力端子を持たないノードの場合は、そのノードに合わせた処理を記述する必要がある。 ノードによって実行方法が異なるため、ここでは参考としてcoreノードのサンプルを提示する。

  • tailノード
    tailノードは指定されたファイルを監視し、ファイルに追加があればその追加内容をメッセージとして後続ノードに送る。 fsモジュールを使って実際にファイルに文字列を追加することで、ノード実行の契機を与えている。

  • websocketノード
    websocketノードは、websocketサーバを追加し、クライアントから送られたデータを後続ノードに送ることができる。(逆も可)
    wsモジュールを使ってwebsocketクライアントを作成し、データを送信することでノード実行している。

6. 処理結果の評価

ノードの処理結果を評価する式をshould.jsを利用して記述する。
テスト対象ノードが出力したメッセージの評価を行いたい場合は、上記コードの通り、helperノードのinputリスナー内で評価する。 inputリスナーは前ノードが出力した後に、呼び出されるイベントであり、引数に前ノードが出力したメッセージが渡される。

テストコードサンプル

ノードのテストコードのサンプルを参照したい場合は下記を確認のこと。

3. テストコード実行

テストコードを作成した後に下記コマンドを実行することでテストを実行できる。

mocha \"test/**/*_spec.js\"

外部サービスを呼び出すノードのテスト

独自サービスなどの外部サービスを呼び出すノードを ローカル環境 でテストする場合、ローカル環境から外部サービスへのアクセスができないと処理の実行ができない。 このような場合はスタブを利用したテストコードを作成することで環境に依存せずにテストする事ができる。
特に単体テストの場合、他の開発者環境やCI/CD環境など異なる環境でテストコードが実行される可能性が高く、環境に依存しないテストコードを作成することを推奨する。 なお、結合テストやシステムテストではスタブを用いずに実外部サービスと処理を行うテストを実施のこと。

スタブの作成方法

本項では例として、HTTP APIを持つ独自サービス(外部サービス)のスタブの作成方法について述べる。

  • nockを利用して作成する (推奨)

    nockはHTTPリクエストのテストのために作られたモジュールである。標準モジュールのhttpの一部をオーバライドすることで、リクエストのスタブを実現している。下のexpressと比べて、簡易な記述でスタブを作成でき学習が容易である。

  • expressを利用して作成する

    expressはWEBアプリ開発のフレームワークである。expressを利用してローカルにWEBサーバを作成し、テスト時は作成したWEBサーバに対してリクエストを行う事で、 WEBサーバのスタブとして働き、外部との依存を切り離す。nockでは対応できない特殊なリクエストを含む処理のテストを行う場合は、expressを利用してスタブWEBサーバの作成を検討すると良い。

    • テストコード作成例
      • httprequestノード テストコード内で指定のレスポンスを返すtestServerを作成し、testServerにリクエストを行うことで、 ローカル外部のWEBサーバに接続することなくテストを実施している。

テストコードによる自動テストの注意事項

  • テストダブルの活用
    テストダブルを活用することで、上記で述べた依存の切り離しや、動作検証が容易になる等多くのメリットが得られる。nockやsinonの利用方法を把握し、必要な場面で活用できるようにすること。

  • テスト時におけるsettings.js
    Version 0.1.7時点でのNode Test Helperでは、内部的に作成されたほぼ空のsettings.jsが読み込まれ、任意のsettings.jsを利用してテストすることができない。
    そのため、settings.jsの設定内容を参照するコードが存在する場合は、テストコードによるテストが実施できない可能性がある。

  • OS依存
    Node-REDはnode.jsで稼働しており、node.jsがあれば基本的にOSに依存せずに実行できる。
    しかし、ファイルアクセスやコマンド実行等のOSリソースを利用する機能を実行する場合は、OSによって動作が異なる可能性がある。Linux,Windows,MacOSと言った代表的なOS環境でそれぞれテストを実行しておくのが望ましい。

ノード開発固有のテスト項目

  • package.jsonのテスト
    Node-REDが独自ノードを読み込む際に、ノードのpackage.jsonを参照して読込む。そのため、package.jsonの記述に誤りがあると正常に読み込むことはできない。 npmコマンドを利用して、実際に独自ノードをnpmパッケージとしてインストールし、Node-REDで読み込めるかテストを実施することを推奨する。

セキュリティ・秘匿化

ノードを開発する際、ノードのプロパティ値のうち、クレデンシャル情報(認証情報やユーザ名、パスワード等)についてはフローデータに含まれないよう適切に変数を定義する必要がある。 もし、本定義を正しく行わないと、フローを共有した際にクレデンシャル情報が第三者に共有されてしまい、第三者による意図しない操作(REST APIに不正にアクセスする等)が生じる恐れがある。

また、JavaScriptはソースコードがコンパイルされない言語であることから、開発したノードをリポジトリに置く場合、第三者によりソースコードの内容を読まれることを考慮すべきである。 ソースコードを読まれたり、再利用されたりすることを防止したい場合は、コード難読化を行った後でノードを公開する必要がある。

本節では、クレデンシャル情報のハンドリング方法、コード難読化の方法についてガイドする。

クレデンシャル情報の記述方法

クレデンシャル情報をノードのプロパティで扱う方法は、公式ドキュメントに記載されている。 クレデンシャル情報のプロパティが、通常のノードのプロパティと異なる点は以下の2つである。

  1. ノードのHTMLファイル内でクレデンシャル情報として変数を定義
  2. ノードのJavaScriptファイル内で、クレデンシャル情報を扱うよう定義

それぞれについて以降で詳細に説明する。

1. ノードのHTMLファイル内でクレデンシャル情報として変数を定義

通常のノードのプロパティを扱う際、HTMLファイルに記載するRED.nodes.registerType()の第二引数内で、defaultの中に変数を記載する。 一方、クレデンシャル情報として扱いたい変数がある場合、defaultではなくcredentailsの中に下記の様に記載する必要がある。

 RED.nodes.registerType('my-node',{
     credentials: {
         username: {type:"text"},
         password: {type:"password"}
     },

2. ノードのJavaScriptファイル内で、クレデンシャル情報を扱うよう定義

通常のノードのプロパティは、ノードのJavaScriptファイル内で特に宣言することなく参照できる。 一方、クレデンシャル情報の場合は以下のとおり、RED.nodes.registerType()の第三引数にHTMLファイル内で宣言したものと同様の宣言が必要である。

 RED.nodes.registerType("my-node",MyNode,{
     credentials: {
         username: {type:"text"},
         password: {type:"password"}
     }
 });

また、ノードのJavaScriptファイル内でクレデンシャル情報を参照するには、this.credentials.<変数名>と指定する必要がある。

コード難読化の方法

コード難読化を行うには、javascript-obfuscatorというツールを用いる。 また、Node generatorでSwagger定義からノードを生成する場合は、--obfuscateオプションを用いることで、難読化したノードを生成することも可能である。 なお、ここでコード難読化の対象とするのは、ノードのJavaScriptファイルのみである。 ノードのHTMLファイルは、ユーザインターフェイスのみであるため、難読化の対象外とする。

1. javascript-obfuscatorコマンド

以下のコマンドを実行することで、難読化したノードのJavaScriptファイルが生成される。

sudo npm install -g javascript-obfuscator
javascript-obfuscator <ノードのJavaScriptファイル>

2. Node generatorコマンド

Node generatorコマンドでコード難読化を行うには、下記のとおり--obfuscateオプションをつけて実行する。 本オプションをつけてノードを生成すると、ノードのJavaScriptファイルのコードのみ難読化される。

sudo npm install -g node-red-nodegen
node-red-nodegen http://petstore.swagger.io/v2/swagger.json --obfuscate

ノードの共有と利用

想定環境

ノードの作成は、Node-REDユーザーのローカル環境で行うものとする。 また、ノードの共有と管理には、GitHubを利用する。 共有されたノードを利用するためには、Node-REDが提供するNode-REDを利用して、そのNode-REDでノードが利用できるようにするためのフローを作成し、実行する。

ノードの共有

ノードを作成したユーザーは、そのユーザーが所属するプロジェクト名のグループで、作成したノードを管理するGitHubプロジェクトを作成し、コード類をアップロードする。

まず、GitHubでプロジェクトを作成する。 次に、New project画面でプロジェクトの設定を行う。 Project pathでは、プルダウンから自身が所属するプロジェクトを選択する。 Project nameでは、作成したノードを示すプロジェクト名を記載する。 Visibility Levelは、Privateに設定する。 その後、Create projectをクリックする。 プロジェクトが作成出来たら、プロジェクトのURLをコピーする。 その後、ノードを開発したローカル環境で、以下のコマンドを実行する。

git clone <コピーしたURL>
cd <GitHubプロジェクト名>

このディレクトリに、開発したノードのコード類を全て格納する。 その後、以下のコマンドを実行する。

git add .
git commit -m "First commit"
git push

GitHubのプロジェクトの画面を更新し、開発したノードのコード類がアップロードされていることを確認する。 Node-REDの同じプロジェクトに所属するメンバーは、GitHubにログインすると、アップロードされたコード類を閲覧することができる。

共有されたノードの利用方法

アップロードされたノードを利用するためには、Node-REDで、そのNode-REDに共有されたノードを利用可能状態にするためのフローを用意して実行する。

用意するNode-REDのフローでは、execノードを用いて、Node-REDが稼動するDockerコンテナ内に、ノードを配備する。 また、配備した後には、Node-REDの再起動を行う。

フローの用意

Node-REDが提供するNode-REDの画面を開き、以下のフローをクリップボードから読み込む。

[{"id":"491d50c0.dabf98","type":"exec","z":"215626a.598695a","command":"cd /root/.node-red && npm install git+https://<ID>:<PASSWORD>@<GitHubURL>","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"ノードを配備","x":340,"y":100,"wires":[["c9f2e61c.0b47a"],["11170135.1120bf"],[]]},{"id":"daa486c2.c5ee6","type":"inject","z":"215626a.598695a","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":100,"wires":[["491d50c0.dabf98"]]},{"id":"c9f2e61c.0b47a","type":"debug","z":"215626a.598695a","name":"execノードの標準出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":560,"y":80,"wires":[]},{"id":"11170135.1120bf","type":"debug","z":"215626a.598695a","name":"stderrを表示","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":530,"y":120,"wires":[]}]

execノードの設定にコマンドの欄がある。 その設定値の<GitHubURL>は、GitHubプロジェクトのURLに置き換える。 また、<ID>と<PASSWORD>は、ユーザのIDとパスワードに置き換える。 その後、フローをデプロイし、injectノードをクリックして、実行する。 エラーが出力されていなければ、コマンドは正常に終了している。

これによって、Node-REDが稼動するコンテナ内の適切な位置に、ノードのコード類が配備される。

Appendix

Tips・べからず集

ノードの状態をフローエディタから把握できるようにする

メリット

  • フローエディタ上にノードのステータスを表示することで、ノードの利用者が状態を容易に把握できるようにする

留意点

  • エディタ上のノード状態表示はオーバヘッドを伴うため、性能への影響を考慮する

解説

外部サーバへの接続を必要とするノードなどでは、ノードが正常に実行可能な状況あることを確認できることが望ましい。そこで、ステータス表示機能を用いるなど、ノードの状態をエディタ上で確認できるようにすべきである。

サーバー機能(Webサーバなど)を持たせない

メリット

  • 外部からアクセス可能なサーバ機能はフローのセキュリティホールとなる可能性があるためサーバ機能を持たせないようにする

留意点

解説

ノードにWebサーバなどのサーバ機能を持たせると、予期せぬアクセスによるセキュリティホールを生じる可能性がある。セキュリティが重要な用途ではこのような機能を持たせないことが望ましい。

他サーバーとセッション確立が必要な場合、確立するまでに届いたメッセージを破棄しない

メリット

  • メッセージが必ず処理さることを保証する

留意点

解説

外部サーバーに対する処理を行う場合、セッションが確立するまでに受け取ったメッセージを破棄してしまうと期待する結果とならない可能性がある。セッションを確立するまでのメッセージを保存しておくなど、処理が必ず行われることを保証することを考える。

個人情報(パスワード等)を埋め込まない

メリット

  • 個人情報の漏洩を防止する
  • 環境の移行を容易にする

留意点

解説

ノードの設定情報はさまざまな方法で参照される可能性があるため、個人情報等秘匿が必要な情報をノード上に埋め込むことは適切ではない。また、アクセスのための情報が変化した場合に対応が困難となる。そのため、このような情報は秘匿可能な形で与えるようにすべきである。

サービスの各機能ごとにノードを作成するのではなく、サービスの関連機能群を1ノードにまとめる

メリット

  • パレット上でノード選択を容易にする
  • フローの処理把握を容易にする

留意点

  • ノードをまとめ過ぎると逆にノードの機能が複雑化する可能性があるため、トレードオフを検討する

解説

一般にサービスの提供するREST APIは細かな処理単位で定義されており、そのままノード化するとパレット上のノードが増加する。また、Node-REDにおけるメッセージ処理の概念に沿わなくなる可能性もある。そこで、ノードの機能面から整理し、関連機能群を1ノードとして纏めることを検討すべきである。ノードの中でサービスのどの機能を利用するかは、ユーザが選択できるようにする。

ノードの実行順序を仮定しない

メリット

  • 予期しないノードの実行が行われないようにする

留意点

解説

Node-REDにおけるノード実行は、入力と出力の関係により規定されるのみであり、左から右、上から下のように順序が規定されるわけではない。また、前ノードのメッセージ出力がすべて終わってから次ノードの実行が開始されることも保証されていない。このため、ノード実行順序を仮定した実装はおこなうべきでない。

ノード内で長時間の逐次処理を行わない

メリット

  • フロー実行が停止することを抑止する

留意点

  • 逐次プログラムを非同期処理により分割する場合、不具合を作りこみ易いので注意する

解説

Node-REDはNode.js上で動作しているが、Node.jsは逐次実行を基本としている。このためある処理が長時間の逐次処理を行う場合には、その他の処理が停止してしまう可能性がある。そのような場合、複数の逐次処理を非同期処理により分割するなど実装方法を工夫する。

Node generatorを用いたfunctionノードから独自ノードの開発

Node generatorを用いてfunctionノードから独自ノードを簡潔に開発することができる。 本節ではその開発手順を紹介する。

functionノードからノードを生成する際の流れは、以下の通りとなる。

  1. functionノードのノードプロパティからJavaScriptのコードをjsファイルとして出力
  2. Node generatorのコマンドで独自ノードを生成
  3. ノードの説明文、テストケース等を追加

以下でfunctionノードから独自ノードを開発する手順を説明する。 なお、本操作手順は、ローカル環境上にインストールされたNode-REDを用いて開発を行うことを前提とする。 (現在のところNode-RED実行環境ではノード開発を行う機能を提供していない)

1. functionノードのノードプロパティからJavaScriptのコードをjsファイルとして出力

functionノードにJavaScriptコードを書き、テストした後に「ライブラリへ保存」の機能を用いてJavaScriptコードをjsファイルとして書き出す。 Node generatorは、ノード名としてfunctionノードの名前を使用するため、functionノードを書き出す前にノードの名を入力すると良い。

Node-REDは、ディレクトリ<ホームディレクトリ>/.node-red/lib/functions/に、jsファイルを保存する。 したがって、コマンドラインの引数して本jsファイルのパスを指定する必要がある。

2. Node generatorのコマンドで独自ノードを生成

以下のコマンドを用いて、独自ノードを生成する。

node-red-nodegen ~/.node-red/lib/function/lower-case.js

指定できるコマンドライン引数は、以下の通りである。

モジュール名

Node generatorは、モジュール名のデフォルトのプレフィックスとして、「node-red-contrib-」を用いる。 したがって、ノードの名が「lower case」であるとき、モジュール名は「node-red-contrib-lower-case」となる。 もしデフォルトのモジュール名を変更したい場合は、--moduleオプションによりモジュール名を指定できる。 プレフィックスのみ変更したい場合は、--prefixオプションによりプレフィックスのみ指定することも可能である。

node-red-nodegen ~/.node-red/lib/function/lower-case.js --module node-red-node-lower-case
node-red-nodegen ~/.node-red/lib/function/lower-case.js --prefix node-red-node

ノード名

前述の様にfunctionノードからノードを開発する場合は、functionノードに指定したノード名が、独自ノードのノード名として使われる。 もしデフォルトのノード名を変えたい場合は、--nameオプションを用いることで、ノード名を指定できる。

node-red-nodegen ~/.node-red/lib/function/lower-case.js --name new-node-name

バージョン

Node generatorが用いるデフォルトのモジュールのバージョンは「0.0.1」である。 モジュールのバージョンを更新する際は、--versionオプションを用いて指定する必要がある。 特に、npmライブラリ上のモジュールを更新する際は、「0.0.1」のままではnpmにモジュールを登録できないため、本オプションによってバージョン番号を上げる必要がある。

node-red-nodegen ~/.node-red/lib/function/lower-case.js --version 0.0.2

キーワード

キーワードは、npmリポジトリ上でモジュールを検索する際に用いられる情報である。 例えば、キーワードとして「lower-case」を指定したい場合は、--keywordsオプションで本単語を指定すれば良い。

node-red-nodegen ~/.node-red/lib/function/lower-case.js --keywords lower-case  

2つ以上のキーワードを追加したい場合は、コンマで区切りで複数のキーワードを指定する。

node-red-nodegen ~/.node-red/lib/function/lower-case.js --keywords lower-case,function

カテゴリ

Node generatorが生成する独自ノードは、デフォルトでフローエディタのパレット上で機能カテゴリに入る様になっている。 本カテゴリを変更したい場合は--categoryオプションで指定できる。 例えば「分析」カテゴリに入れたい場合は、以下の様に指定すれば良い。

node-red-nodegen ~/.node-red/lib/function/lower-case.js --category analysis

3. ノードの説明文、テストケース等を追加

情報タブに表示するノードの情報

Node generatorは、node.htmlファイルにノードの情報のテンプレートを出力する。 例えば、下記の様なテンプレートを修正する必要がある。

node-red-contrib-lower-case/node.html

<script type="text/x-red" data-help-name="lower-case">
    <p>Summary of the node.</p>
    <h3>Inputs</h3>
    <dl class="message-properties">
       <dt>payload<span class="property-type">object</span></dt>
       <dd>Explanation of payload.</dd>
       <dt class="optional">topic <span class="property-type">string</span></dt>
       <dd>Explanation of topic.</dd>
    </dl>
    <h3>Outputs</h3>
    <dl class="message-properties">
        <dt>payload<span class="property-type">object</span></dt>
        <dd>Explanation of payload.</dd>
        <dt class="optional">topic<span class="property-type">string</span></dt>
        <dd>Explanation of topic.</dd>
    </dl>
    <h3>Details</h3>
    <p>Explanation of the details.</p>
    <p><b>Note</b>: Note of the node.</p>
</script>

テンプレートには、ノードのSummaryと3つのセクションが用意されている。 Inputsには、入力されるメッセージの情報を記載する。 Outputsには、出力されたメッセージの形式についての説明を記載する。 もし、追加の情報がある場合、Detailsに記載するとよい。

README

どの様なノードかを利用者が分かる様するためには、README.mdファイルにノードの説明を記載する必要がある。 このREADME.mdは、ノードをパブリックのnpmjsに公開した際、npmjsのサイトで参照できるようになる。 Node generatorはREADME.mdのテンプレートを出力するため、本ファイルを編集すればよい。

node-red-contrib-lower-case/README.md

node-red-contrib-lower-case
=====================

Node-RED node for lower case

Install
-------

Run the following command in your Node-RED user directory - typically `~/.node-red`

        npm install node-red-contrib-lower-case

テストケース

テストケースはノードの品質を維持するために重要であるため、追加することが推奨される。 Node generatorは、生成されたディレクトリの中のtest/node_spec.jsにテストケースのテンプレートを出力するため、本ファイルを編集すればよい。

node-red-contrib-lower-case/test/node_spec.js

    it('should have payload', function (done) {
        var flow = [
            { id: "n1", type: "lower-case", name: "lower-case", wires: [["n2"]] },
            { id: "n2", type: "helper" }
        ];
        helper.load(node, flow, function () {
            var n2 = helper.getNode("n2");
            var n1 = helper.getNode("n1");
            n2.on("input", function (msg) {
                msg.should.have.property('payload', 'abcd'); // (2) define output message
                done();
            });
            n1.receive({ payload: "AbCd" }); // (1) define input message
        });
    });

上記の例のノードは、大文字を小文字に変える役割を持つ。 そのため、入力されたメッセージが「AbCd」の場合、出力メッセージは「abcd」となることを記述すれば良い。 テストケースを実行したい場合は、生成されたディレクトリの下で「npm test」を実行すると、テストケースが走る。

cd node-red-contrib-lower-case
npm install
npm test

オブジェクトストレージの推奨ディレクトリ構成

独自サービスやNode-RED内のサービスは、Node-REDが提供するオブジェクトストレージを利用して、入出力データや、それらのサービス上で利用されるスクリプトファイルなどを保存できる。 オブジェクトストレージは、一般的なNASなどと同じように、データをディレクトリで利用できるが、オブジェクトストレージのディレクトリ自体には特にポリシーがないため、場当たり的に利用していると、ディレクトリ構成が見にくくなる。

そこで、以下のディレクトリ構成を推奨ポリシーとする。

.
├── default
│   ├── NodeSample
│   └── ....
├── flows
│   ├── MyFlow1
│   │   └── shared_data_in_flow_1.dat
│   └── MyFlow2
│       └── shared_data_in_flow_2.dat
└── my_enabler_1
    ├── transform1.dat
    └── transform2.dat

Node-REDのユーザーは、defaultバケット内のディレクトリをCRUD操作できるが、サンプルフローの実行に支障が発生するため、行わない方が良い。

ユーザーが独自にオブジェクトストレージを利用する際は、defaultバケットとは別のバケットを新たに作成し、その中でフローごとにディレクトリを作成してデータ管理をするのが良い。 ただし、defaultバケット内に格納されたスクリプトのみ参照できるノードがあるため、そのようなノードを利用する場合は、defaultバケット内に、自身のフロー用のディレクトリを用意し、管理する。

独自サービスを開発し、その独自サービスがあらゆるフローで統一的に利用するデータがあれば、新たにバケットを作成し、その中で独自サービスごとにディレクトリを作成してデータ管理をするのが良い。

Clone this wiki locally