Ponz Dev Log

ゆるくてマイペースな開発日記

Mongooseの処理が非同期であるのを忘れてはいけない(戒め)

Node.jsのMongoDBのORMといえば、Mongoose。APIドキュメントをよく読んで使用されている方ならお分かりかと思いますが、よく使う.save(ドキュメント保存)や.find (ドキュメント検索)といった処理は非同期で実行されます。
これ、意識してないと処理はあってるのに値がundifinedになってハマる(実際ハマった)ので、非同期で処理されるという前提を頭に置こう(戒め)というメモです。

一応MongooseのPromiseのページにしれっと以下のように書いてあります。

Mongoose Promises v5.0.0-rc1

Built-in Promises
Mongoose async operations, like .save() and queries, return ES6 promises. This means that you can do things like MyModel.findOne({}).then() and await MyModel.findOne({}).exec() (if you're using async/await.
Queries are not promises
Mongoose queries are not promises. However, they do have a .then() function for yield and async/await. If you need a fully-fledged promise, use the .exec() function.

日本語で要約すると、以下のようになるかと。

  • .save()や query(find() / findOne() / etc...) といった 非同期の処理 は、ES6のPromiseオブジェクトを返します。

    • これらのメソッドは .then()を続けられる。
    • async / await 句をつけて実行できる。
  • ただし、queryは厳密にはPromiseではない。

    • .then(), async / await 句を使える。
    • 厳密にPromiseオブジェクトを使いたい場合は .exec()を使おう。

非同期であることはもちろん、返却されるのがPromiseオブジェクト(的なもの)であることもポイントです。 素直に配列が返却されると思いきや、オブジェクトが返ってくるので軽くビビります。

以下のようなコードが非同期で処理されるOK / NGパターンかな。

[NGの例]

 /**
  * findRcd(filterKeys)
  * @description 引数に渡された値・キーでレコードを検索し、検索結果を返却。
  * @param {Object} filterKeys - 更新するレコードの検索キー
  * @return {Array} resultSet - 検索結果
  */
findRcd(filterKeys) {
    knmKanr.find(filterKeys, (err, awesomeRecords) => {
 
    log.debug(`FilterKeys: ${JSON.stringify(filterKeys)}`);

    // レコード検索失敗
    if (err) {
        throw new Error("Find Docs Error");  // エラーを投げて処理を中断
    }

    // レコード検索成功
    log.debug(`AwesomeRecords: ${awesomeRecords}, Size: ${awesomeRecords.length}`);

    return awesomeRecord;    // ここで結果の配列が返却される
});

const searchResultSet = findRcd({ flg : '1' });    // 非同期で処理完了前に、変数に値が格納される
console.log(JSON.stringify(searchResultSet));    // 処理が未解決のため、undefined になる

[OKの例]

  /**
   * findRcd(filterKeys)
   * @description 引数に渡された値・キーでレコードを検索し、検索結果を返却。
   * @param {Object} filterKeys - 更新するレコードの検索キー
   * @return {Promise} resultSet - 検索結果
   */
  async findRcd(filterKeys) {
    const resultSet = await knmKanr.find(filterKeys, (err, awesomeRecords) => {
 
     console.log(`FilterKeys: ${JSON.stringify(filterKeys)}`);

        // レコード検索失敗
        if (err) {
            throw new Error("Find Docs Error");  // エラーを投げて処理を中断
        }

        // レコード検索成功
        console.log(`AwesomeRecords: ${awesomeRecords}, Size: ${awesomeRecords.length}`);

    });

    console.log(`resultSet: ${resultSet}`);    
    return resultSet;    // ここでPromiseオブジェクトが返却される
  }
}

findRcd(query)
    .then((searchResultSet) => {
        console.log(`searchResultSet: ${JSON.stringify(searchResultSet)}`);    // {...} 非同期処理が解決されてから値がセットされる
    }

こういう時こそ、async / await が効果的なんだってわかりますね。 改めて、Mongooseの処理が非同期であるのを忘れてはいけないです(戒め)。

API Blueprintで爽やかなAPI仕様書を作る

ローカルで動かそうがDockerで動かそうが、APIを持つアプリを作るとなると自然と一覧が欲しくなるもの。 できれば型や仕様書のフォーマットを... ブラウザで見れる&修正後はDiffが取れる形で欲しいですね。
(自分の関わってたプロジェクトだとExcelにペタペタ定義を書くor貼る仕様書でした。古いとこだと一太郎で作ってた。一太郎2018ってあるんだぜ)

[一太郎] ... フォントや段組機能は好きです www.justsystems.com

そこでAPI仕様書を簡単に作ろうと思った時に当たった選択肢が、Swagger or API Blueprintでした。 今回はAPI Blueprint触った感じを書きます。

[API Blueprint] API Blueprint | API Blueprint

[Swagger] ... こっちの方が The World's Most Popular らしい swagger.io

完成図

一聞は一見にしかず。下図みたいに エンドポイントごとに作成できます。 GETは青色、POSTは緑色とHTTPメソッドごとに色別になっているのはSwaggerと同じ。また、APIもグルーピングができるようです。

f:id:accelerk:20180106183702p:plain

基本

マークダウンで書きます。SwaggerはJSON or YAMLで書くので、API Blueprintはマークダウンに慣れている人にはぴったりです。 ただ、マークダウンといっても作成するファイル形式は .apib だったり、段落にも意味があったり、特定のブロックは3tab or 12こスペース空けるといったコード規約があります。ここはクセがありますが、1時間もあれば慣れます。

流れとしては、だいたい4つに分けられます。

  1. マークダウンで 仕様書を書く
  2. できたファイルは .apib で保存
  3. .apibファイルを 変換用のツールでHTMLにする
  4. HTMLをお好きなブラウザで見る

HTML変換ツールも言語・フレームワークによって色々用意されているようですね。 例えば、Aglio: Node.js / Laravel Blueprint Docs: PHP(Laravel) / django-apiblueprint-view: Python(Django) / Snowboard: Go といった具合です。 API Blueprint Tools | API Blueprint

また、書いたマークダウンを元にAPIのモックサーバーを動かせるんですが、MacOS10.12以降では動かないみたいですね。。。
→ issuesに上がってました。

github.com

書けること

だいたい以下のようなことをかけます。

  • APIURI
  • リクエス
    • HTTPステータス
    • Header項目
    • リクエストパラメータ(型 / 必須)
  • レスポンス
    • HTTPステータス
    • Header項目
    • レスポンスデータ(型 / 必須)

HTML生成時にリクエストパラメータやレスポンスデータのスキーマを必須・オプション、データ型含めてまとめて表示してくれるのは嬉しい。

面倒なポイント

私は幸いにもマークダウンは普段から書いてる & Node.jsの例は豊富にあったので書くこと自体は苦はないですが、 aglioでの面倒な点として.apib 1ファイルしか変換できないこと がネックでした。1ファイルじゃなくて、分割して書いたものを1つにまとめたいじゃないですか。

少し前の記事ですが、他の方も複数ファイルあるものを1つの.apibファイルにまとめて変換をかけているような手段をとっているようです。

dev.classmethod.jp

というわけで、シェルスクリプト書いて分割した.mdファイルを1つの.apibファイルにまとめて変換しました。 → Gistで公開してます。(マークダウンはソース貼り付けるとGist上で読みやすく変換してくれるおかげでソースコード見れなくなってますが笑)

gist.github.com

作成した仕様書はGithubPagesやローカル、社内のサーバーに置くなりすれば立派な仕様書ができそう。

MetabaseがRedashの苦労を吹き飛ばすくらい熱い

昨年末からデータ可視化ツールを諸々触る中で Redash おもろいわ〜ってなってたんですが、QiitaでMetabaseの記事を目にしてしまったので使わざるを得なかった。投稿者様ありがとうございます!!
面白さに勢いで書いてしまったので、拙い点はご容赦ください。

qiita.com

結論から言えば、MetabaseはRedashなんかより遥かに使いやすい!!(あくまで可視化系ツールを最初に触る人としてはという枕詞つき)
Docker使ってる身としては、docker composeでシコシコとセットアップしなくても、docker run 一発で起動できるのは楽で助かる。もちろん、docker compose は様々なコンテナをまとめてセットアップする点で楽なのですが、1ツール1コマンドで起動するのに比べたら手間がかかりますしね。

[今回紹介するMetabase] www.metabase.com

[苦労した方のRedash] redash.io

何がすごいの??

MetabaseとRedashで比較してみます。それぞれ数時間しか触ってないですが、雰囲気だけでも伝わればと。

セットアップが楽

公式によると、セットアップの選択肢は結構あるようです。

  • Docker ... コマンド一発で起動する
  • AWS ... Elastic Beastalkで動かす
  • Heroku ... ボタンをポチポチするだけ
  • OSの上に直接のっける (Macでやる方法が最初に紹介されてますが、まだ深くまでは見れてない)
  • JARファイルをダウンロードして、java -jar metabase.jar

私はDockerの勉強がてらDockerのイメージから起動させましたが、JARファイルでやるのも簡単そう!

Dockerだと以下コマンドを叩いて、http://localhost:3000にアクセスすればOKです。

$ docker run -d -p 3000:3000 --name metabase metabase/metabase
xxxxxxyyyyyyyxxaserjaser  <- コンテナID

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
xxxxxxyyyyy        metabase/metabase   "/app/run_metabase.sh"   3 seconds ago       Up 2 seconds        0.0.0.0:3000->3000/tcp   metabase

ハマりポイントとしては、DockerコンテナでMetabaseのデータ取得先のDBを動かしている場合は、--link オプションで動かさないとセットアップで以下のエラーが出て先に進めなくなります。
No matching clause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.

解決例としては、以下のようにリンクさせればOK。これはMySQLのコンテナとリンクさせた例。
docker run -d -p 3000:3000 --name metabase --link ponz-mysql:ponz-mysql metabase/metabase

アクセスして約1分待てばHome画面に遷移します。超早い!

f:id:accelerk:20180104211132p:plain

ここから、自分で登録したDBやサンプルで最初から入ってるデータセットを調べたりできます。

f:id:accelerk:20180104213312p:plain

データ可視化までにSQLを使わなくても良い

これがRedashとの最大の違いかなと。
例えば、東京都練馬区の各図書館の蔵書数(平成28年度時点)について調べたいとします。

※ こちらの蔵書数については、区発行のオープンデータを使用させていただきました。元はRedashいじるように使おうとしてました。。。

図書館所蔵資料数:練馬区公式ホームページ

調べたいデータを"New Question -> Custom" から選んで、自分で読み込んだDBのテーブルを選択。あとはプルダウンからサクッとデータ項目を選べば綺麗なチャートがあら簡単。円グラフが作れました。ここから、区分'0001'(図書資料)が圧倒的に多いな〜と分かりますね。
Redashは "Group by" に選んだ項目で絞ったりグラフを描画しても思った通りにならない時があるので、こちらの方が直感的かもしれないです。(個人差はあると思いますよ)

f:id:accelerk:20180104211356p:plain

もちろん、SQLも書いても問題なくいけます。"New Question -> Native Query" でSQLエディターが出てくるので、ここでSQLを打てばOK。
若干Redashの方がグラフのオプションが多い気がします。

f:id:accelerk:20180104211531p:plain

ダッシュボートはグリッド形式で大きさを指定して配置できます。ここも難なくできます。

f:id:accelerk:20180104212418p:plain f:id:accelerk:20180104212256p:plain

雑感

まだバージョンが 0.27.2 (2018/01/04時点) なので、破壊的な変更が入る可能性がなきにしもあらずですが、非常に可能性を感じさせるプロダクトですね!非エンジニアもRedash以上にデータを扱いやすくなりそう...

ZapierでスケジュールやらRSSフィードを一括でSlackに送る

みなさま、明けましておめでとうございます! 新年一発目は新しいことやろうとしていたので、ずっとやりたかったSlackで必要なことを1つにまとめるTipsについてです。

昨年のこちらの記事に触発されたこともあるので、ZapierでSlackにスケジュールとRSSフィードをまとめます。

[偉大な参考記事] tech.mercari.com

[Zapier公式] zapier.com

やること

Zapierって?

よく使うツールを繋ぎ合わせて、一連の作業を自動化するツール。一連の作業はZapとして作成します。 スクショの通り、めちゃくちゃいろんなツールが使えます。 例えば、Google DriveにファイルがアップロードされたらSlackに通知するとか、GithubのissuesをTrelloカードに追加したりできます。 Exploerタブからツールを選べば、10,000以上のZap例が出てくるのでみて見ると夢が膨らみます。

f:id:accelerk:20180101123427p:plain

サクッと作る

早速作ります。Exploerタブから使いたいツールを選択すると、よく作られるZapの一覧が下に出てきます。 自前で作ることもできるようですが、だいたい作るZapは定型化されているので Use this Zap から使わせてもらいましょう!

f:id:accelerk:20180101124547p:plain

あとはガイドに沿ってプルダウンやら項目を埋めれば5分経たずに作成できます。ヽ(*´∀`)人(´∀`*)ノ

f:id:accelerk:20180101125203p:plain

他にも、はてなブログのメール通知だと見逃す購読中のブログの更新(RSSフィード)もポストしたりできまする。 見るべきところを1つにまとめられると楽ですね。

1つのツール内でもトリガーやアクションは選べるようで、アクションの条件もフィルタリングできるようです。 例えば、Google Calenderのイベントの内容に[準備] が含まれているときだけ通知とか。

f:id:accelerk:20180101124132p:plain

他にも使えそうなツールがたくさんあるようなので、自分の手持ちのツールのintegrationをみて見るといいかもしれないですね。

IBM CloudでAnalytics Engineを触る

Hadoopの勉強がてらHORTONWORKS DATA PLATFORM(HDP)に触って感動したけど、SandboxをVMで動かすためのHW要件が中々ハードル高い...(メモリ最低8GBはMS Surfaceくんにはん荷が重すぎ)。 そんなときはCloud使っちまえと代替できるものとしてIBM CloudのAnalytics Engineがあったので気付いたことのまとめです。

Analytics Engineって??

HadoopとSparkのクラスターを立ててくれるサービス(下図カタログの左上のサービス) 。 ライト・アカウントでもLiteプランのインスタンスは作れるのでお手軽。この記事を書いている2017/11/24時点でHortonworks Data Platform 2.6.2をベースにクラスターを構成しているようなので、HDPの代替としては申し分なさそう。

f:id:accelerk:20171125004300p:plain

できること Good Point

大雑把に

ドキュメントを読む限り、HadoopとSparkのだいたいのことはできる(HDPがベースだから当然と言えば当然か...)。

  • HDFSにデータをUL
  • MapReduceジョブを実行
  • Hiveでクエリを投げる
  • HBaseを操作する
  • Sparkのジョブを実行

Analytics Engineのドキュメント ⇒ IBM Cloud Docs

細かいところでは...

ドキュメントだと、大体の操作はSSH接続でやるかCLIでやれってあるけど、やっぱりWeb コンソール使えたら楽(これはIBM Cloud全般に言えることだけど)。触れば分かりますが、Ambariコンソールが使える!
必ず最初に資格情報(Credential)を作成してから、資格情報内の ambari_console のURLにアクセス。同じく資格情報にあるユーザーとパスワードを入れればAmbariコンソールが開けます!すごい!

気を付けること

稼働時間の制約

特にLiteプランはサービスあげっぱなしだと、50H/monthの制限は3日で使い果たします。残り時間やべえよってメール来ますが、気付かないとその月はそのインスタンスがつかえなくなる罠があります。使えなくなった場合や使い切りそうになったら、おとなしく1か月待つこと。ライト・アカウントの場合、サービスを消して新しくインスタンスたてても1か月経過しないと作り直せないようです。(PAYGのアカウントの場合は未検証です)

Packageを間違えると使えないSW

Packageは2種類ある。AE 1.0 Spark と、AE 1.0 Hadoop and Spark の2つ。 (Liteプランでつくった場合)図のように使えるSWの数が違う! HiveやHBase、OozieといったSWは AE 1.0 Hadoop and Spark packにしか含まれない ので使う予定があるのなら後者を選んでおくこと。

f:id:accelerk:20171125010545p:plain

f:id:accelerk:20171125010621p:plain

軽く触ってみた程度ですが、Hadoopの学習にはすごくよさそうですな。

AR Studioでサンシャイン池崎になってクラウドとくっつけた話

なぜサンシャイン池崎に?

Facebook AR Studioワークショップ&ハッカソンに行ってきました。
お題として提示されたのが、今回の話題のfacebook AR Studio! ARを使ったエフェクトや表現を作成できるツールです!

本当はイケメンになるつもりだったのですが、"イケメンとは..." を突き詰めている間に池崎がフィットした次第です。 ARとあって、顔の表面にオブジェクトを貼り付けた上で動きを追従する優れもの。。。こんな感じ↓で貼り付けられます。

f:id:accelerk:20171114222044p:plain

たまたま貼り付けたサンシャイン池崎の顔の比率がぴったりでしたねw 顔のフィット具合の検証に関しては、以下リンクに譲ります。

hiroga.hatenablog.com

池崎とクラウドをくっつけて通信する

さて、本題です。ソーシャル要素を考えると、サーバー側と通信してデータを取ってきたいという欲が出てきます。 AR Studioは動きをつけたり、エフェクトを細かく制御するのは Javascriptを使うことになります。

ただし、制約事項としては以下2点を気に留めておきましょう。 1. Node.jsやブラウザのJSのような XMLHttpRequestやNPMを直接使えるわけではない 2. モジュール分割しづらい

データを取得する場合は、Networking Classの fetch メソッドを使えばOK。(中身は JSのfetch APIを呼んでいるみたいです)

[Networking Class]
https://developers.facebook.com/docs/camera-effects/reference/networking_module/networking_class

今回のハッカソンでは、以下のようにDB(今回はIBM CloudのCloudant NoSQL DB)からデータを引っ張る形にしました。 バックエンドはNode-REDサーバーを立ててCloudantにアクセスするAPIを叩く構成です。(Node-REDやCloudantの話は今回は割愛します)

// 表情のオブジェクトを取得する
const yeah = Scene.root.find('ikevoice');

// get neta serif url of Cloudant searching by id
function getNetaUrl(_id) {
    return "https://${bluemix-app-name}.mybluemix.net/neta?_id=" + _id;
}

// DBから取得したネタセリフをオブジェクトに書き込む
function fetch_neta(url) {
    Networking.fetch(url)
    .then(function (result) {
        if (result.status >= 200 && result.status < 300) {
            const neta_serifu = result.data.neta;
            yeah.text = neta_serifu;  // ここでセリフを yeah というテキストオブジェクトにテキストを書き込む
            D.log("Fetch succeeded!!");
        } else {
            D.log('There was an error: ' + result.status);
            yeah.text = 'Error retrieving data';
        }
    });
}

// 顔を後ろに倒した時のイベントを登録
// すごく直感的なメソッド!!
FaceGestures.isLeanedBack(face).monitor().subscribe((e) => {
    if (e.newValue) {
        if (judgeOrder(3)){
            ikezakiSwitch[3] = true
            saaaay('warai2');
        } else {
            allReset()
        }
        yeah.hidden = false;
        fetch_neta(getNetaUrl('0004'));  // '0004'のセリフをDBから取ってくる
        // driver.start();
    } else {
        // driver.end();
        yeah.hidden = true;
    }
});

注意すべきは、2017/11/12現在で HTTPのGETしか使えないこと。本格的に通信するならば、websocket使えるといいんだどなー...
今回はこっちから値を渡してDBに格納するフローもあったので、ハッカソンでは GET でAPIを叩く→ 叩いたAPIでパラメータを渡すPOSTのAPI叩く→ 結果を返す という手段をとりました。こうすればCRUD処理を実装できますね(๑ᴖ◡ᴖ๑)

[Node-RED側の実装例] f:id:accelerk:20171114224318p:plain

まとめ以上!

ログアウトしてもSFTPでファイル転送させるやり方

やべえ...PCのバッテリーも尽きそう(ThinkPadいわく、残り5分)なのに、SFTP転送に30分かかる処理やんなきゃ...そんなときに調べたことの備忘録です。

やりたいこと

  • ターミナルを閉じてもSFTPの転送を実施する
  • なるべく対話処理をしない(バッテリーのライフは危機的じょうきょうだからね!)
  • 失敗しても叩くコマンドを少なく再実行可能にしたい

やったこと

  1. nohup ... でターミナルを閉じてもプロセスがキルされないようにする。
  2. 1のnohupつきでSFTP転送のコマンドを実行 nohup sftp -o "batchmode no" -b sftp_to_hoge ponz@aaa.bbb.ccc.ddd
  3. Ctrt + Z で処理を中断
  4. bg (プロセス番号) でバックグラウンドに回す

それぞれ何をやっているか、具体的に分解します。

1. プロセスをキルされないようにする

ターミナルをそっと閉じると、実行中のプロセスは閉じた瞬間に終了します。(MySQLコマンドみたいに裏で流れ続けるものもあるけど)
プロセスを終了させないための、nohup コマンド。肝としては、コマンドの最後に & をつけないこと。 これをつけてしまうといきなりバックグラウンドに処理が回って、パスワード入力できないのね。


2. SFTP転送のコマンド実行

webkaru.net

今回の肝の部分。上のリンクにもある通り、オプション付きで実行します。

-b ... このオプションの後に、処理するコマンドを記述することで、ファイルからコマンドを叩ける。いろんなページには .bat 拡張子で書かれたものを実行しているみたいだけど、拡張子なしでもいけた。

$ ls -1 /home/ponz/targets/*.csv
ponz_team.csv
ponz_workdays_2017.csv
ponz_worktime.csv

$ cat sftp_to_hoge
lcd /home/ponz/targets/
cd /mnt/hoge/datas/csv/
put *.csv
quit

-o ... 実行する環境によっては、Permittion denied と処理を続行できない場合がある。明示的にバッチ処理を行うことを宣言するため batchmode no とする。(バッチ処理なのに、batchmode no とか気持ち悪い...)

コマンド叩いてパスワードを求められたら、入れてEnterでOK。


3. 処理を中断

説明不要の、処理中断。jobs と叩くと「一時停止」となっているはず。

$ jobs
[1]+  Stopped (tty output)    nohup sftp -o "batchmode no" -b sftp_to_hoge ponz@aaa.bbb.ccc.ddd

4. 中断した処理をバックグラウンドに回す。

jobs で表示されていたジョブの番号を確認してから、bgコマンドでバックグラウンドに回してあげる。 もう一度 jobs でプロセスを確認してあげれば、バックグラウンドを表す & が勝手についているはず!

$ bg 1
$ jobs
[1]+  Running    nohup sftp -o "batchmode no" -b sftp_to_hoge ponz@aaa.bbb.ccc.ddd &

もうちょいスマートなやりかた

完全に対話処理をなくすなら、

  • 秘密鍵ファイル(identity)を置いて、-i オプション実行
  • expect コマンドで想定される標準出力に合わせてパスワード入力処理をバッチファイルに書く

どちらかでやればいいはず。 シェル化するならば、expect で書いてあげたほうがいいかも。

参考

d.hatena.ne.jp

qiita.com