ジンジャー研究室

長めのつぶやき。難しいことは書きません。

Netlify Functions + FaunaDB 使ってみた

個人開発でサクッと何か作りたいとき、 Heroku みたいに手軽に「git push はいリリース」なノリのやつがあると便利なんだけど Heroku は無料だと半日寝てるし東京に居ないしアドオンも少しまともに使うと値段が跳ね上がる。ので、なんか良い代替がないかなと探していたら、 Netlify Functions というものを見つけた。静的コンテンツなら Netlify が便利なのは言うまでもないとして、サーバーサイドも何か書きたいときにはこれを使って AWS Lambda を動かせると言うことらしい。従量課金でお金も節約できそう?というわけで、やってみた。

以下、「何も情報がないよりはマシだろう」程度のメモ。日記なので技術記事だと思って読まないほうがいいです。

Netlify Functions + FaunaDB

Netlify Functions 自体についての解説はググれば Qiita やら何やら出てくるので、そっちを当たってください。説明するの面倒臭いです。

せっかくなので裏に DB を置きたいんけど、 Lambda だからって裏を DynamoDB にすると結局 AWS に浸かることになってしまい面白くないので、もう少し調べてみるとこういう記事が出てきた。 www.netlify.com

FaunaDB というのは初めて聞いたけど、スケーラブルなドキュメント DB らしく「World's best serverless database, now with native GraphQL」っていう売り文句で、ちょっと面白いのでやってみるかと(結局 GraphQL 使ってないけど)。

作ったもの

github.com

REST API (Webhook) のエンドポイントを動的に追加して気軽にテストできるやつです(前回 Heroku で作っていたやつのマイグレーション)。メールのテストに使える MailSlurp というサービスがあって、それのパクリ。

感想・ハマったポイントなど

正直 Netlify でこんなにハマるとは予想してなかったというか、分かったら簡単なんだけど上手くレールに乗らないと死ぬ。ローカルテストとかスムーズに開発するために netlify-lambda というツールを使うんだけど、 TypeScript でやろうとすると結構ハマりポイントが多い(願わくば TypeScript デフォルトにして欲しい)。

babel 設定

ここ に書いてある通り .babelrc が必要。 TypeScript on Node で babel が必要なのかは疑問だが、内部的に使っているようなので仕方がない。で、 async/await を何の気なしに使っていると babel がエラーを吐くので、フロントエンドだと @babel/polifill を入れて解決するところだけど、今回は Node.js 8 以降で動かすのでターゲットの方を変える

webpack 設定

ここで webpack が登場するのは、 Lambda に zip を送信する都合上、必要な依存だけを node_module からかき集めるのが都合が良いという話だと思う。いつものフロントエンドじゃないか助けてくれ。

デバッグしてたらいきなり i is not a function と出るので(sourcemap どうすれば利くの)ここ に従って webpack.config を書くことでまずは minify を解除。すると require is not a function になるのでどうしたものかと調べると同じハマり方をしてる人がいて、早い話がこの Issue の UPDATE2 を書くと解決する。

Netlify Dev

netlify-lambda の後発として netlify dev という netlify-cli のコマンドがあって、バンドルはしないけど必要な依存は解決してかき集めてくれるらしい。というのを後から知って「じゃあ netlify-lambda もう要らないのか」と思ったんだけど、どうも TypeScript とかが必要な時は結局 netlify-lambda が必要らしきことが README に書いてある。うーん、なんかもうちょっと綺麗にまとめてくれないものだろうかと思うものの、 netlify dev はまだベータなので改善を待ちたい。

Express

実は Express が使える。FaaS と言えば基本的に1エンドポイントにつき1関数だと思うけど、全てのリクエストを1関数に集約すると、その中でルーティングできるようになる。それでいいのかよという気もするが、そんなに細かいチューニングがしたいわけでもないし、 Express でサクサク書きたい気持ちが上回ってしまうのだから仕方がない。

詳しくはこの記事に書いてある通り、 serverless-http というライブラリを使うとサクッと Lambda と express の辻褄を合わせてくれる。

Decoder

デコーダーと言えば Elm のあれなんだけど、 TypeScript でも同じようなことができるのでやってみた。何かと言うと any をバリデーションして型をつけてくれる。例えば下のように Decoder<User> を作って userDecoder.run(value) すると、成功時には anyUser になって返ってくるし、失敗時には例外が発生する。あとは express と組み合わせて 400 を返せば OK 。

export const userDecoder: Decoder<User> = object({
  age: number,
  name: string
});

使い方はこのリポジトリがわかりやすいと思う。まだ PoC なので実装は適当だけど、最低限は使えるはず。実務だと Swagger から JSON Schema を引っ張り出して ajv とかでバリデーションするんだけど、まあ軽い用途だとこのくらいで良いよねという感じ。

FaunaDB

https://fauna.com/ からサインアップしてドキュメントを読むと大体雰囲気がわかる。ダッシュボードはかなり使いやすい方だと思う。 GraphQL も使えるけど機能に制限があるらしいので、今回は FQL (Fauna Query Language) というものを使った。FQL はかなり癖が強くとっつきにくいが、慣れてしまえば API ドキュメントを読み読みしながら書けるようになる。以下は TypeScript の SDK を使って書いてみた様子。

q.Select(
  "data",
  q.Filter(
    q.Map(
      q.Paginate(q.Match("results_by_key_order_by_requestedAt", key), {
        size: 100000
      }),
      q.Lambda(["_", "ref"], q.Select("data", q.Get(q.Var("ref"))))
    ),
    q.Lambda("x", q.GTE(q.Select("requestedAt", q.Var("x")), from || 0))
  )
)

正直これ AST を直書きしてるのと変わらないので、SQL みたいに文字で書きたくなってくる。とは言え、これはこれで composable なので上手くヘルパーを使えば楽になるかもしれない。一応、型もついてる。

あと、情報源がほぼ公式のドキュメントしかなくて、そこで理解できないとたちまち詰む。まあでもマイナー技術ってそんなものというか苦労するからこそ達成感を感じたりするし、作者も高確率でエゴサしてるので Twitter でちょっと呟くと飛びついてくるのが面白い(前例: Elm, ArangoDB, MailSlurp, etc.)。

全体としての感想

  • TypeScript で書こうとすると webpack や babel が絡んで難易度が上がる。TypeScript の開発がもう少しスムーズになるようにサポートして欲しい。その分だけ Heroku より手軽ではないが、 AWS を直に触るよりはかなり楽だと感じる。
  • FaunaDB は今後どうなるか未知数だが、 Netlify Functions と親和性は良さそうなので個人的にはもう少し使いたい。今のところ無料だし。
  • 手軽デプロイ系だと他には aws-cdk とか Firebase とかも気になってる。あと Netlify Edge 。