Ruby製の究極のJSONシリアライザ、Albaのご紹介
はじめに
この記事はRuby on Rails アドベントカレンダー 2021の記事です。昨日は@yamat47によるRuby on RailsでもHLS形式で動画を再生したい!でした。
Railsアプリにおける鬼門、JSONシリアライザ
昨今のRailsアプリケーションははじめからAPI専用として構築されることも多くなってきています。この場合、フルスタックフレームワークとしてRailsを利用する場合と比べて利用しなくなるgemも多いのですが、一方でAPI専用の場合に多く使われるgemも存在します。それがJSONシリアライザです。
この文脈における「JSONシリアライザ」という単語の指すものは、「任意のRubyオブジェクトをJSON文字列に変換するための機構(特にDSL)」を提供するライブラリです。
RubyにおけるJSONシリアライザの歴史は長く、最も有名なActiveModelSerializersのバージョン0.1.0がリリースされたのは2011年のことでした。そこから10年、今やRubyにおけるJSONシリアライザは史上最大の乱立期を迎えています。
現状については私自身が記事を書いていますが、この記事を書いた後にさらに多くのライブラリを発見したため、今や考慮できる選択肢は10近い数存在します。記事で言及されていないものとしては、panko_serializerというライブラリが圧倒的な速度で目を引きます。高速な分制約も大きいため、常に採用できるものではないかもしれませんが、有力な候補にはなるでしょう。
ここで問題なのは、存在する選択肢には決定版が存在しないことです。スター数やダウンロード数からはActiveModelSerializersの1強なのですが、こちらはあまり活発にメンテされていませんし、コードベースは非常に複雑です(コード行数は2600行を超えます、これは類似のライブラリ中最大です。ちなみにAlbaは約500行です)。
Jbuilderもまた著名でありダウンロード数も非常に多いのですが、(主観が入りますが)かなり癖の強いDSLを書かなくてはいけない点と、partialが多用されると速度が遅くなる点が原因で敬遠されることも多いgemです。
他の選択肢はすべてそれほど著名ではありません。Jbのスター数が1000を超えているほかは4桁に達しているものはまだありません。機能の面でも速度の面でも、存在するほぼ全ての選択肢に改善の余地が多く残されています。
Alba
そんな状況で決定版となるべく作られたのがAlbaです。Albaは機能と速度の両面で他の全ての選択肢を凌駕することを目標としています。
機能についてはさすがに客観的な指標があるわけではないのですが、他のgemが持つ有力な機能はほとんど備えているはずです。唯一足りていないのはJSON:APIへの対応ですが、こちらは次期バージョンで対応予定です。
速度についてはベンチマークがあるのでそちらを実行すると、Albaは総じて前述のpankoより3割程度遅い速度で動作します。pankoはC拡張まで使って高速化を図っているのでさすがに勝てないと思われます。そのほかのgemに対してはAlbaは互角以上に高速に動作しています。
Albaの興味深い機能
Albaにはユニークな機能がいくつかあります。
レイアウト
https://github.com/okuramasafumi/alba#layout
最近実装されたのは「レイアウト」機能です。これはAlbaが基本的に単一のオブジェクトまたはコレクションに対してのみシリアライズを行うため、任意のJSON文字列を生成できないという制約を解除するものです。
使い方は上記リンクに記載されていますが、日本語で書くと「あるリソースが自分のシリアライズ結果をその中に埋め込むような文字列またはハッシュを指定できる」というものです。この文字列はファイルの内容として与えるかProc
の返り値として与えることができます。どちらの場合でもシリアライズ済みのJSON文字列などにメソッドかインスタンス変数経由でアクセスできます。
レイアウトの主な使いどころはページネーションかなと思っています。また、前述のJSON:API対応はこの機能の上に作られることになりそうです。
インライン定義
https://github.com/okuramasafumi/alba#inline-definition-with-albaserialize
多くのJSONシリアライザは動作のためにクラス定義が必要です。普通はそれで問題ないのですが、再利用しないことがわかっている場合などはクラス定義は若干冗長に感じるかもしれません。
このような場合にAlba.serialize
というメソッドを使うことができます。このメソッドはブロックが与えられた場合はブロックの内容をリソースの内容であるかのように解釈してシリアライズします。ブロックが与えられなかった場合は、引数として与えられたオブジェクトのクラスに基づいて既存のリソースクラスを探し、見つかればそれでシリアライズします。
この機能はSinatraのような単一ファイルで動作するフレームワークを利用する際に有用かもしれません。
型定義
https://github.com/okuramasafumi/alba#experimental-support-of-types
最近何かとホットな型定義ですが、Albaでも型定義ができます。といっても、RBSを使ったものではなく、JSONに対する型定義です。
現時点では属性に定義できる型はJSONのネイティブ型であるString
, Integer
, Boolean
のみです。配列とオブジェクト型は現時点ではサポートされていません。
(これを書いていて気づいたのですが、今の実装ではFloat
に対応できていないので近いうちに対応します)
型を定義するだけでなく型を自動で変換することも可能です。例えば型をString
に指定した場合、デフォルトの自動変換はto_s
を呼び出します。Procを与えて独自の型変換ロジックを実装することも可能です。
依存関係がない
Albaはデフォルトでjson
ライブラリのみに依存しています。json
は標準ライブラリですので、gemとして見た場合はAlbaに依存関係はありません。一部の機能については実行時にrequire
するので、機能を使わなければ依存も発生しない設計になっています。これにより、例えば非Rails環境での利用の際にactivesupport
が依存に入ってしまう事態を防げます。
今後のデファクトになってほしい
作者の私が言うので割り引いて考えてほしいですが、Albaはかなり良い選択肢になっています。今後もRailsを含むRubyアプリケーションでオブジェクトをJSONに変換する機会はたくさんあると思うのですが、その際にはぜひ、Albaを試してみていただけるとありがたいです。そして、利用して何か困ったことや改善のアイデアがあればDiscussionsよりフィードバックをいただけるととても助かります。
そして、RailsアプリケーションをAPIサーバーとして利用する際にJSONシリアライザについて考える必要がなくなることを願っています。