SequelとSinatraがRubyのAPI問題をどのように解決するか
公開: 2022-03-11序章
近年、JavaScriptシングルページアプリケーションフレームワークとモバイルアプリケーションの数が大幅に増加しています。 これにより、それに応じてサーバー側APIの需要が増加します。 Ruby on Railsは今日最も人気のあるWeb開発フレームワークの1つであり、バックエンドAPIアプリケーションを作成するための多くの開発者の間で当然の選択です。
Ruby on RailsのアーキテクチャパラダイムではバックエンドAPIアプリケーションの作成が非常に簡単ですが、RailsをAPIにのみ使用するのはやり過ぎです。 実際、Railsチームでさえこれを認識し、バージョン5で新しいAPIのみのモードを導入したという点ではやり過ぎです。RubyonRailsのこの新機能により、RailsでのAPIのみのアプリケーションの作成がさらに簡単になりました。そしてより実行可能なオプション。
しかし、他のオプションもあります。 最も注目に値するのは、2つの非常に成熟した強力なgemであり、これらを組み合わせることで、サーバー側APIを作成するための強力なツールを提供します。 彼らはシナトラと続編です。
これらの宝石は両方とも非常に豊富な機能セットを備えています。SinatraはWebアプリケーションのドメイン固有言語(DSL)として機能し、Sequelはオブジェクトリレーショナルマッピング(ORM)レイヤーとして機能します。 それでは、それぞれについて簡単に見てみましょう。
シナトラ
Sinatraは、ラックベースのWebアプリケーションフレームワークです。 Rackは、よく知られているRubyWebサーバーインターフェイスです。 たとえば、Ruby on Railsなどの多くのフレームワークで使用され、WEBrick、Thin、Pumaなどの多くのWebサーバーをサポートします。 Sinatraは、RubyでWebアプリケーションを作成するための最小限のインターフェースを提供し、その最も魅力的な機能の1つは、ミドルウェアコンポーネントのサポートです。 これらのコンポーネントは、アプリケーションとWebサーバーの間にあり、要求と応答を監視および操作できます。
このラック機能を利用するために、SinatraはWebアプリケーションを作成するための内部DSLを定義します。 その哲学は非常に単純です。ルートはHTTPメソッドで表され、その後にパターンに一致するルートが続きます。 要求が処理され、応答が形成されるRubyブロック。
get '/' do 'Hello from sinatra' end
ルートマッチングパターンには、名前付きパラメータを含めることもできます。 ルートブロックが実行されると、パラメータ値がparams
変数を介してブロックに渡されます。
get '/players/:sport_id' do # Parameter value accessible through params[:sport_id] end
マッチングパターンでは、splat演算子*
を使用できます。これにより、 params[:splat]
を介してパラメーター値を使用できるようになります。
get '/players/*/:year' do # /players/performances/2016 # Parameters - params['splat'] -> ['performances'], params[:year] -> 2016 end
これは、ルートマッチングに関連するシナトラの可能性の終わりではありません。 正規表現やカスタムマッチャーを介して、より複雑なマッチングロジックを使用できます。
Sinatraは、REST APIの作成に必要なすべての標準HTTP動詞(Get、Post、Put、Patch、Delete、およびOptions)を理解しています。 ルートの優先順位は、定義された順序によって決定され、要求に一致する最初のルートがその要求を処理するルートになります。
Sinatraアプリケーションは2つの方法で作成できます。 クラシックまたはモジュラースタイルを使用します。 それらの主な違いは、クラシックスタイルでは、Rubyプロセスごとに1つのSinatraアプリケーションしか持てないことです。 その他の違いはごくわずかであるため、ほとんどの場合、無視してデフォルト設定を使用できます。
古典的なアプローチ
従来のアプリケーションの実装は簡単です。 Sinatraをロードし、ルートハンドラーを実装するだけです。
require 'sinatra' get '/' do 'Hello from Sinatra' end
このコードをdemo_api_classic.rb
ファイルに保存すると、次のコマンドを実行してアプリケーションを直接起動できます。
ruby demo_api_classic.rb
ただし、PassengerなどのRackハンドラーを使用してアプリケーションをデプロイする場合は、Rack構成config.ru
ファイルからアプリケーションを開始することをお勧めします。
require './demo_api_classic' run Sinatra::Application
config.ru
ファイルを配置すると、次のコマンドでアプリケーションが起動します。
rackup config.ru
モジュラーアプローチ
モジュラーSinatraアプリケーションは、 Sinatra::Base
またはSinatra::Application
:のいずれかをサブクラス化することによって作成されます。
require 'sinatra' class DemoApi < Sinatra::Application # Application code run! if app_file == $0 end
run!
従来のアプリケーションと同様に、 ruby demo_api.rb
を使用して、アプリケーションを直接起動するために使用されます。 一方、アプリケーションをRackでデプロイする場合、 rackup.ru
のハンドラーの内容は次のとおりである必要があります。
require './demo_api' run DemoApi
続編
続編は、このセットの2番目のツールです。 Ruby on Railsの一部であるActiveRecordとは対照的に、Sequelの依存関係は非常に小さいです。 同時に、それは非常に豊富な機能であり、あらゆる種類のデータベース操作タスクに使用できます。 Sequelは、その単純なドメイン固有言語により、接続の維持、SQLクエリの構築、データベースからのデータのフェッチ(およびデータベースへのデータの送信)に関するすべての問題から開発者を解放します。
たとえば、データベースとの接続を確立するのは非常に簡単です。
DB = Sequel.connect(adapter: :postgres, database: 'my_db', host: 'localhost', user: 'db_user')
connectメソッドは、データベースオブジェクト(この場合はSequel::Postgres::Database
)を返します。これは、生のSQLを実行するためにさらに使用できます。
DB['select count(*) from players']
または、新しいデータセットオブジェクトを作成するには:
DB[:players]
これらのステートメントは両方とも、基本的なSequelエンティティであるデータセットオブジェクトを作成します。
Sequelデータセットの最も重要な機能の1つは、クエリをすぐに実行しないことです。 これにより、後で使用するためにデータセットを保存し、ほとんどの場合、それらをチェーンすることができます。
users = DB[:players].where(sport: 'tennis')
したがって、データセットがデータベースにすぐにヒットしない場合、問題はいつ発生するのかということです。 Sequelは、いわゆる「実行可能メソッド」が使用されている場合、データベースでSQLを実行します。 これらのメソッドは、いくつか例を挙げると、 all
、 each
、 map
、 first
、 last
です。
Sequelは拡張可能であり、その拡張性は、プラグインシステムで補完された小さなコアを構築するという基本的なアーキテクチャ上の決定の結果です。 機能は、実際にはRubyモジュールであるプラグインを介して簡単に追加できます。 最も重要なプラグインはModel
プラグインです。 これは、クラスまたはインスタンスメソッドを単独で定義しない空のプラグインです。 代わりに、クラス、インスタンス、またはモデルのデータセットメソッドを定義する他のプラグイン(サブモジュール)が含まれています。 モデルプラグインを使用すると、Sequelをオブジェクトリレーショナルマッピング(ORM)ツールとして使用でき、「ベースプラグイン」と呼ばれることもあります。
class Player < Sequel::Model end
Sequelモデルは、データベーススキーマを自動的に解析し、すべての列に必要なすべてのアクセサーメソッドを設定します。 テーブル名は複数形であり、モデル名の下線付きバージョンであると想定しています。 この命名規則に従わないデータベースを操作する必要がある場合は、モデルの定義時にテーブル名を明示的に設定できます。
class Player < Sequel::Model(:player) end
これで、バックエンドAPIの構築を開始するために必要なものがすべて揃いました。
APIの構築
コード構造
Railsとは異なり、Sinatraはプロジェクト構造を強制しません。 ただし、メンテナンスと開発を容易にするためにコードを整理することは常に良い習慣であるため、ここでも次のディレクトリ構造を使用してコードを整理します。
project root |-config |-helpers |-models |-routes
アプリケーション構成は、現在の環境のYAML構成ファイルから次のようにロードされます。
Sinatra::Application.config_file File.join(File.dirname(__FILE__), 'config', "#{Sinatra::Application.settings.environment}_config.yml")
デフォルトでは、 Sinatra::Applicationsettings.environment
の値はdevelopment,
あり、 RACK_ENV
環境変数を設定することで変更されます。

さらに、アプリケーションは他の3つのディレクトリからすべてのファイルをロードする必要があります。 これは、次のコマンドを実行することで簡単に実行できます。
%w{helpers models routes}.each {|dir| Dir.glob("#{dir}/*.rb", &method(:require))}
一見すると、このロード方法は便利に見えるかもしれません。 ただし、この1行のコードでは、配列内のディレクトリからすべてのファイルが読み込まれるため、ファイルを簡単にスキップすることはできません。 そのため、より効率的な単一ファイルのロードアプローチを使用します。これは、各フォルダーにマニフェストファイルinit.rb
があり、ディレクトリから他のすべてのファイルをロードすることを前提としています。 また、Rubyロードパスにターゲットディレクトリを追加します。
%w{helpers models routes}.each do |dir| $LOAD_PATH << File.expand_path('.', File.join(File.dirname(__FILE__), dir)) require File.join(dir, 'init') end
このアプローチでは、各init.rb
ファイルでrequireステートメントを維持する必要があるため、もう少し作業が必要ですが、その見返りとして、より詳細な制御が可能になり、マニフェストのinit.rb
ファイルから1つ以上のファイルを削除することで簡単に除外できます。ターゲットディレクトリにあります。
API認証
各APIで最初に必要なのは認証です。 ヘルパーモジュールとして実装します。 完全な認証ロジックは、 helpers/authentication.rb
ファイルにあります。
require 'multi_json' module Sinatra module Authentication def authenticate! client_id = request['client_id'] client_secret = request['client_secret'] # Authenticate client here halt 401, MultiJson.dump({message: "You are not authorized to access this resource"}) unless authenticated? end def current_client @current_client end def authenticated? !current_client.nil? end end helpers Authentication end
ここで行う必要があるのは、ヘルパーマニフェストファイル( helpers/init.rb
)にrequireステートメントを追加してこのファイルをロードし、 authenticate!
を呼び出すことだけです。 リクエストを処理する前に実行されるSinatraのbefore
フックのメソッド。
before do authenticate! end
データベース
次に、アプリケーション用にデータベースを準備する必要があります。 データベースを準備する方法はたくさんありますが、私たちはSequelを使用しているので、移行者を使用して準備するのが自然です。 Sequelには、整数ベースとタイムスタンプベースの2つの移行タイプがあります。 それぞれに長所と短所があります。 この例では、Sequelのタイムスタンプ移行ツールを使用することにしました。これには、移行ファイルのプレフィックスにタイムスタンプを付ける必要があります。 タイムスタンプ移行機能は非常に柔軟性があり、さまざまなタイムスタンプ形式を受け入れることができますが、使用するのは年、月、日、時、分、秒の形式のみです。 2つの移行ファイルは次のとおりです。
# db/migrations/20160710094000_sports.rb Sequel.migration do change do create_table(:sports) do primary_key :id String :name, :null => false end end end # db/migrations/20160710094100_players.rb Sequel.migration do change do create_table(:players) do primary_key :id String :name, :null => false foreign_key :sport_id, :sports end end end
これで、すべてのテーブルを含むデータベースを作成する準備が整いました。
bundle exec sequel -m db/migrations sqlite://db/development.sqlite3
最後に、 models
ディレクトリにモデルファイルsport.rb
とplayer.rb
があります。
# models/sport.rb class Sport < Sequel::Model one_to_many :players def to_api { id: id, name: name } end end # models/player.rb class Player < Sequel::Model many_to_one :sport def to_api { id: id, name: name, sport_id: sport_id } end end
ここでは、モデルの関係を定義する続編の方法を採用しています。ここでは、 Sport
オブジェクトには多くのプレーヤーがいて、 Player
は1つのスポーツしか持てません。 また、各モデルはto_api
メソッドを定義します。このメソッドは、シリアル化する必要のある属性を持つハッシュを返します。 これは、さまざまな形式に使用できる一般的なアプローチです。 ただし、APIでJSON形式のみを使用する場合は、Rubyのto_json
を引数only
で使用して、シリアル化を必要な属性、つまりplayer.to_json(only: [:id, :name, :sport_i])
に制限できます。 もちろん、 Sequel::Model
から継承するBaseModel
を定義し、デフォルトのto_api
メソッドを定義することもできます。このメソッドから、すべてのモデルを継承できます。
これで、実際のAPIエンドポイントの実装を開始できます。
APIエンドポイント
すべてのエンドポイントの定義は、 routes
ディレクトリ内のファイルに保持されます。 ファイルのロードにマニフェストファイルを使用しているため、ルートをリソースごとにグループ化します(つまり、すべてのスポーツ関連ルートをsports.rb
ファイルに保持し、すべてのプレーヤールートをroutes.rb
に保持します)。
# routes/sports.rb class DemoApi < Sinatra::Application get "/sports/?" do MultiJson.dump(Sport.all.map { |s| s.to_api }) end get "/sports/:id" do sport = Sport.where(id: params[:id]).first MultiJson.dump(sport ? sport.to_api : {}) end get "/sports/:id/players/?" do sport = Sport.where(id: params[:id]).first MultiJson.dump(sport ? sport.players.map { |p| p.to_api } : []) end end # routes/players.rb class DemoApi < Sinatra::Application get "/players/?" do MultiJson.dump(Player.all.map { |p| s.to_api }) end get "/players/:id/?" do player = Player.where(id: params[:id]).first MultiJson.dump(player ? player.to_api : {}) end end
1つのスポーツ/sports/:id/players
players内のすべてのプレーヤーを取得するルートのように、ネストされたルートは、他のルートと一緒に配置するか、ネストされたルートのみを含む別のリソースファイルを作成することによって定義できます。
指定されたルートを使用すると、アプリケーションはリクエストを受け入れる準備が整います。
curl -i -XGET 'http://localhost:9292/sports?client_id=<client_id>&client_secret=<client_secret>'
helpers/authentication.rb
ファイルで定義されているアプリケーションの認証システムで要求されているように、要求パラメーターで直接クレデンシャルを渡していることに注意してください。
結論
この単純なサンプルアプリケーションで示されている原則は、すべてのAPIバックエンドアプリケーションに適用されます。 これは、model-view-controller(MVC)アーキテクチャに基づいていませんが、同様の方法で責任を明確に分離しています。 リクエストの処理がSinatraのroutesメソッドで行われている間、完全なビジネスロジックはモデルファイルに保持されます。 ビューが応答のレンダリングに使用されるMVCアーキテクチャとは異なり、このアプリケーションは、ルートメソッドでリクエストを処理するのと同じ場所でそれを実行します。 新しいヘルパーファイルを使用すると、アプリケーションを簡単に拡張して、ページ付けを送信したり、必要に応じて、応答ヘッダーでユーザーに制限情報を要求したりできます。
最終的に、機能を失うことなく、非常にシンプルなツールセットを使用して完全なAPIを構築しました。 依存関係の数が限られているため、アプリケーションのロードと起動がはるかに高速になり、Railsベースのアプリケーションよりもメモリフットプリントがはるかに小さくなります。 したがって、次にRubyで新しいAPIの作業を開始するときは、SinatraとSequelを使用することを検討してください。これらは、このようなユースケースにとって非常に強力なツールです。