Expressのよさを語る2018
2012年くらいから使っているが、一貫していいと思い続けているkeroxp.icon2018/8/1
そのよさの理由を語ります
Expressの良さは、公式でも言っている通り
特定の意見に固執しない(unopinioned)
高速で(fast)
最小限の(minimal)
Node.js向けWebフレームワーク
であるところに集約されている
Expressは、とにかく実装がシンプルである
フレームワークと謳っているが、もともとはconnectというhttpサーバの薄いラッパーとして作られた v4から互換性は保ちつつもconnectは使われなくなった
connect自体がNode.jsの癖のあるhttp標準ライブラリを使いやすく表現した素晴らしい発明だったのだけど、expressはその補完的役割を提供し、人気を博していった
結果、tj holowaychuk氏の個人プロジェクトから始まったExpressはNode.js財団のソフトウェアになった
Expressが人気を博した理由は、その設計のシンプルさにあったと思う
ExpressでHTTPサーバを立てるのは、こうするだけである
code:index.js
const express = require("express")
const app = express()
app.get("/", (req,res) => {
res.json({hoge: "hoge"})
});
app.listen(8000)
比較対象として出して申し訳ない気持ちもあるが、Ruby on Railsはここまでシンプルに記述できない
Expressは、RubyのWAFであるsinatraの影響を受けて作られている code:index.rb
require 'sinatra'
get '/' do
'hoge'
end
ルートを関数で定義するというところを引き継いでいる
sinatraの場合は、RubyのRackという仕組みの上に乗っているだけあってブラックボックスな部分が多い
"hoge"をreturnするとなぜレスポンスが返るのか?とか
これはSpring-BootやRailsにも通ずる要らぬ抽象化だと僕は思っている
Expressの場合は、フレームワークが担当する部分が本当に薄い
実質的に、HTTPリクエストのI/Oしかやっていない
その最小限の設計のおかげで、開発者は
「パスでリクエストを受けてレスポンスを書き込む」
というHTTPサーバの基本動作を関数だけで簡単に記述でき、柔軟に動作させられることができる
HTTPはTCPのサブプロトコルに過ぎず、TCP自体はステートフルなストリーム志向のプロトコルである
なのでHTTPで「リクエストを返す」という行為はTCPストリームに素の文字列のヘッダとボディを書き込むことに過ぎない
これらが提唱するControllerという概念は、httpのサーバをオブジェクト、ルートをメソッドとして抽象化して、httpのレスポンスをメソッドの返り値(=オブジェクト)で表現しようとする
しかしそもそもtcp自体がオブジェクト志向とは関係ない設計になっている以上、httpルートの振る舞いを関数化するのは無意味である
一方で、リクエストをオブジェクト化するという試みは一定の成功を収めているように思う
素のhttpがステートレスなプロトコルであるメリットとして、リクエストのデータが変わることがないという点がある
ヘッダーがmapになっていたり、bodyがバイナリアレイになって表現されているのは分かりやすい
なぜなら、大抵の場合サーバはhttpリクエストをすべて受け取ってから出ないと機能をサーブをできないからである
httpのプロトコルの仕様上、リクエストをストリームから読み出しつつ何かをやるということは可能である
が、普通はそういうことはやらない
アップロード処理とかはヘッダだけ先に読むことはありそうだが
一方で、httpレスポンスを返すということは、関数の返り値を返すということとはあまり相性が良くないように思う
というのは、httpのレスポンスも結局はtcpストリームであるから明示的に終わりを告げないとクライアントは結果を待ち続ける
逆に言えば、待たせることができる
サーバの処理は常に非同期処理を伴うものなので、レスポンスを作る作業を平行化する都合上、ストリームに逐次書き込むというスタイルが相性がいい
Expressは、レスポンスの提供法としてRails wayなコントローラの返り値ではなく、resオブジェクトのメソッドとして実装されている
code:js
res.status(200); // ヘッダに200を書き込む
res.set("content-type", "text/html"); // ヘッダのContent-Typeにtext/htmlを書き込む
res.send("data"); //ボディに文字列を書き込んで終了する
一方で、ExpressのRespomseはNode.jsのStream APIを実装したオブジェクトでもあるので、ストリーム処理をpipeさせることもできる。
code:js
const fs = require("fs");
res.status(200);
res.set("content-type", "text/html")
fs.readFile("index.html").pipe(res)
大きなデータなどをchunk化して送信する場合にはこういった処理が必要になる
ここで重要なのは、ExpressがこうしたHTTPサーバの振る舞いを、ピュアJavaScriptの機能だけで実現している点にある
JavaやC#系統のフレームワークは、伝統的にこういった込み入った処理を複雑なアノテーションやプリプロセッサの機能を使って実現する
Railsのroute.rbの記述とcontrollerのメソッドの対応付は、彼らが批判している「設定」によって行われている
Expressがよりシンプルな実装にできているのは、言語的特性も大きく、特に関数がファーストクラスであるという点によるところが大きい
Webアプリを勉強し始める初心者が、「設定よりも規約」を謳うJavaやRailsなどのフレームワークから入ると、「パスでリクエストを受け取ってレスポンスを書き込む」というWebサーバ、ひいてはUNIXのファイルシステムの基本思想を肌で理解することを阻害すると思う
UNIXの世界ではHTTPレスポンスを返すこともファイルにデータを書き込むこともコマンドをpipeすることも同義である
あるファイルからデータを読み出して、別のファイルにデータを書き込むことしかやっていない
UNIXファイルシステムは、それだけ強固で自己内包的なシンプルさを持っている
が、なにの理由からかこういった根底にある重要な思想を飛ばして、表面的なわかりやすさ(わかりにくくしさ)を売りにするのは僕は好きではない
何より、人の作った規約を覚えるのは大変に苦痛な作業であり、プログラミングの楽しさをスポイルしている
そして彼らのいう「規約」は、単に彼らにとってのローカルルールであり、一度ドメインをはずれれば役に立たない
「規約を覚えれば楽」という思想は、もう今の巨大化したフレームワークでは形骸化していると思っている
そもそも、Webアプリ、HTTPサーバは基本的にシンプルなことしかできない
「リクエストを受け取って、レスポンスを返す」
その過程で、どこからどうやって返すデータをとってくるか(ファイル、DB、API...)の違いしか無い
これの役割はHTTPの仕様が大幅に変わらない限り不変である
しかし、時間の経過とともにフレームワークが陳腐化すればその頑張って覚えた「規約」も無駄な知識になってしまう
前職時代Spring-BootやJacksonの膨大な数のアノテーションをイライラしながら覚えていたが、もう完全に忘れた
プログラマは怠惰であるべきなのに、なぜそういった不要な勤勉さを礼賛する風潮が多いのだろうか?
「覚えるのが難しいこと=価値のあること」という価値観は、知識層には根深いのだろうか?
しかし現在のソフトウェアの基本となっているUNIXの思想は、一貫してシンプルさ、小ささを美徳としている
まとめると、Expressの良さはその不要な抽象化を廃したシンプルさにあるということ