util.promisify が追加された

Node.js のコアに util.promisify が追加された。 github.com

今回は util.promisify が持つ役割を中心に Node.js における Promise の立場についても話していけるといいと思う。

util.promisify とは

読んで字のごとく関数を Promise に変換してくれるユーティリティメソッド。 下記のような要領で変換できる。

const util = require('util');
const fs = require('fs');

const stat = util.promisify(fs.stat);
stat('.').then((stats) => {
  console.log(stats);
}).catch((error) => {
  console.error(error);
});

async-awaitを使いたい場合(Node.js v7の最新では既にenabled)は下記の通り

const util = require('util');
const fs = require('fs');

const stat = util.promisify(fs.stat);

async function callStat() {
  try {
    const stats = await stat('.');
    console.log(stats.uid);
  } catch(e) {
    console.error(e);
  }
}

util.promisify 注意事項

Node.js のコアメソッドに限らず、他のメソッドもPromiseに変換できる、ただし、変換する場合はその関数がpromisifyの規約に従っている必要がある。

その規約というのは、

  • コールバック関数を引数の最後に取ること ( function(arg1, arg2, cb){} )
  • コールバック関数の最初の引数はエラーであること ( cb(err, res) )

である、これに従わない関数の場合はうまくPromisifyされないので要注意、特に2つ目の規約に違反しているとエラーじゃないものがPromise.reject の対象になってしまうことがある。

Node.js のコアメソッドのほとんどは上記の規約に従ったコールバック関数を取るが、Node.jsの規約から外れたコールバックの使い方をしているメソッドの場合は一工夫が必要になる。

例えば、 setTimeoutsetImmediate の場合がそうなる、これらはコールバック関数を最初の引数に要求するし、コールバック関数の最初の引数はエラーとは限らない。

こういった関数を Promise に変換したい場合は util.promisify.custom をプロパティにしてカスタマイズされたPromisify関数を提供してあげる必要がある。

const test = function(cb, arg){ cb(arg) }
test[util.promisify.custom] = (arg) => { return new Promise((resolve, reject) => { test(resolve, arg); }) };

const p = util.promisify(test);
p('foo').then((arg) => console.log(arg));

Node.js の setTimeoutsetImmediate はこの util.promisify.custom を使ってcustomizeされたPromisifyを作っている。

Node.js における Promise の位置付け

フロントエンドでは async await が採用されたり、 Web 標準のAPIが採用していたりと、ほぼスタンダードな印象を受ける Promise だが、 Node.js の中では実はまだまだ議論の余地がある。

util.promisify を採用するかどうかを議論していた時にその場に居たので、要点をまとめると

unhandledRejectionの時の振る舞いが決まっていない

という一点につきる。

議論で話している感じは以下の通り:

  • Promise のニーズは高い、特に async await のような構文サポートまであるので強力
  • しかしながら、Promise の unhandledRejection が起きた時にNodeのデフォルトをどう動かすようにするかが未定
  • 現時点では warnings が出る。しかし、今やってる unhandledRejection はただ単に例外発生時に .catch をする Promise が その時点で いなかっただけであり、Promiseが例外をキャッチするのは仕様上いつでも良いので、この時点で出るwarningsとしては適切ではない(非同期にキャッチされる可能性があるため)。
  • 現在の仕様で Promies を使ったとして、例外をキャッチしなかった際に容易にメモリリークやファイルディスクリプタのリークが起きることは想像しやすく、やはりNodeコアの中でも簡単にリークが作り込めてしまうような状況にするべきではない、リークが気づきにくくなる位なら異常終了した方がマシ

というのが議論ポイントだった。

もう少し噛み砕くと、『 Promise を簡単に使えるようにする(util.promisifyを提供する)なら、 Promise を安全に使える手段として提供してあげる(リークを起こさないようにする)べき』という感じだろうか。

これに対しては現時点で提案中のデフォルトの unhandledRejection の動きで既に3,4候補存在する。

unhandledRejection が起きたら:

まだこの部分の議論は続いている。

Node.js Collaborators Summitにおける、約一時間の議論は発散して終わった感じがするが、 util.promisify を追加する件に関してはある程度の有用性、コアでやることの意義が認められてマージされた。

Promise とどう付き合っていくか

僕らアプリケーションをNode.jsで書いている側としては気をつけるべきなのは、 Promise が使いやすくなってきているが、まだまだ運用面での知見が少ないという点だと思う。

もしかしたらリークが起きてるけど気づいていないとか、例外がスローされていたけどそのまま放置されていたとかそういう事がないように Promise は気をつけて使うべきだろう。

実際に Node.js v4 では Promise でメモリリークが起きていた(現在は修正済み)

更に言うと、現時点の Promise には core-dump を出す仕組みもない(processが死んだ時の --abort-on-uncaught-exception 相当)。Promiseを使ってしまうとエラーになって死んだ時に解析がしにくいという側面もある。

www.joyent.com

util.promisifyができたことで、Promiseが使いやすくなっているが、この辺りはまだ仕様検討中なのでv8.0次第ではPromiseの使い勝手は変わる可能性もある。

2017/05/12 追記: Promiseを無限ループさせるとリークが起きるというのは仕様の問題であって、Node.jsの問題ではありませんでした。

まとめ

  • util.promisify の説明
  • Promise と Node の位置付け
  • Promise とどう付き合っていくか

node の security checkをするなら nsp が便利

nspとは

先日たまたま会社で Vulnerability の話になって色々と Node.js だとこういうのあるんですよって言ったら知らなかった方も多かったので紹介。 nsp は node security platform の頭文字を取ったプロジェクトである。

Node Security Platform はサイト上で脆弱性を公開している。 Node.js のコアの脆弱性というよりも npm モジュールなどのモジュールの脆弱性だ。

nsp に挙げられてる脆弱性の一例

例えばこの脆弱性なんかは2017年2月11日に公開された脆弱性である。

https://nodesecurity.io/advisories/313

github.com

どういう脆弱性かというと、このモジュールはJavaScript Objectをシリアライズするためのモジュールだが、そのserializeする時に関数までも変換してくれる、JSONよりも少しだけやってることが複雑である。問題はdeserializeする時で、deserializeする時はnew Function 等で括って eval として関数を実行している、こうすると不正な即時関数 {e: (function(){ eval('console.log("exploited")') })() } をserializeしたオブジェクトが渡された場合にdeserializeした側の環境で勝手に実行されてしまう。この例題コードはconsole.logだから良いが、child_processのexecFileやらなんやらがサーバで実行されたら目も当てられない。

さて、この手の脆弱性は実は週単位のペースで上っている。これをいちいちチェックしてたらキリがない。ツールで自動化させようというのがこの nsp である。

nsp 使い方

インストールはとりあえず簡単。

$ npm install nsp -g

別にローカルモジュールに入れて npm run security とかでチェックできるようにしても良い。

$ nsp check

(+) 2 vulnerabilities found
┌───────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│               │ Code Execution Through IIFE                                                                                                                                                     │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Name          │ serialize-to-js                                                                                                                                                                 │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Installed     │ 0.5.0                                                                                                                                                                           │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Vulnerable    │ <=0.5.0                                                                                                                                                                         │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Patched       │ >=1.0.0                                                                                                                                                                         │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Path          │ server-timing@1.1.0 > serialize-to-js@0.5.0                                                                                                                                     │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ More Info     │ https://nodesecurity.io/advisories/313                                                                                                                                          │
└───────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌───────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│               │ Regular Expression Denial of Service                                                                                                                                            │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Name          │ ms                                                                                                                                                                              │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Installed     │ 0.7.0                                                                                                                                                                           │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Vulnerable    │ <=0.7.0                                                                                                                                                                         │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Patched       │ >0.7.0                                                                                                                                                                          │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Path          │ server-timing@1.1.0 > ms@0.7.0                                                                                                                                                  │
├───────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ More Info     │ https://nodesecurity.io/advisories/46                                                                                                                                           │
└───────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

これだけ。これだけでshrinkwrap もしくは package.json の定義を眺めて脆弱性のあるモジュールがないかを Node Security Platform の API に投げて確認してくれる。

もう少し凝った使い方をしたければ、 .nsprc に 例外ルールを追加して無視することも可能。

{
  "exceptions": ["https://nodesecurity.io/advisories/12"]
}

もちろんこれだけで脆弱性が防げるわけではない。これは自分の依存モジュールに脆弱性が報告されていないことを見つけるための道具でしか無い。

自分のサイトの脆弱性や自分のモジュールがうっかりSQL Injectionしていたなんて事にならないようにしたい。

ちなみに yarn 対応なんかはまだの様子。

github.com

Demystifying webpack2 tree shaking

webpack2 に最近移行しました。

その時の知見とせっかくなので tree shaking が実際に中でやってることを追ってみたので紹介。

webpack2 移行時の注意

基本的にはほぼここに書いてあるとおり。

Migrating from v1 to v2

かいつまんで説明すると、configファイルの書き方がガラッと変わって、 module.loadersmodule.rules になったり、 resolve.root がなくなって resolve.modules に変わったり。この辺の書き換えは割りとすんなりいくはず。

辛いのはpostcss周りのオプションの渡し方辺り。これまではconfigのrootにpostcssプロパティを用意してそこに記述できたが、その記述はできなくなり、 webpack.LoaderOptionsPlugin 経由で渡すか postcss.config.js というファイルを作ってそこに渡す必要がある。どちらでも構わないが、 postcss.config.js で渡す方法が postcss-loader の issue でオススメされていたのでそれを採用することにした。

※コメントで教えてもらったが、 .postcssrc でやる手段もある様子。

また、 ExtractTextWebpack という Plugin がまだ v2 では beta 版という位置づけで、割りとオプションの渡し方周りが定まりきっていないので注意。 ハマったので issue とにらめっこしながらコード読みながら進めるとこのスレで解説されていたのでその通りやると良い。

github.com

webpack2 が出たからと言ってまだローダー周り、プラグイン周りが若干stableじゃないことに注意した上で移行すると良いだろう。

Tree Shakingとは

Rollup が言い始めたのか、出自を辿るとRich Harris が語るRollupの話が出てきた。 要は木を枝刈りするという意味。もうすこしかいつまむと、使っていないライブラリを枝刈りして削り、小さくしてbundleすることを指す。

webpack2 におけるおそらくはメイン機能の1つであり、webpack2 にする人はだいたい tree shaking までやる傾向にある。

webpack2 の Tree Shaking を試すのは簡単で、 babel での module トランスパイルを辞めれば良い。

{
  "presets": [
    "react", 
    ["es2015", {"modules": false}] // modules を false にする。
  ]
}

こうすると webpack2 では import/export 構文をそのまま扱えるようになる。これを使って、 export されてるけど、 import されていないものを見つけて、それだけはbundleしないという方法を取る。

実際にやってみると多少の効果はあり、このような結果になった。

Demystifying webpack2 tree shaking

じゃあ中で何をやってるんだろう、ということで中身を追ってみた*1

webpack2 は実際 export されているファイルはほぼそのまま展開する。ただし、 export されているが、 import されていない関数や変数、クラスに関しては実際には export する時に common js として export しない。

例を挙げる、下記のようなファイルが存在するとする。

// main.js
import { sum } from './math';

sum(1,2);
// math.js

export function sum(a, b){
  return a + b;
}

export function sub(a, b){
  return a - b;
}

export function mul(a, b){
  return a * b;
}

ここで、 webpack2 の変換をかけると下記のようになる。

"use strict";
/* harmony export sum */ 
exports["sum"] = sum;

/* unused harmony export sub */
/* unused harmony export mul */

function sum(a, b) { return a + b };
function sub(a, b) { return a - b };
function mul(a, b) {return a * b};

見てもらうと分かるが、関数はそのまま展開されているのがわかると思う。ただし、 exports オブジェクトには submul といった関数をマッピングしていない。その代わり「コメントでunusedなので export しなかった」というのがわかるようになっている。

このままだと、特にtree shakingの旨味はない。コメントがある分、むしろファイルサイズとしては増える可能性もある。

tree shaking しても何もおきないのかと思うのは時期尚早。ここで production 用のビルドをして、 UglifyJS の力を借りると下記のようになる。

a["sum"] = function(a,b){return a+b};

実は UglifyJS には unused な関数や変数を消してくれる、という機能が備わっている。

GitHub - mishoo/UglifyJS2: JavaScript parser / mangler / compressor / beautifier toolkit

この機能をwebpackは内部的に使うことで tree shaking を実現している、上の例で言うなら、 submul といった関数はそのまま出ていたとしても、結局 exports オブジェクトにマッピングされていないため、参照がなくなり、誰からも使われていない事になる。

これによって UglifyJS2 がunusedなものとして、削ることができるというわけだ。

ちなみに babel の場合、ファイル単位での変換を基本とするため、『export されているが import されていない』という情報を持たずに transpile している。これによって上述したような『この関数や変数だけは common js にマッピングしないでおこう』というような処理ができない。ただし、すごく単純な仕組みなのでいつか実装される可能性もある。

これまでtranspileされてcommonjs になるだけで特に強い意味は無かった ES6 modules 形式での記法だが、このような形で メリットが享受されるようになるなら書いてもいいのかもしれない。

まとめ

  • webpack2 移行の注意
  • Tree Shaking とは
  • Demystifying Webpack2 Tree Shaking

参考

webpack/examples/harmony-unused at master · webpack/webpack · GitHub Tree-shaking with webpack 2 and Babel 6

*1:というのも、実際に削除されてるのか追ってる内にへ~と思う発見があっただけ

ソフトウェア例え話、格言、小噺

2016年になってから色んなソフトウェアエンジニアの人と話してきて、その中で3人から聞いた例え話、格言、小噺が面白かったので、僕の中だけで留めておかずに開放しておく。

息継ぎをするには『まず息を吐く』という例え話

水泳で息継ぎをするなら『まず息を吐きなさい』と教わるらしい。これは息を吐かずにどこかで息を貯めてしまうと、ちゃんと息を吸えないという事を意味してる。息を吐くと苦しくなって顔は絶対に水面に出る。

これと同じことがソフトウェアの学習にも言える。

つまりまずアウトプットする、なんでも良い。作ったものをGitHubに公開するとか、発表するとか、ブログやQiitaに書くとか。ちゃんとアウトプットしたものはフィードバックがあり、そのフィードバックを受ける(PRやissue, 質問, マサカリ etc)、どんどん吐き出していくと吸わないとネタがなくなるので、吸い込むためにまたインプットする。

同じような話として、教えることで勉強するという学習法がある。

自分が誰かに教える役になるというのは実は一定の知識がないとできない。

アウトプットする、という最初の一歩は躊躇しがちかもしれないが、アウトプットすればするほど次のインプットになり、良い効果が得られる。

ちなみにこの例え話は及川卓也さんとの対談で語っていただきました(ちなみに下の記事じゃなくて多分別な記事になるはず)。

logmi.jp

プロジェクトを失敗させる方法

プロジェクトを絶対に失敗させる方法というのが1つある、関係者をひとつにまとめずにバラバラの部屋に分けること。

ソフトウェア開発プロジェクトでも考えると1つの部屋で広い部屋を借りるか複数の狭い部屋を借りて部屋を分けるのとどっちが都合が良いか、というと圧倒的に前者。

まず、一つの部屋にまとまってると直接顔を見てコミュニケーションがしやすい。部屋が分かれてるとそれだけでコミュニケーションコストがかかる。SlackやIRCなどでコミュニケーションはできるとはいえ、やっぱり表情を見ながらホワイトボードに書きながらの話ができるのとは少し違う。

さらに言うと、プロジェクトには『偶発的なコミュニケーション』が重要になる。偶発的なコミュニケーションというのは、たまたま聞こえてきた議論の内容だったり、そのへんに書かれてたホワイトボードの走り書きとかが見えるとか、そういう所から始まる突発的なコミュニケーションの事を指す。

よくタバコ部屋での会話がきっかけで仕事の話が回るとかいう話は出るが、それと少し似ている。要は偶発的なコミュニケーションが起こりやすい環境でトークするというのはきちんとした会議で決まることよりも重要な事がある。

さて、これを振り返ると、この偶発的なコミュニケーションというのは色んな所で実は応用できる。

例えばslackでの分報システムもそうだと思う。リモートというのを逆手に取って敢えて自分が今やってること、詰まってることを積極的に共有する仕組みで、偶発的なコミュニケーションを引き出そうとする。

c16e.com

もう少し話をすすめるとGitHubかなんかのリポジトリもチームやら言語やらで分けるよりもプロジェクトが1つのリポジトリでやった方が偶発的なコミュニケーションが生まれやすい。コードが見えるだけじゃなく、PRも見えるし、issueも見える。

実はgoogleなんかは1つの巨大なリポジトリでプロジェクトを管理してることが多いらしい。

Google の巨大レポジトリとブランチ無し運用 - Kato Kazuyoshi

ちなみにこの小噺はt-wadaさんとお昼を一緒になった時に教えてもらいました。

正しいものを正しく作る

正しさには厳密には2種類ある。 validation verification の2種類。

validation は語源を辿ると、value になる。つまり「価値があるかどうか」という意味。ソフトウェア開発で言うと、何かしらの数字に反映される事がvalidationを満たしていることになる。分かりやすく言えば売上があがる、コストが下がるとか。

verification は語源をたどると、veryになる。veryは「とても」と訳される事が多いが「まさしく」という意味。つまり、「まさしく在るべき姿」であるかどうかという意味。分かりやすく言えばテスト書いてるかどうかとかコードの設計が良いとか、直接的に数字に反映されるようなものじゃなく、プロジェクトとしてあるべき姿になっているかどうか。

どっちの正しさを満たすべきっていう話じゃなくて、両方満たすのが一番正しい。(ただこの手の締め切りを優先させるべきかテストを書いたほうが良いか等の話が出る度にどっちかに振り切った話が多い気がする。実際はどっちかだけ満たしても正しくない。)

確かに両方満たそうとしても実際はなんだかんだで取捨選択を迫られることは多い。もちろんその時はプロジェクトの状況や内容に応じて決めれば良い。繰り返しになるがどっちかを取れば良いというものじゃなくて、両方満たすのが圧倒的に正しい。

f:id:yosuke_furukawa:20161231225632p:plain

もちろん両方満たすのなんて理想だと思う。ただし、両方満たせないのだとした時に今度はそれを埋められるように研鑽を積むべきだし、チームとしてどうあるべきかを振り返っていく必要がある。要は理想だからといって諦めるんじゃなくて両方満たせるようになろうよ技術者なら。という事を説いていきたい。

ちなみにこの格言は弊社の隣で一緒に開発している。 koichik から教えてもらいました。

来年もよろしくお願いします。皆様。

Exploring Node.js Future というタイトルで jsconf.asia で発表してきました。

このブログ記事は Node.js advent calendar の 4日目の記事です。

qiita.com

いやーギリギリ 12/4 に間に合いました。

というわけで本題。

はじめに

Node.js の日本のコミュニティを3年運営して、色んな所で語ってきた僕だからこそ、 Asia の JavaScript コミュニティに対して語れる話があるんじゃないか、ということで jsconf.asia で Node.js の今後について話してきました。

f:id:yosuke_furukawa:20161208104803j:plain

jsconf.asia とは

シンガポールで行われる JavaScript のカンファレンスです、 jsconf.asia という名前の通り、 JavaScript カンファレンスの Asia 代表版ですね。 今年で5年目らしいです。

jsconf っていうだけあって、 web 系の話かと思いきや、脳波測定とか、機械学習とか、IoTとかの話ありで、ごちゃまぜ感あって、すごく面白かった。

僕の発表

Node.js の機能の今後の話をしてきました。 Exploring Future Node というタイトルで、Node.jsのアジア代表のつもりで話してきました。

Node.js は今、『コアに色んな便利機能を追加しよう』とか『 cool な機能を増やしていく』ような事は避けていて、 "small core" を表明しています。一方で、 "small core" とはいえ、 Web Application 開発を重要視しております。

これを検討する上で、重要なのが "Web Standard" になります。 Web Standard の機能は追従するという姿勢でやっていて、例えば ECMAScript はもちろん、 国際化標準のECMA-402、ブラウザのAPIを決める WHATWG、 httpだったりネットワークだったりの標準化を決める IETF のやっていることをちゃんと core の中に入れていくという話です。

今回の資料はその現状と今後を見せた感じです。

今後の話で言うと今のところ一番ホットだと僕が思ってるのは、 HTTP/2 と ES Modules interop で、この2つは今後の Web Standard になりうる話題だと思っているので、かなり重点的に話してきました。

発表してきて

まぁやっぱり jsconf.asia 自身はすごく面白いイベントだったので良かった。2015年は NodeConf.EU で発表できて、 2016年は jsconf.asia で発表できたので、来年もどこか海外カンファレンスで発表したいと思います。

#builderscon に参加してついでに発表してきました。

『知らなかったを聞く』というのが builderscon のコンセプトなので、文字通り全く知らない発表ばかり聞いてきました。

参加したのは下記の通り:

  • OSSWindows で動いてこそ楽しい
  • 動け!Golang 〜圧倒的IoTツール開発へようこそ〜
  • Automatic Smile Camera を作った話 - 親バカハックノススメ -
  • Open Beer Serverの理論とその実装
  • C 言語で行う Web フロントエンドプログラミング
  • Highly available and scalable Kubernetes on AWS
  • そろそろプログラマーFPGAを触ってみよう!
  • Docker swarm mode などで作る PaaS モドキとその悲しみ
  • 世の中の困り事はだいたいGoのコード自動生成で解決する
  • Bluetooth キーボードの作りかた

太字は僕の発表

面白かった発表

Open Beer Serverの理論とその実装

さすがの moznion 氏というか、プレゼンに対してのパフォーマンスがもはやエンジニアの域ではない気がしました。作成したビールサーバーの内容もさることながら、要所要所での笑いのポイント、最後のデモンストレーション、質疑応答の立ち居振る舞い全部が最高でした。

ただ発表資料だけではおそらくこの面白さは伝わってこないので、是非興味のある方は直接見に行かれると良いかと思います。

Bluetooth キーボードの作り方

ErgoDox のキーボードの話聞いて、これを作ってみようという風になるのはなんというかすごくエンジニアチックで素晴らしかった。 ほぼこの一言に言いたかったことが集約されてる。

『俺は自分を変えたくない、変わるべきはインタフェース』、この精神は多分最初の mattn さんの話も同じで、 Windows で開発するのはすごく苦労が伴うが、変わるべきはインタフェースだとする発想を発表者二人共持っていたのは面白かった。

builderscon tokyo 2016 で「 Bluetooth キーボードの作りかた」を喋りました | tech - 氾濫原

自分の発表

娘の笑顔を検出してカメラで撮影する IoT の話をしました。

割りと面白かったとかいい話だったとかそういう意見が twitter で見れたので良かったーーー。

発表を通して

全く知らなかった話を聞くのは面白かったけど、聴講者のレベルがまちまちなので、発表者側は誰にでも分かるようにレベルを落とす必要があって、そういう風に初歩の初歩から教えるようになっていない発表は難しそうに感じてしまいました。

FPGAの話は全く門外漢だったけど、デモを中心になってて分かりやすかった。 C言語で Web フロントエンドプログラミングをする話も同様にデモがいくつかあって、分かりやすかったです(Emscriptenは全く知らないというわけじゃなく、実は割りと使ったことあるレベルですが)。結局やりたいことが何なのかを分からせるのにデモより雄弁な仕組みは無いんだなと思いました。

全く知らない人達をちゃんとケアするように発表者はなるべく専門用語を使わずに、デモで分かりやすく、面白おかしく伝えられるとこういうイベントでは良いのかなと思いつつ、自分の発表にももっとデモベースで話せると良かったかなと思いました。

builderscon すごく面白いイベントでした!! 2017 年も行きたい!!!!

リクルートテクノロジーズのフロントエンド開発 2016

前書き

このエントリーは Recruit Engineers Advent Calendar の 1日目の記事です。 www.adventar.org

リクルートテクノロジーズのフロントエンド開発

リクルートテクノロジーズではいくつもの並行するタスクが走っていて、プラットフォーム基盤と呼ばれる基盤技術開発とインフラソリューションと呼ばれるインフラ開発、後はアプリケーション開発支援などのタスクが存在します。

アプリケーション開発支援の中でもウェブフロントエンド開発は目下のところ重要タスクとされており、色んなやり方をトライアルしています。

基本的には、 React Redux Node.js という組み合わせでフロントエンド開発をしています。

主には以前 ubb.jp というイベントで発表したこの資料に記載されている内容でやっていますが、諸々補足します。

リッチなウェブアプリケーションを作るための 7つの原則

以前に書いた下記のような原則をなるべく守るようにしています。

yosuke-furukawa.hatenablog.com

特に気をつけているポイントは以下の点です。

  • サーバーサイドでもクライアントサイドでもレンダリングをする ( Server Side Rendering)
  • ユーザーの入力に迅速に反応する ( Optimistic UI )
  • history を壊さない、特にブラウザバック・フォワードをした時のスクロール位置に注意する (History API / Scroll Middleware)

それぞれ少しずつ補足します。

サーバーサイドでもクライアントサイドでもレンダリングする

所謂サーバーサイドレンダリングと呼ばれるレンダリングを行っています。

構成としては下記のような形です。 f:id:yosuke_furukawa:20161201110714p:plain

Backend に Micro Services 化された API 群があり、 ブラウザのレンダリングをサポートするための層として Backend For Frontend を設置し、そこでサーバーサイドレンダリングなり、 API のアグリゲーションなり、認証なり、ファイルアップロードなり、 WebSocket なりのマルチタレント役を引き受けてやっています。

サーバーサイドレンダリングをするのは主に初期ロードの高速化のためです。実アプリケーションの中では react-router の機能と react の renderToString を使ってレンダリングをしています。ただし、そのままだとアプリケーションによっては性能の問題を引き起こす可能性があるので、チューニングもしています。主なチューニングの内容はこちらの資料が詳しいです。

ユーザーの入力に迅速に反応する

Optimistic UI とも言われますが、入力した内容に対して迅速にリアクションを返して何かが起きてる事をちゃんと伝えるようにしています。

optimstic

Optimistic UI 出典元: Stop Getting In My Way! — Non-blocking UX – Sophie Paxton – Medium

これを実現するにはボタンを押した、フォームから検索した等の状態が変わったタイミングでインジケータなり、プログレスバーなりの情報を出して上げる必要があります。今のところ、このリクエストの元となるイベントが発火してから完了するまでを簡単に実現するためには redux-effects-steps というミドルウェアライブラリを活用しています。

github.com

これ自身はそこまで多機能ではなく、 redux-effects と呼ばれるミドルウェアライブラリを使ってアクションをまとめ上げる機能を提供するものです、これを使ってリクエストの開始、完了、失敗を管理しつつ、開始が始まったらインジケータを表示し、完了したらインジケータを無くすといった処理をシンプルに書けるようにしています。

history を壊さない、特にブラウザバック・フォワードをした時のスクロール位置に注意する (History API / Scroll Middleware)

Single Page Application だと、 ブラウザの戻る・進むをした時にうまく戻れなかったり状態が引き継げないことがあったりします。これ自身はアプリの作りが悪かったりもするのですが、気をつけないと簡単に起こせてしまうので、注意が必要です。

https://cldup.com/c081WZaE8H.gif 戻るボタンを押した時に正しく初期のHTMLがロードされていない例(Githubでコメントを追加て、別なページに行った後、戻ってくるとコメントが消えている例)。 出典元: 【翻訳】リッチなWebアプリケーションのための7つの原則 - from scratch

リクルートテクノロジーズでは、 react-router の history 機能を使いつつ、自分たちでロードのタイミングを調整するために redux-async-loader というライブラリを使っています。

github.com

これ自身は、割と多機能なライブラリで、 react-router から Component が mount された時にアクションを実行させてデータをロードする機能やサーバサイドレンダリングでもアクションを実行する機能などが存在しますが、 react-router-scroll と組み合わせると、戻る・進むを行った時のスクロール制御の機能も提供しています。

import useScroll from 'react-router-scroll';

const RenderWithMiddleware = applyRouterMiddleware(
  useAsyncLoader(),
  useScroll()
);

API仕様 ( Consumer Driven Contract )

API 仕様を定義するのに Consumer Driven Contract を実践しています。 Consumer Driven Contract とは、従来バックエンド (Provider)が決めていた API の仕様をフロントエンド(Consumer)が主導して要求を書く(Contract)ことで API 仕様を決めていくというスタイルの仕様策定方法です。

pact とかのツールが有名ですが、リクルートテクノロジーズの僕の開発では、 agreed というツールで行っています。

github.com

agreed は Mock Server 兼、テストクライアントになっており、 agreed を使って Mock となる振る舞いを決めてサービスを作りつつ、バックエンドはその Mock の内容を確認しながら実装していきます。

最初にクライアント側での要求を書きます。 agreed ではこれを contract と呼んでます。

f:id:yosuke_furukawa:20161201163906p:plain

このファイルは JSON5 や js などで書くことが可能で、 request の形と response の形を決めて、その内容を記述するスタイルです。

f:id:yosuke_furukawa:20161201171715p:plain

次に agreed server を起動させてクライアント側を先行して実装していきます。実装する過程で API の詳細が決まってくると思うので、それを基に agreed の contract ファイルを拡充させていきます。

f:id:yosuke_furukawa:20161201171846p:plain

最後にagreedをclientとして起動させてバックエンドがその要求(Contract)を満たしているのかを確認することができます。

これを使うとバックエンドの統合テストっぽくも振る舞えるので、少なくともクライアントの要求を満たしているかどうかは確認できます。ただし、これを使ったからといって、 『API 仕様定義のための交渉事が完全になくなる』というわけではありません。あくまで API 仕様を定義するのはバックエンドとフロントエンドの両方です。このフロントエンド側とバックエンド側の議論の叩き台にはなるし、交渉をスムーズにする手助けにはなります。

他に諸々

上記のような技術を使って実際のアプリケーションを構築している真っ最中です。

1つは先日リリースされた Booking Table ですが、 Node 学園祭でも発表があったとおり、 かなりチューニングに気を使って設計されています。 またこれからもリリースされるものはあります。

まだまだフロントエンド開発のためのツールや方法論は足りていないので、絶賛作りながら足りないツールやハックをノウハウとして貯めている所です。アップデートがあったら2017年版をいつか公開します。