- 投稿日:2020-02-21T14:25:29+09:00
AzureServiceBusを色々と試してみた
はじめに
アーキテクチャに弾力性を持たせるためにはキューの存在は必須なわけですがAzureにもAWS SQSと同じようなサービスが存在します。それがAzureServiceBusです。
今回も仕事で使うためにAzureServiceBusについて調べてみました。
似たものとしてBlobStorageQueueがあるのですが、そのあたりの違いについては公式のドキュメントをご覧ください。ServiceBusの各プランの機能と料金についてはこちらをご覧ください。
本記事で作ったソースはこちらにあります。
準備
サンプルプログラムを試すためのServiceBusとキューはAzurePortalから予め作っておいてください。
キューの名前は何でも構いませんがサンプルプログラム内ではqueueという名前で作成されたものを使っています。また、キューにアクセスするためのポリシーの作成と接続文字列はプログラム内で使いますのでメモしておいてください。
私は「管理」「送信」「リッスン」それぞれのポリシーを作っておきました。簡単なメッセージの送信
簡単なプログラムを使ってメッセージを送ってみます。
send-message.jsconst {ServiceBusClient} = require("@azure/service-bus"); const connectionString = process.env.SERVICE_BUS_CONNECTION_STRING; const queueName = process.env.QUEUE_NAME; const main = async () => { const sbClient = ServiceBusClient.createFromConnectionString(connectionString); const queueClient = sbClient.createQueueClient(queueName); const sender = queueClient.createSender(); const message = { body: 'Hello. Service Bus', label: 'test', userProperties: { testPropertyName: 'property value' } }; console.log('send message start'); try { await sender.send(message); await queueClient.close(); console.log('send message end'); } finally { await sbClient.close(); } }; main().catch(err => { console.log(err); });実行してみましょう
set SERVICE_BUS_CONNECTION_STRING=<ConnectionString> set QUEUE_NAME=<QueueName> C:\Users\uzres\products\azure-servicebus-transaction>node send-message.js send message start send message endServiceBus Explorerで確認
こちらからServiceBus Explorerをダウンロードします
File → Connectを選択し、ConnectionStringを入力します。
ConnectionStringのポリシーは「管理」である必要があります。左メニューのqueueを選択し、Message→OKをクリックするとメッセージを見ることができます。
カスタムプロパティもみることができますね。
メッセージの受信
今度はメッセージを受信してみましょう。
receive-message.jsconst {ServiceBusClient, ReceiveMode} = require("@azure/service-bus"); const connectionString = process.env.SERVICE_BUS_CONNECTION_STRING; const queueName = process.env.QUEUE_NAME; const main = async () => { const sbClient = ServiceBusClient.createFromConnectionString(connectionString); const queueClient = sbClient.createQueueClient(queueName); const receiver = queueClient.createReceiver(ReceiveMode.receiveAndDelete) console.log('receive message start'); try { const messages = await receiver.receiveMessages(5); console.log(messages.map(message => message.body)); await queueClient.close(); console.log('receive message end'); } finally { await sbClient.close(); } }; main().catch(err => { console.log(err); });実行するためには、「リッスン」権限が必要です。
C:\Users\uzres\products\azure-servicebus-transaction>node receive-message.js receive message start [ 'Hello. Service Bus' ] receive message end実行してみるとわかりますが結構待たされます。
これはメッセージを受信するときに60秒間待つからです。
短くするにはreceveMessagesの2番目の引数に待つ秒数を指定します。const messages = await receiver.receiveMessages(5, 1);Azure Functionで受信
先ほどの例ではとあるタイミングでプログラムを実行しQueueからメッセージを受信しましたが、Queueに入ったタイミングでFunctionを動かすことができます。
これでQueueをポーリングする手間がなくなりますね。素晴らしい・・・。関数の引数であるmySbMsgにメッセージが入ってきます。
module.exports = async function(context, mySbMsg) { context.log('JavaScript ServiceBus queue trigger function processed message', mySbMsg); context.log('EnqueuedTimeUtc =', context.bindingData.enqueuedTimeUtc); context.log('DeliveryCount =', context.bindingData.deliveryCount); context.log('MessageId =', context.bindingData.messageId); };functions.json{ "bindings": [ { "name": "mySbMsg", "type": "serviceBusTrigger", "direction": "in", "queueName": "queue", "connection": "" } ] }Functionを設定するときの注意点
- connectionのところを空にした場合は、アプリケーション設定の追加で、AzureWebJobsServiceBusという名前でQueueへの接続文字列を設定しておきます。
- function.jsonにqueueNameを指定しているので接続文字列からはEntityPath=queueを削除しておかないとエラーになります。
- このへんの設定はトリガーとバインド ServiceBusを確認してください。
Azure Functionで送信
Bindingという機能を使うとServiceBusにメッセージを送ることができます。
先ほど作ったfuctionで受け取ったメッセージをoutputqueueというQueueに投入してみたいと思います。まずはfunction.jsonにdirectionがoutの要素を追記します。
function.json{ "bindings": [ { "name": "mySbMsg", "type": "serviceBusTrigger", "direction": "in", "queueName": "queue", "connection": "" }, { "name": "output", "type": "serviceBus", "queueName": "outputqueue", "connection": "OutputConnection", "direction": "out" } ] }プログラムも少し変えましょう。受信したメッセージにoutput を付けて、outputqueueにメッセージを入れてみます。
bindingsの後の文字列はfunction.jsonのnameで指定した文字列になります。index.jsmodule.exports = async function(context, mySbMsg) { context.log('JavaScript ServiceBus queue trigger function processed message', mySbMsg); context.log('EnqueuedTimeUtc =', context.bindingData.enqueuedTimeUtc); context.log('DeliveryCount =', context.bindingData.deliveryCount); context.log('MessageId =', context.bindingData.messageId); const outputMessage = 'output' + mySbMsg; context.bindings.output=outputMessage; };今回はfunction.jsonのconnectionにOutputConnectionを指定したので、Functionの環境変数にっはAzureWebJobsOutputConnectionという名前で接続文字列を指定します。
実行してoutputqueueの中身を見るとメッセージが配信されていることがわかりますね。
PeekLockとReceiveAndDelete
AzureFunctionでメッセージを受信したとき既定の動作はPeekLockです。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-service-bus?tabs=csharp#trigger---peeklock-behavior処理が正常に終了すれば、FunctionからCompleteを呼び出しQueueからメッセージが削除されるようになっています。ReceiveAndDeleteに設定することもできますが、これは受信した瞬間にメッセージが削除されるモードです。これはエラー発生時にメッセージがロストしてしまう可能性があります。
DeadLetter
MaxDeliveryCountに指定されている回数分処理が失敗することや、TimeToLiveの超過が起こるとDeadLetterQueue(DLQ)にメッセージが送信されます。
これはアプリケーションで明示的に指定することも可能です。DLQの中身は自動的にクリーンアップされないため、DLQに移ったメッセージはログに出力するなどして確実に処理する必要があります。
早速試してみましょう。
DeadLetterQueueにメッセージを移す簡単な方法は、先ほど作ったFunctionの出力先であるoutputqueueを削除したあとにメッセージを送りましょう。
少し経ってFunctionのログを見ると10回失敗していることがわかります。
ExplorerでDeadLetterQueueに移っていることも確認できます。
DLQのメッセージを取得してみたいと思います。
receive-DLQ-message.jsconst {ServiceBusClient, ReceiveMode} = require("@azure/service-bus"); const connectionString = process.env.SERVICE_BUS_CONNECTION_STRING; const queueName = process.env.QUEUE_NAME + "/$DeadLetterQueue"; const main = async () => { const sbClient = ServiceBusClient.createFromConnectionString(connectionString); const queueClient = sbClient.createQueueClient(queueName); const receiver = queueClient.createReceiver(ReceiveMode.peekLock) console.log('receive DLQ message start'); try { const messages = await receiver.receiveMessages(5, 1); for (let i = 0; i < messages.length; i++) { const message = messages[i]; console.log(message.body) await message.complete(); } await queueClient.close(); console.log('receive DLQ message end'); } finally { await sbClient.close(); } }; main().catch(err => { console.log(err); });DLQはQueueNameの後ろに/$DeadLetterQueueを付けただけです。
今回は、PeekLockで取得するのでメッセージ取得後complete()を呼び出しDLQからメッセージを削除しています。おわりに
シンプルなメッセージのやり取りからFunctionを使った送受信、DLQまで試してみましたがいかがだったでしょうか。割と簡単に処理できることがお分かりいただけたかなと思います。
- 投稿日:2020-02-21T02:56:21+09:00
NW.jsでHTMLをWindowsデスクトップアプリにする
NW.jsとは?
ざっくりとした理解としては、Chromiumエンジン使ってhtmlを表示するデスクトップアプリを作れるフレームワークです。
Electronとの違いなど、詳しい記事は、こちら
NW.jsでデスクトップアプリの夢を見る!1分でWindowsデスクトップアプリをつくる
いろいろ複雑なことやっている記事が多いですが、本当に簡単なので、1分でアプリ起動できます!
手順
公式サイト( https://nwjs.io/ )から、左側の「NORMAL」をダウンロードします。
zip展開し、src というフォルダを作成。そこに起動するHTMLファイル一式を格納します。
package.json を作成して同フォルダに入れます。最低限の設定は以下。
package.json{ "name":"test.application", "version":"1.0.0", "main":"src/index.html" }以上。超簡単です。
NW.js Tips
package.json
ウインドウの最低サイズとアイコンの指定
package.json{ "name":"test.application", "version":"1.0.0", "main":"src/index.html" "window":{ "min_width":1200, "min_height":760, "icon":"assets/icon32.png" } }JavaScript
いろいろな情報の寄せ集めですが、動いたコードを紹介。
exeをキックする
var spawn = require('child_process').spawn, child = spawn('C:\\windows\\notepad.exe', ["C:/Windows/System32/Drivers/etc/hosts"]); child.stdout.on('data', function (data) { console.log('stdout: ' + data); }); child.stderr.on('data', function (data) { console.log('stderr: ' + data); }); child.on('close', function (code) { console.log('child process exited with code ' + code); });エクスプローラーでフォルダーを開く
require('child_process').exec('start "" "c:\\windows"');htmlと同フォルダーにあるファイルを関連付けられたアプリケーションで開く
var path = require('path'); var startDir = path.dirname(process.execPath); var gui = window.require( 'nw.gui' ); gui.Shell.openItem( startDir+'\\src\\test.pdf' );標準のブラウザでURLを開く
var gui = window.require( 'nw.gui' ); gui.Shell.openExternal( 'https://google.com/' );
- 投稿日:2020-02-21T01:11:40+09:00
SORACOM GPS マルチユニット のデータを kintone に保存する
概要
発表と同時に注文していた「GPS マルチユニット SORACOM Edition スターターキット」が届いたので早速 kintone へのデータ保存を試してみました。
SORACOMエンジニアブログによると、GPSマルチユニットは Unified Endpoint を経由して、Harbest Data や、Beame、Funnel、Funk で利用できます。
SORACOMエンジニアブログ(4 つのセンサーと省電力通信 LTE-M 内蔵「GPS マルチユニット SORACOM Edition」発売開始!)より引用そこで、今回は以下のようにGPSマルチユニットから、Unified Endpoint、SORACOM Funk を経由して、AWS Lambda をキックして kintone にレコードを追加する方法を試してみました。
GPS マルチユニット->Unified Endpoint->SORACOM Funk->AWS Lambda->kintoneGPS マルチユニットの準備
「GPS マルチユニット SORACOM Edition スターターキット」には、GPS マルチユニット SORACOM Edition、SORACOM 特定地域向け IoT SIMカード (plan-D サイズ:ナノ / データ通信のみ) 、充電/給電用マイクロUSBケーブル 1本が同梱されています。
すでにSORACOMのサイトに一通りのドキュメントがあり、それを参考に本体にSIMを入れた後に前面にあるボタンを1秒以上押すか、同梱されているUSBケーブルで充電すると本体が起動します。GPS マルチユニット SORACOM Edition 製品仕様
https://dev.soracom.io/jp/gps_multiunit/specification/
GPS マルチユニット SORACOM Edition デバイスの使い方
https://dev.soracom.io/jp/gps_multiunit/how-to-use/kintone の準備
今回はGPS マルチユニットのデータをkintoneに保存しますので、以下のドキュメントを参考にGPSマルチユニットのデータを全て保管するkintoneのアプリを用意します。
GPS マルチユニット SORACOM Edition 機能の説明
https://dev.soracom.io/jp/gps_multiunit/how-it-works/kintoneに以下のようなフォームを持つアプリを追加します。
フィールド名 タイプ フィードコート・要素ID 日時 日時 datetime 緯度(Y) 数値(少数9桁) lat 経度(X) 数値(少数9桁) lon 電池残量 数値(少数0桁) bat アンテナ感度 数値(少数0桁) rs 温度 数値(少数1桁) temp 湿度 数値(少数1桁) humi 加速度X 数値(少数1桁) x 加速度Y 数値(少数1桁) y 加速度Z 数値(少数1桁) z タイプ 数値(少数0桁) type あとアプリの設定画面で、レコード追加権限を持つAPIトークンを追加、控えておきます。
完成したアプリは以下です。
AWS アカウントの追加
次に、GPS マルチユニットからのデータを受信処理する AWS Lambda をキック(起動)するための、AWS アカウントを追加します。AWS コンソール IAM のユーザ画面で、ユーザを追加します。
ユーザの詳細設定で「ユーザ名」を入力し、「アクセスの種類」にプログラムによるアクセスを選択し、次のステップに移ります。
アクセス許可の設定で「既存ポリシーを直接アタッチ」を選択し、「ポリシーのフィルタ」に AWSLambdaFullAcess を選択して、次のステップに進みます。
本来は先に AWS Lambda 側の設定を行った後に以下のような「ポリシーの作成」を行った方が良いのですが、今回は試験的に試すだけですのでAWSLambdaFullAcessにしています。
ユーザの追加に成功したら、アクセスキーIDと、シークレットアクセスキーを控えておきます。
以上でアカウントの追加は完了です。AWS Lambda の準備
続いて、GPS マルチユニットからのデータを受信処理する AWS Lambda の設定を行います。「関数の作成」で「関数名」を入力し、ランタイムが Node.js 12.x、その他の項目は変更せず関数を作成します。(写真はソースコード他の設定を終えた状態。)
画面右上の ARN(写真はグレーで消している)は、GPS マルチユニットの通信設定に必要なので控えておきます。
作成した関数の基本設定の「メモリ(MB)」を256MBに、「タイムアウト」を30秒に設定します。(メモリは初期値の128MBでも良いですが、タイムアウト初期値3秒は kintone API のレスポンスに遅延が発生する場合も想定して10秒以上に変更した方が良いでしょう。)
作成した関数の非同期呼び出しの「イベントの最大有効時間」は1分程度に、「再試行」は0で行なわないようにします。(今回は、通信のタイムアウトなどで処理が中断した場合は再実行しない設定にしています。)
GPS マルチユニット SORACOM Edition 機能の説明で紹介しているGPS マルチユニットから送信されるbase64をデコードしたフォーマットを参考に、関数のテストを追加します。
{ "lat":35.000000, "lon":139.000000, "bat":3, "rs":4, "temp":16.0, "humi":32.4, "x":0.0, "y":-64.0, "z":-960.0, "type":0 }最初うっかりGPS マルチユニット SORACOM Edition 機能の説明で紹介している base64 ペイロードデータを処理するプログラムを作成し、base64 ペイロードデータテストを設定していましたが、実際は SORACOM Funkから Lambda にデータが渡される時にはbase64デコードしているため上記の設定になります。
テスト実行後、kintone にレコードが追加されているのが確認できます。
AWS Lambda のプログラム
AWS Lambda のプログラムは、以下の node.js のパッケージ request-promise と moment を追加する必要があります。
https://www.npmjs.com/package/request-promise
https://www.npmjs.com/package/momentLambda で node.js のパッケージを使う説明は割愛しますが、以下などを参考に設定すると良いでしょう。
Lambda の Node.js でもっといろんなパッケージを使いたいとき
https://tech-lab.sios.jp/archives/9017
AWS Lambda Layersでnode_modulesを使う
https://xp-cloud.jp/blog/2019/01/12/4630/Lambda ではGPS マルチユニットからのデータは Json 形式で簡単に受け取れるため、実装は kintone API の Json フォーマットに変換して POST するだけです。
index.js'use strict'; const request = require('request-promise'); const moment = require("moment"); const Domain = "cybozu.com"; const Subdomain = "SUBDOMAIN"; const Path = "/k/v1/record.json"; const Protocol = "https://"; let Url = Protocol + Subdomain + '.' + Domain + Path; const AppId = "KINTONE_APP_ID"; const Token = "KINTONE_TOKEN"; exports.handler = async function(event, context, callback) { console.log('Function Start.'); var dateTime = moment().format("YYYY-MM-DDTHH:mm:ssZ"); var json = { "dateTime" : { "value" : dateTime }, "lat" : { "value" : event.lat }, "lon" : { "value" : event.lon }, "bat" : { "value" : event.bat }, "rs" : { "value" : event.rs }, "temp" : { "value" : event.temp }, "humi" : { "value" : event.humi }, "x" : { "value" : event.x }, "y" : { "value" : event.y }, "z" : { "value" : event.z }, "type" : { "value" : event.type }, }; await PostKintoneRecode(request, Url, AppId, Token, json); console.log('Function Stop.'); }; // kintone のデータを追加する async function PostKintoneRecode(request, url, appId, token, json) { try { var options = { url: url, method: 'POST', headers: { 'Content-type': 'application/json', 'X-Cybozu-API-Token': token }, json: { app : appId, record: json }, }; await request(options); return true; } catch (err) { console.error(JSON.stringify(err)); return false; } }GPS マルチユニットの設定
最後に、いよいよGPS マルチユニットの設定に入ります。(翌日気づいたのですが、すでにSORACOMさんの丁寧なドキュメントが公開されていましたので、以下を参考にすると良いでしょう。)
GPS マルチユニット SORACOM Editionを使用して、定期的に位置情報を送信する
https://dev.soracom.io/jp/start/gps_multiunit_location/SIMの有効化
SORACOM のコンソールにログインして、以下の画面に遷移してGPS マルチユニットに付属していた SIM(plan-D ナノサイズ)を有効化します。
Menu->発注->注文履歴->「受け取り確認」ボタン
SIMグループの追加と設定
SIM 有効化後、SIM グループの画面でグループを追加します。
追加したグループの Unified Endpoint を設定します。今回は SORACOM Funk を利用しますので「フォーマット」に SORACOM Funk を選択します。
追加したグループの SORACOM Funk を以下のように設定します。「サービス」は AWS Lambda を選択、「送信データ作成」は JSON を選択、「認証情報」を追加(追加方法は後述)、「関数のARN」は先に控えておいた Lambda の ARN を入力します。
「認証情報」の追加は、「認証情報」の項目にマウスを移動すると以下が表示されるので、クリックすると追加画面が表示されます。
「認証情報」追加画面の「認証情報ID」と「概要」を入力し、「識別」はAWS 認証情報を選択、「AWS Access Key ID」と「AWS Secret Access Key」はAWS アカウントの追加で保管したアクセスキーIDとシークレットアクセスキーを入力し、登録します。
以上のグループの設定が完了したら SIM 管理画面に戻り、先に有効化した SIM にこのグループを紐づけます。
GPS マルチユニット本体の設定
SIM と SIMグ ループの登録・紐づけ後、以下の画面に遷移して GPS マルチユニット本体を設定します。
Menu->ガジェット管理->GPS マルチユニット
GPS マルチユニットの画面から、新規デバイス設定を行います。
使用するSIMを選択する画面で先に登録したSIMを選択し「次へ」遷移、設定を保存するグループを選択する画面で既存のグループを利用を選択し先ほど作成したグループを選択して「設定を編集」画面に遷移します。GPS マルチユニットの設定画面で、送信内容を全てチェックします。
送信先の「SORACOM Harvest(Lagoon)」をチェックします。(SORACOM Funk は最初からチェックされて、こちらでは変更できません。)
送信モードは「定期送信 - 手動モード」を選択します。
定期送信 - 手動モード詳細設定は毎日10分毎に設定します。(最初のテスト時は最短の1分毎に設定していました。)
加速度割り込みはお好みで良いでしょう。
以上の設定を完了したら「保存」します。GPS マルチユニットからデータ送信の確認
GPS マルチユニット本体の設定が完了した後、「データを確認」で GPS マルチユニット本体から送られてきたデータを確認できます。
加速度Z の値が極端なため自動ではちょっと見ずらいですが、データが取得できていることが確認できます。
結果
kintone アプリと AWS Lambda、GPS マルチユニットの全ての設定を終えると、フィールドにGPS マルチユニットの値がセットされたkintoneアプリのレコードが追加されます。(2020/02/21 自宅の地図は面白くないので外出時に差し替え。)
折角 GPS 情報が保管されていますので、住所/緯度経度変換プラグインで地図を表示させてみました。(2020/02/21 こちらの地図も差し替え。)
住所/緯度経度変換プラグイン
https://www.tis2010.jp/geocoding/これで以前からやってみたいと思っていた kintone で移動環境計測のトレースができるので、2020/02/21朝に車の移動で早速試してみました。
その結果が以下です。
う~ん、車の移動ではデータが取得できない場合が多いようですね。週末はラーメン博があるので、食べ歩きで試してみるつもりです。参考
「GPS マルチユニット SORACOM Edition スターターキット」の出荷を開始いたしました!
https://blog.soracom.jp/blog/2020/02/18/shipping-gps-multiunit-soracom-edition/
GPSマルチユニットSORACOM Edition ユーザーガイド
https://dev.soracom.io/jp/gps_multiunit/what-is-gps_multiunit/
SORACOM Funk を利用して AWS Lambda を呼び出し Slack へ通知する
https://dev.soracom.io/jp/start/funk_aws_lambda/
SORACOM LTE-M Button を押したログを kintone に保管
https://qiita.com/yukataoka/items/9f9daac2dc05194bbc63
- 投稿日:2020-02-21T00:10:30+09:00
huskyを使ってgit hookを楽に扱う
前提
- Node.js限定です
- prettier, eslintは導入済みとする
gitコマンドを使わないと走らないので注意
SorceTreeでcommitしたら動きましたやること
pre-commitで
prettier --write
,eslint --fix
,jest
を走らせる。方法
huskyを入れてpackage.jsonにちょろっと書いて終わり。便利。
huskyとlint-stagedをインストール
~$ npm i -D husky lint-stagedpackage.json編集
nameやversionと同階層に以下を追加。対象コードが格納されてるディレクトリは環境に合わせて修正。
"husky": { "hooks": { "pre-commit": "lint-staged; jest" } }, "lint-staged": { "*.{js,jsx}": [ "prettier --write './src/**/*.js'", "prettier --write './__tests__/**/*.js'", "eslint --fix './src/**/*.js'", "eslint --fix './__tests__/**/*.js'" ] }おわり
後は適当に編集して
git add -A
してgit commit
すればlint-stagedに書いた内容が実行されてからjestが実行されます。
sh書かなくても良いのは楽ですね。ReactやVue.js環境でももちろん動きます。
VSCode環境ならprettierもeslintもAutoSaveで走らせればあまり必要はない気もしますが。