私がNode-REDを使い始めて4年が経ちました。これまでにハッカソンや勉強会等でNode-REDのフローを見てきて、開発の後戻りや混乱が起きやすいアンチパターンが分かってきたため、紹介します。
Node-REDとは
マウスで部品をつなぐ操作のみでアプリケーションをノンプログラミングで開発できる開発環境です。IoT分野に注力している企業は、ほぼ全て活用しておりIoT業界標準のソフトウェアとなっています。
Node-REDは、クラウドとエッジデバイスの環境で使えるため、両環境に対応させたフローを開発することでポータピリティ性の高いフローになります。また、Node-REDはクラウドネイティブアプリケーションの開発ルールである12 Factor Appに沿った作りとなっているため、フローにおいても本ルールに従うことで、今時の(?)開発手法の恩恵を受けることができます。
それではさっそく作りがちなアンチパターンと解決方法を紹介します。
1. コーディング能力が必要となるfunctionノードを多用する
functionノードはJavaScriptを記述できる便利ノードですが、沢山使ってしまうとコーディングになってしまいノンプログラミングで素早く開発できるNode-REDの利点がなくなってしまいます。例えば以下のフロー内では、1つ目のfunctionノード「AbCdを設定」では「msg.payload = 'AbCd';」というコードを記載し、msg.payloadに値を設定しています。2つ目のfunctionノード「大文字を小文字に変換」では、「msg.payload = msg.payload.toLowerCase();」というコードで大文字を小文字に変換しています。
値の代入を行う際は、changeノードかtemplateノードを使いましょう。やむをおえずfunctionノードを用いた場合は、Node generatorを用いて一般的な自作ノードに変換し、フローライブラリに公開すると良いです。これによってコーディングができない他のユーザがノードを再利用できるようになります。
その他、四則演算などの簡単な処理を行いたい場合は、changeノードのJSONata機能を使うとシンプルになります。
2. changeノードで複数の変数を代入する
前述の様にfunctionノードで値の代入を行う代わりとして、changeノードを用いて代入することがあります。1個の変数の代入であれば、changeノードを用いると簡単ですが、複数の変数を代入しだすと、どの値を扱っているかが分かりにくくなってしまいます。例えば、以下のフローではmsg.payloadに入った「タイムスタンプ」とmsg.topicに入った「topicValue」という値を合わせて、「{"timestamp": "<タイムスタンプ>", "value": "topicValue"}」というJSONオブジェクトを生成しています。
changeノードのプロパティでは、msg.payloadのタイムスタンプをmsg.tmpに移動した後、新規にJSONオブジェクトに値を代入しています。ルールが3つもあり何をしているか分かりにくいです。
本問題を解決するには、templateノードのMustacheテンプレートを用いると複数の変数の代入が分かりやすくなります。Mustacheテンプレートでは前のノードから受け取ったJSONデータ内の変数を「{{}}」で指定して新たなJSONオブジェクトを生成できます。
3. injectノードに初期値を定義する
injectノードのノードプロパティでは、次のノードに渡す値として、タイムスタンプ値以外にも変数やJSONデータを定義できる様になっています。例として以下では、GPIO端子のON/OFFを制御するために0または1をgpioノードに送るフローを用いました。
フローの実行と値設定を同時にできて一見便利そうですが、フローの再利用の観点だと値は設定しない方が良いです。以下の様にinjectノードとtemplateノードに分けてフローを作っておいた方が、後々フローを修正する手間が減ります。
例えば、後からswitchノードで判定結果を基に0か1を送る様に変更したい場合、templateノードの前にswitchノードを追加するだけでよくなります。もしinjectノードに値を設定していた場合は、後からtemplateノードを追加しないといけないことが分かるかと思います。
4. execノードで環境依存のコマンドを実行する
execノードはNode-REDから手軽にコマンドを実行できて便利ですが、Mac、Linux、Windows環境間でフローの受け渡しをすると、環境依存のコマンドが実行できないためトラブルが起こります。例えば、Raspberry PiのIPアドレスを取得するために「hostname -i」コマンドをexecノードで実行することがよくあります。
この書き方をしてしまうと、MacやWindowsで動作しなくなってしまいます。代替手段としてIPアドレスを取得するサードパーティノードを用い、全てのNode.js環境で動作する書き方の方が親切です。
他のケースにおいても、execノードに環境依存のコマンドを書く前に、他の環境でも動くサードパーティノードがないか探してみましょう。
5. fileノードを用いてデータを保存する
Node-REDでは「センサから集めたデータをcsv形式でfileノードを用いて保存する」というフローを書きがちですが、データ管理で後から困るケースが多いです。
まずPaaS環境では、インスタンスを再起動するとファイルシステムに保存したデータが消える仕様になっているものが多いため、データを消失してしまいます。そのため、初めからfileノードが使えない様になっているNode-RED環境もあります。クラウドとエッジデバイスの両方で動作するフローにするには、fileノードの代替としてDBノードを用いた方が良いです。Node-RED公式のMySQLノードやMongoDBノードがおすすめです(MySQLノードの使い方はこちらが参考になります)。
DBを用いることで古いデータがディスク領域を圧迫してきた際に、一部データを削除することも容易になります。
6. フローの編集が困難なsubflowを多用する
subflowノードは、一連のフローをノード化して再利用するために用いるノードです。プログラミング言語の関数の様に使えますが、一度subflow化してしまうと中のフローは編集が困難です。特にsubflowが入れ子になっている場合は、階層構造が複雑化しバグを作り込む原因となります。
subflow化したいフローは、代わりにhttp-in、http responseノードを用いてREST API化し、http requestノードから呼び出すようにしましょう。
REST API化の副次的な効果として、一部のフローを切り出して別のNode-REDインスタンスで動作させ、負荷分散や障害に強いマイクロサービスアーキテクチャに変更できるメリットもあります。マイクロサービス向けのテストツールも使えるため、フローの品質維持もできるようになります。
7. ノードの説明をcommentノードに記載する
ノードの説明を書くために、各ノードの横にcommentノードを配置することがあります。
このフローを編集する際、ノードの移動と合わせて対応するcommentノードも移動する必要が出てきて、開発の時間の殆どがノードの配置に費やされてしまいます。commentノードはフローの説明だけとし、ノードの説明はノードプロパティの中にある「説明」の中に記載しましょう(v0.20から有効)。
ノードの説明を記載すると、情報タブでノードの説明を確認できます。
特にhttp-inノードにおいては、HTTPエンドポイントの説明を記載できるnode-red-node-swaggerノードを用いましょう。
本機能を用いるとhttp://localhost:1880/http-api/swagger.jsonからOpen API定義を取得できるため、API Managementとの連携したり、Node generatorを用いてREST APIにアクセスする自作ノードを開発したりできるようになります。また、Node-REDフローエディタ上で以下の様なSwagger UIを用いてエンドポイントをテストすることもできます。
8. フロー内に環境固有の変数を定義する
Node-RED起動時に初期化処理としてグローバルコンテキストやフローコンテキストに値を入れるフローを作成される方がよくいます。
固定の値なら問題ないですが、環境毎に異なる値を入れる必要がある場合、フローを新環境に読み込む毎に値を記入する必要があります。クラウドネイティブアプリケーションの開発では、一般的に環境変数に環境固有の情報を格納します。Node-REDは環境変数から値を読み取る機能があるため、Node-REDにおいても環境変数に設定した値を用いるようにしましょう。
これによって複数のNode-RED環境を用意する際、環境変数を設定するのみで環境固有の情報を設定できるため、デプロイ作業が簡単になります。
9. 変数名が衝突する可能性があるグローバルコンテキストを使う
ノード間で値を共有する場合、グローバルコンテキストを用いることがあります。もし2つの異なるフローを1つのNode-REDに読み込んだ場合、グローバルコンテキストの変数名が衝突してしまい、フローの挙動がおかしくなってしまうことがあります。
全てのタブで共有する必要がある変数以外は、タブ内のノードで共有するフローコンテキストを用いるようにしましょう。
10. デバックタブのみでログを参照する
Node-REDの開発では、debugノードを用いてデータをデバッグタブに出力し、フローを流れているデータを確認します。この確認方法は開発時の一時的な方法であり、WebSocket接続切断や表示件数超過によってデータがデバッグタブから消えてしまいます。
ログ管理を行いたい場合は、debugノードのノードプロパティにて「システムコンソール」にチェックを入れます。
これによってデフォルトで標準出力にログを出力できるため、Papertrailの様な標準出力をログとして収集するツールが使えます。また、settings.jsにlogstash等のカスタムロガーを指定することもできます。
最後に
Node-REDはプロトタイプで用いることが多いことから、その場限りのフローを書きがちです。しかし、今後フローのバージョン管理機能や、クラウドやエッジデバイスにデプロイできるデスクトップ版Node-REDが一般的になり本番適用が進んでくると、環境に依存しない再利用性の高いフローが重要になってくると考えます。みなさんもアンチパターンのノウハウを見つけましたら、ぜひ共有してみてください。
紹介したフロー
1. コーディング能力が必要となるfunctionノードを多用する
[{"id":"7e681e.ebe017e4","type":"function","z":"90d22362.8b36","name":"大文字を小文字に変換","func":"msg.payload = msg.payload.toLowerCase();\nreturn msg;","outputs":1,"noerr":0,"x":460,"y":80,"wires":[["fa25f3dd.f88e3"]]},{"id":"5845e859.fb9718","type":"inject","z":"90d22362.8b36","name":"フローを開始","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":80,"wires":[["350e5cf1.4612c4"]]},{"id":"fa25f3dd.f88e3","type":"debug","z":"90d22362.8b36","name":"abcdを出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":650,"y":80,"wires":[]},{"id":"7bf67a94.878d44","type":"comment","z":"90d22362.8b36","name":"functionノードを用いて大文字を小文字に変換するフロー","info":"","x":230,"y":40,"wires":[]},{"id":"89a71556.577468","type":"inject","z":"90d22362.8b36","name":"フローを開始","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":180,"wires":[["bb6c9932.fbb1a8"]]},{"id":"fadc1016.34d27","type":"debug","z":"90d22362.8b36","name":"abcdを出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":650,"y":180,"wires":[]},{"id":"cc6068f6.bb0dc8","type":"comment","z":"90d22362.8b36","name":"changeノードと自作ノードを用いて大文字を小文字に変換するフロー","info":"","x":270,"y":140,"wires":[]},{"id":"57e47ee7.fe108","type":"lowercase","z":"90d22362.8b36","name":"大文字を小文字に変換","x":460,"y":180,"wires":[["fadc1016.34d27"]]},{"id":"350e5cf1.4612c4","type":"function","z":"90d22362.8b36","name":"AbCdを設定","func":"msg.payload = 'AbCd';\nreturn msg;","outputs":1,"noerr":0,"x":270,"y":80,"wires":[["7e681e.ebe017e4"]]},{"id":"bb6c9932.fbb1a8","type":"change","z":"90d22362.8b36","name":"AbCdを設定","rules":[{"t":"set","p":"payload","pt":"msg","to":"AbCd","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":180,"wires":[["57e47ee7.fe108"]]}]
2. changeノードで複数の変数を代入する
[{"id":"53341283.27208c","type":"template","z":"30a94c90.74b054","name":"値を代入しJSON作成","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\n \"timestamp\": \"{{payload}}\",\n \"value\": \"{{topic}}\"\n}","output":"json","x":360,"y":180,"wires":[["d0da7f3f.8dc38"]]},{"id":"83c4c1e5.1d691","type":"change","z":"30a94c90.74b054","name":"値を代入しJSON作成","rules":[{"t":"move","p":"payload","pt":"msg","to":"tmp","tot":"msg"},{"t":"set","p":"payload.timestamp","pt":"msg","to":"tmp","tot":"msg"},{"t":"set","p":"payload.value","pt":"msg","to":"topic","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":80,"wires":[["76501293.3bc43c"]]},{"id":"8d594214.31879","type":"inject","z":"30a94c90.74b054","name":"","topic":"topicValue","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":180,"wires":[["53341283.27208c"]]},{"id":"d0da7f3f.8dc38","type":"debug","z":"30a94c90.74b054","name":"JSONを出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":550,"y":180,"wires":[]},{"id":"26019d5c.1e7822","type":"inject","z":"30a94c90.74b054","name":"","topic":"topicValue","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":80,"wires":[["83c4c1e5.1d691"]]},{"id":"76501293.3bc43c","type":"debug","z":"30a94c90.74b054","name":"JSONを出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":550,"y":80,"wires":[]},{"id":"2c8f7f0.e6a3882","type":"comment","z":"30a94c90.74b054","name":"msg.payloadとmsg.topicからJSONオブジェクトを作成するフロー","info":"","x":260,"y":40,"wires":[]},{"id":"8b174c4e.73c1f","type":"comment","z":"30a94c90.74b054","name":"msg.payloadとmsg.topicからJSONオブジェクトを作成するフロー","info":"","x":260,"y":140,"wires":[]}]
3. injectノードに初期値を定義する
[{"id":"ae977e0e.62161","type":"template","z":"62e57f99.8d1d8","name":"0を設定","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"0","output":"str","x":260,"y":220,"wires":[["70513569.f1143c"]]},{"id":"70513569.f1143c","type":"rpi-gpio out","z":"62e57f99.8d1d8","name":"GPIO端子をON/OFF","pin":"3","set":"","level":"0","freq":"","out":"out","x":460,"y":240,"wires":[]},{"id":"fd18c78b.6bca08","type":"template","z":"62e57f99.8d1d8","name":"1を設定","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"1","output":"str","x":260,"y":260,"wires":[["70513569.f1143c"]]},{"id":"3c7b77df.49ff08","type":"rpi-gpio out","z":"62e57f99.8d1d8","name":"GPIO端子をON/OFF","pin":"3","set":"","level":"0","freq":"","out":"out","x":400,"y":100,"wires":[]},{"id":"19bb1f5c.c57b31","type":"template","z":"62e57f99.8d1d8","name":"0を設定","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"0","output":"str","x":400,"y":360,"wires":[["660fc5ef.d067ec"]]},{"id":"660fc5ef.d067ec","type":"rpi-gpio out","z":"62e57f99.8d1d8","name":"GPIO端子をON/OFF","pin":"3","set":"","level":"0","freq":"","out":"out","x":600,"y":380,"wires":[]},{"id":"33ee0fa.10cc2f","type":"template","z":"62e57f99.8d1d8","name":"1を設定","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"1","output":"str","x":400,"y":400,"wires":[["660fc5ef.d067ec"]]},{"id":"4be6565e.629d48","type":"switch","z":"62e57f99.8d1d8","name":"payload < 40","property":"payload","propertyType":"msg","rules":[{"t":"lt","v":"40","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":230,"y":380,"wires":[["19bb1f5c.c57b31"],["33ee0fa.10cc2f"]]},{"id":"2fab2acf.b7c4f6","type":"mqtt in","z":"62e57f99.8d1d8","name":"温度を取得","topic":"topic","qos":"2","datatype":"auto","broker":"2acb6291.9186ee","x":80,"y":380,"wires":[["4be6565e.629d48"]]},{"id":"b4bf3da2.3026f","type":"inject","z":"62e57f99.8d1d8","name":"フローを開始","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":220,"wires":[["ae977e0e.62161"]]},{"id":"e0521be8.ad7e18","type":"inject","z":"62e57f99.8d1d8","name":"フローを開始","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":260,"wires":[["fd18c78b.6bca08"]]},{"id":"21098a71.78bbe6","type":"inject","z":"62e57f99.8d1d8","name":"0を設定後、フローを開始","topic":"","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":80,"wires":[["3c7b77df.49ff08"]]},{"id":"7b813b89.28f324","type":"inject","z":"62e57f99.8d1d8","name":"1を設定後、フローを開始","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":120,"wires":[["3c7b77df.49ff08"]]},{"id":"279c1fe7.c68c2","type":"comment","z":"62e57f99.8d1d8","name":"GPIO端子のON/OFFを制御するフロー","info":"","x":170,"y":40,"wires":[]},{"id":"bd22335a.b4bb1","type":"comment","z":"62e57f99.8d1d8","name":"GPIO端子のON/OFFを制御するフロー","info":"","x":170,"y":180,"wires":[]},{"id":"181a08d8.857727","type":"comment","z":"62e57f99.8d1d8","name":"温度が閾値を超えた際、GPIO端子のON/OFFを制御するフロー","info":"","x":250,"y":320,"wires":[]},{"id":"2acb6291.9186ee","type":"mqtt-broker","z":"","name":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]
4. execノードで環境依存のコマンドを実行する
[{"id":"780ec9ac.825628","type":"exec","z":"2d22483a.83eb98","command":"hostname -i","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":270,"y":80,"wires":[["cd20895c.35faf8"],[],[]]},{"id":"1a8b45cd.85e38a","type":"hostip","z":"2d22483a.83eb98","name":"Host IP","x":260,"y":180,"wires":[["fc1f89a0.d90c18"]]},{"id":"6bd6550f.62dcac","type":"inject","z":"2d22483a.83eb98","name":"フローを開始","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":80,"wires":[["780ec9ac.825628"]]},{"id":"cd20895c.35faf8","type":"debug","z":"2d22483a.83eb98","name":"IPアドレスを出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":450,"y":80,"wires":[]},{"id":"5e5345f3.b179dc","type":"inject","z":"2d22483a.83eb98","name":"フローを開始","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":180,"wires":[["1a8b45cd.85e38a"]]},{"id":"fc1f89a0.d90c18","type":"debug","z":"2d22483a.83eb98","name":"IPアドレスを出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":430,"y":180,"wires":[]},{"id":"1b8f35a6.28b6ca","type":"comment","z":"2d22483a.83eb98","name":"IPアドレスを取得するフロー","info":"","x":140,"y":40,"wires":[]},{"id":"82cf837.e69a28","type":"comment","z":"2d22483a.83eb98","name":"IPアドレスを取得するフロー","info":"","x":140,"y":140,"wires":[]}]
5. fileノードを用いてデータを保存する
[{"id":"86074697.cba528","type":"mysql","z":"b9c94991.617d18","mydb":"","name":"MySQLを問合せ","x":540,"y":240,"wires":[["57b97dd3.49f3a4"]]},{"id":"4697c37f.fc8d1c","type":"file","z":"b9c94991.617d18","name":"ファイル書込","filename":"","appendNewline":true,"createDir":false,"overwriteFile":"false","x":500,"y":80,"wires":[[]]},{"id":"335ddc16.c37384","type":"file in","z":"b9c94991.617d18","name":"ファイル読込","filename":"","format":"utf8","chunk":false,"sendError":false,"x":320,"y":120,"wires":[["b9afd63b.a3c9e8"]]},{"id":"57b97dd3.49f3a4","type":"debug","z":"b9c94991.617d18","name":"問合せ結果","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":710,"y":240,"wires":[]},{"id":"70bbdfd3.d2062","type":"template","z":"b9c94991.617d18","name":"INSERT文を作成","field":"topic","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"insert into users(name) values('{{name}}');","output":"str","x":330,"y":220,"wires":[["86074697.cba528"]]},{"id":"19735350.0a14bd","type":"inject","z":"b9c94991.617d18","name":"データ書込を実行","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":220,"wires":[["70bbdfd3.d2062"]]},{"id":"e9bb092a.600fa8","type":"inject","z":"b9c94991.617d18","name":"データ読取を実行","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":260,"wires":[["cbeb3af8.fcfc08"]]},{"id":"cbeb3af8.fcfc08","type":"template","z":"b9c94991.617d18","name":"SELECT文を作成","field":"topic","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"SELECT * FROM users;","output":"str","x":330,"y":260,"wires":[["86074697.cba528"]]},{"id":"b9afd63b.a3c9e8","type":"debug","z":"b9c94991.617d18","name":"読取データを出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":510,"y":120,"wires":[]},{"id":"ca5fb5ca.162948","type":"template","z":"b9c94991.617d18","name":"書き込むデータ","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"This is the payload: {{payload}} !","output":"str","x":320,"y":80,"wires":[["4697c37f.fc8d1c"]]},{"id":"621c207f.24fa3","type":"inject","z":"b9c94991.617d18","name":"データ書込を実行","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":80,"wires":[["ca5fb5ca.162948"]]},{"id":"b07de0d4.84337","type":"inject","z":"b9c94991.617d18","name":"データ読取を実行","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":120,"wires":[["335ddc16.c37384"]]},{"id":"62d07d49.3c3e44","type":"comment","z":"b9c94991.617d18","name":"fileノードを用いてデータの書込/読取を行うフロー","info":"","x":210,"y":40,"wires":[]},{"id":"e7d140a3.e0eb9","type":"comment","z":"b9c94991.617d18","name":"MySQLノードを用いてデータの書込/読取を行うフロー","info":"","x":220,"y":180,"wires":[]}]
6. フローの編集が困難なsubflowを多用する
[{"id":"e9e3706e.93005","type":"subflow","name":"Subflow 2","info":"","in":[{"x":40,"y":40,"wires":[{"id":"97a863c6.f60dc"}]}],"out":[{"x":240,"y":40,"wires":[{"id":"97a863c6.f60dc","port":0}]}]},{"id":"97a863c6.f60dc","type":"template","z":"e9e3706e.93005","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"subflow2","output":"str","x":140,"y":40,"wires":[[]]},{"id":"f0b4d6e9.27f1c8","type":"subflow","name":"Subflow 1","info":"","in":[{"x":40,"y":40,"wires":[{"id":"66b1cf38.cd42f"}]}],"out":[{"x":240,"y":40,"wires":[{"id":"66b1cf38.cd42f","port":0}]}]},{"id":"66b1cf38.cd42f","type":"template","z":"f0b4d6e9.27f1c8","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"subflow1","output":"str","x":140,"y":40,"wires":[[]]},{"id":"2fddb1e9.fd897e","type":"http request","z":"ca729981.099898","name":"api1にアクセス","method":"GET","ret":"txt","url":"localhost/api1","tls":"","proxy":"","x":280,"y":180,"wires":[["34b7d724.4278e8"]]},{"id":"90336304.8d357","type":"subflow:f0b4d6e9.27f1c8","z":"ca729981.099898","name":"","x":260,"y":80,"wires":[["c11510c3.4566c"]]},{"id":"4d2ad27d.05b77c","type":"inject","z":"ca729981.099898","name":"フローを開始","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":80,"wires":[["90336304.8d357"]]},{"id":"bd872f1f.19a5a","type":"http in","z":"ca729981.099898","name":"","url":"/api1","method":"get","upload":false,"swaggerDoc":"","x":100,"y":220,"wires":[["c2198059.3187c"]]},{"id":"6b1453da.6d379c","type":"inject","z":"ca729981.099898","name":"フローを開始","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":180,"wires":[["2fddb1e9.fd897e"]]},{"id":"6977a929.f4ce78","type":"debug","z":"ca729981.099898","name":"出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":610,"y":180,"wires":[]},{"id":"228c27ca.0d61f8","type":"http response","z":"ca729981.099898","name":"","statusCode":"","headers":{},"x":390,"y":220,"wires":[]},{"id":"d4c31410.46fc08","type":"http in","z":"ca729981.099898","name":"","url":"/api2","method":"get","upload":false,"swaggerDoc":"","x":100,"y":260,"wires":[["69eba473.f6b02c"]]},{"id":"947f902e.00932","type":"http response","z":"ca729981.099898","name":"","statusCode":"","headers":{},"x":390,"y":260,"wires":[]},{"id":"34b7d724.4278e8","type":"http request","z":"ca729981.099898","name":"api2にアクセス","method":"GET","ret":"txt","url":"localhost/api2","tls":"","proxy":"","x":460,"y":180,"wires":[["6977a929.f4ce78"]]},{"id":"4f1a8ebb.a0e79","type":"debug","z":"ca729981.099898","name":"出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":530,"y":80,"wires":[]},{"id":"c2198059.3187c","type":"template","z":"ca729981.099898","name":"api1の処理","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"api1","output":"str","x":250,"y":220,"wires":[["228c27ca.0d61f8"]]},{"id":"69eba473.f6b02c","type":"template","z":"ca729981.099898","name":"api2の処理","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"api2","output":"str","x":250,"y":260,"wires":[["947f902e.00932"]]},{"id":"b06bee6a.f0209","type":"comment","z":"ca729981.099898","name":"サブフローを用いたフロー","info":"","x":130,"y":40,"wires":[]},{"id":"a733d6fb.3ba158","type":"comment","z":"ca729981.099898","name":"サブフローの代わりにREST APIを用いたフロー","info":"","x":200,"y":140,"wires":[]},{"id":"c11510c3.4566c","type":"subflow:e9e3706e.93005","z":"ca729981.099898","name":"","x":400,"y":80,"wires":[["4f1a8ebb.a0e79"]]}]
7. ノードの説明をcommentノードに記載する
[{"id":"312b4c60.4fe5e4","type":"comment","z":"445f5678.c13758","name":"↑getFullYear()を用いて西暦を取得","info":"","x":310,"y":120,"wires":[]},{"id":"8721e41.e68d918","type":"function","z":"445f5678.c13758","name":"西暦を取得","func":"msg.payload = new Date(msg.payload).getFullYear();\nreturn msg;","outputs":1,"noerr":0,"x":250,"y":80,"wires":[["57f61a12.99ede4"]]},{"id":"187627a.db90cd8","type":"inject","z":"445f5678.c13758","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":100,"y":80,"wires":[["8721e41.e68d918"]]},{"id":"4cb77667.82f3b8","type":"debug","z":"445f5678.c13758","name":"平成の年を出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":620,"y":80,"wires":[]},{"id":"4cf9c44b.85d2ac","type":"comment","z":"445f5678.c13758","name":"↓西暦から1988を引くことで平成の年を算出","info":"","x":530,"y":40,"wires":[]},{"id":"bd6506d8.f254f8","type":"comment","z":"445f5678.c13758","name":"平成の年を出力するフロー","info":"","x":130,"y":40,"wires":[]},{"id":"c5b6dbef.b2da88","type":"function","z":"445f5678.c13758","name":"西暦を取得","func":"msg.payload = new Date(msg.payload).getFullYear();\nreturn msg;","outputs":1,"noerr":0,"x":250,"y":220,"wires":[["9f9dd077.df2b4"]],"info":"getFullYear()を用いて西暦を取得"},{"id":"fe175309.d2ccb","type":"inject","z":"445f5678.c13758","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":100,"y":220,"wires":[["c5b6dbef.b2da88"]]},{"id":"806e6782.e138e8","type":"debug","z":"445f5678.c13758","name":"平成の年を出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":620,"y":220,"wires":[]},{"id":"df36af43.81b2c","type":"comment","z":"445f5678.c13758","name":"平成の年を出力するフロー","info":"","x":130,"y":180,"wires":[]},{"id":"57f61a12.99ede4","type":"change","z":"445f5678.c13758","name":"西暦を平成に変換","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload-1988","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":80,"wires":[["4cb77667.82f3b8"]]},{"id":"9f9dd077.df2b4","type":"change","z":"445f5678.c13758","name":"西暦を平成に変換","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload-1988","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":220,"wires":[["806e6782.e138e8"]],"info":"西暦から1988を引くことで平成の年を算出"},{"id":"e68bac15.21894","type":"http in","z":"445f5678.c13758","name":"","url":"/api","method":"get","upload":false,"swaggerDoc":"16c1b9de.ece046","x":80,"y":320,"wires":[["8d50fdee.4ad8c"]]},{"id":"9d3778e6.c2aeb8","type":"http response","z":"445f5678.c13758","name":"","statusCode":"","headers":{},"x":350,"y":320,"wires":[]},{"id":"8d50fdee.4ad8c","type":"template","z":"445f5678.c13758","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"This is the payload: {{payload}} !","output":"str","x":220,"y":320,"wires":[["9d3778e6.c2aeb8"]]},{"id":"297757e7.a1a728","type":"comment","z":"445f5678.c13758","name":"REST APIのフロー","info":"","x":110,"y":280,"wires":[]},{"id":"16c1b9de.ece046","type":"swagger-doc","z":"","summary":"REST APIのサマリ","description":"REST APIの詳細","tags":"","consumes":"","produces":"","parameters":[],"responses":{},"deprecated":false}]
8. フロー内に環境固有の変数を定義する
[{"id":"7e55c77a.175908","type":"inject","z":"4e944cf6.7632f4","name":"フローを開始","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":340,"wires":[["b2263a5d.e1e3a8"]]},{"id":"b2263a5d.e1e3a8","type":"change","z":"4e944cf6.7632f4","name":"payload = $foo","rules":[{"t":"set","p":"payload","pt":"msg","to":"foo","tot":"env"}],"action":"","property":"","from":"","to":"","reg":false,"x":280,"y":340,"wires":[["3411f322.7701dc"]]},{"id":"3411f322.7701dc","type":"debug","z":"4e944cf6.7632f4","name":"fooValueを出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":460,"y":340,"wires":[]},{"id":"b2b60731.d404f8","type":"inject","z":"4e944cf6.7632f4","name":"フローを開始","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":80,"wires":[["d555ec27.3eaf4"]]},{"id":"d555ec27.3eaf4","type":"change","z":"4e944cf6.7632f4","name":"global.foo = 'fooValue'","rules":[{"t":"set","p":"foo","pt":"global","to":"fooValue","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":300,"y":80,"wires":[[]]},{"id":"2424e39e.e7285c","type":"inject","z":"4e944cf6.7632f4","name":"フローを開始","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":180,"wires":[["144b4e9b.8bebb1"]]},{"id":"144b4e9b.8bebb1","type":"change","z":"4e944cf6.7632f4","name":"payload = global.foo","rules":[{"t":"set","p":"payload","pt":"msg","to":"foo","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":300,"y":180,"wires":[["4c1c8d37.182f34"]]},{"id":"4c1c8d37.182f34","type":"debug","z":"4e944cf6.7632f4","name":"fooValueを出力","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":500,"y":180,"wires":[]},{"id":"769267.771c1d98","type":"comment","z":"4e944cf6.7632f4","name":"(2) 環境変数から値を取得","info":"","x":130,"y":300,"wires":[]},{"id":"33eb4a97.89f826","type":"comment","z":"4e944cf6.7632f4","name":"(2) グローバルコンテキストから値を取得","info":"","x":180,"y":140,"wires":[]},{"id":"fd2ff6df.f92fa8","type":"comment","z":"4e944cf6.7632f4","name":"(1) Node-RED起動前に環境変数へ値を設定(export foo=fooValueを実行)","info":"","x":270,"y":260,"wires":[]},{"id":"373c73d6.25360c","type":"comment","z":"4e944cf6.7632f4","name":"(1) グローバルコンテキストに値を設定","info":"","x":170,"y":40,"wires":[]}]