SinatraでAPIサーバをつくるとき、いわゆるStrongParameter
のようなリクエストパラメータのバリデーションをどのように作るだろうか?
筆者は2年前くらいから、Sinatraでチャット用のAPIサーバをちびちびと開発していて、ずっとsinatra-paramというバリデーション・ヘルパのモジュールを使っていた。しばらくは満足していたのだが、残念なことにこのGemはあまり更新頻度が高くなく、2017年のSinatraの2.x系アップグレードには全く持って追従していないことが分かった。パフォーマンスの観点などからできるだけ最新のSinatraを使いたいので、こうしたGemの依存でアップグレードに追従できなくなるのはやはり避けたいところ。というわけで、この気にsinatra-paramを使うのをやめ、dry-validationというコンポーネント化されたバリデーション・モジュールに乗り換えることにした。
そもそも、dry-validationのような特定の機能に絞られたモジュールを組み合わせて使うことは**「疎結合性」**の観点でアドバンテージがある。sinatra-param
のようにフレームワークの実装に乗っかったモジュールの場合には、フレームワークの変更によって、モジュールが動かなくなる可能性がある1。言い換えれば、今回のように特定のモジュールが他のなにかに依存していることによって、コードベースを弄らずには、モジュールを更新できない状態になりえるということだ。数百行のソースコードであればいいかもしれないが、これが何千万行のエンタープライズ向けアプリケーションともなれば、ちょっとは辛くもなってくるというものだ。
dry-validationとは
dry-validationはdry-rbというコンポーネント指向なライブラリ群のひとつで、端的に言うとバリデーションを行うことに特化したモジュールだ
Dry::Validation.Schema
にブロックを渡すことで、バリデーションルールを定義できる。
require 'dry-validation'
schema = Dry::Validation.Schema do
required(:email).filled(:str?)
required(:age).filled(:int?, gt?: 18)
end
schema.call(email: nil, age: 19).messages
# { :email => ["must be filled"] }
Sinatraで使ってみる
これをSinatraで使うには、リクエストハンドラの中でうけとれるparams
を引数にしてバリデーションよ呼び出すだけでよい。dry-validationはバリデーション・ロジック自体の機能しか提供しないので、実際にエラーコードを発行する処理は自前でやらねばらない。
get '/hello' do
# バリデーションスキーマを定義
schema = Dry::Validation.Schema do
required(:id).filled(:str?)
end
# バリデーションを実行
validation = schema.call(params)
# 以下のふたつが使えるので、どちらかを使う
# - validation.failure?
# - validation.success?
# バリデーションが失敗していたら422(Unproceccessable Entity)を返す
halt 422 if validation.failure?
...
end
ヘルパにしてみる
上の実装によってバリデーションは実装できたが、しばらく実装を続けていると、スキーマを定義する以外の箇所(バリデーションが失敗していたら422を返す等)は何度も同じコードを使いまわしていることに気づく。こうしたリクエストハンドラの中で何度も登場するような処理は、ヘルパにするのが吉だ。
helpers do
def validates(&block)
schema = Dry::Validation.Schema(&block)
validation = schema.call(params)
halt 422 if validation.failure?
end
end
このようなヘルパを実装することで、リクエストハンドラの中のコードはスキーマの実装だけでよくなる。
get '/hello' do
validates do
required(:id).filled(:str?)
end
end
若干DSL色が強めだが、ヘルパを使うことでDRYになったことが分かる。
sinatra-validation
原点回帰してしまう感はあるが、sinatra-validationというものがある。これは、ここまで実装したdry-validationをより便利に使うためのSinatraのヘルパ・モジュールを提供してくれるGemだ。これをつかうことで、上で実装してきたヘルパ+αで便利なオプションなどを使うことができる。
アプリケーションにSinatra::Validation
を登録するだけでvalidates
ヘルパがリクエストハンドラの中で使えるようになる。
class Application < Sinatra::Base
configure do
register Sinatra::Validation
end
get '/basic' do
validates do
required("name").filled(:str?)
required("age").filled(:str?)
end
end
end
その他にも、raise
オプションで例外を投げるようにしたり、バリデーションエラーを返り値で受け取れるようにするsilent
オプションなど様々な機能がある。
dry-validationはdry-rbという、特定の機能にフォーカスを絞った小さなライブラリ・コレクションのうちのひとつで、それ以外にも様々な便利ライブラリが充実している。今回はバリデーションの観点でSinatraとの組み合わせを記事にしたが、他のライブラリもいろいろと見てみると良いかと思われる。
-
sinatra-param
はgemspecでSinatraの依存バージョンが固定されていただけで、実際に内部の実装が1系でしか動かないものであるかどうかは分からないので、PRを出して更新を促すという処置もできたかもしれない。 ↩