Sequel ve Sinatra, Ruby'nin API Problemini Nasıl Çözer?

Yayınlanan: 2022-03-11

Tanıtım

Son yıllarda, JavaScript tek sayfalık uygulama çerçevelerinin ve mobil uygulamaların sayısı önemli ölçüde artmıştır. Bu, sunucu tarafı API'leri için buna bağlı olarak artan bir talep getirir. Ruby on Rails, günümüzün en popüler web geliştirme çerçevelerinden biri olduğu için, arka uç API uygulamaları oluşturmak için birçok geliştirici arasında doğal bir seçimdir.

Yine de Ruby on Rails mimari paradigması, arka uç API uygulamaları oluşturmayı oldukça kolaylaştırırken, Rails'i yalnızca API için kullanmak aşırıya kaçar. Aslında, Rails ekibinin bile bunu fark etmesi ve bu nedenle sürüm 5'te yeni bir yalnızca API modu sunması o kadar abartılı ki Ruby on Rails'deki bu yeni özellik sayesinde, Rails'de yalnızca API uygulamaları oluşturmak daha da kolaylaştı. ve daha uygun bir seçenek.

Ama başka seçenekler de var. En dikkate değer olanı, bir arada sunucu tarafı API'leri oluşturmak için güçlü araçlar sağlayan çok olgun ve güçlü iki değerli taştır. Bunlar Sinatra ve Sequel'dir.

Bu taşların her ikisi de çok zengin bir özellik kümesine sahiptir: Sinatra, web uygulamaları için alana özgü dil (DSL) olarak hizmet eder ve Sequel, nesne-ilişkisel haritalama (ORM) katmanı olarak hizmet eder. Öyleyse, her birine kısaca bir göz atalım.

Sinatra ve Sequel ile API: Ruby Eğitimi

Diyette Ruby API: Sequel ve Sinatra'yı tanıtmak.
Cıvıldamak

Sinatra

Sinatra, Rack tabanlı web uygulaması çerçevesidir. Rack, iyi bilinen bir Ruby web sunucusu arayüzüdür. Örneğin Ruby on Rails gibi birçok çerçeve tarafından kullanılır ve WEBrick, Thin veya Puma gibi birçok web sunucusunu destekler. Sinatra, Ruby'de web uygulamaları yazmak için minimal bir arayüz sağlar ve en çekici özelliklerinden biri ara yazılım bileşenlerini desteklemesidir. Bu bileşenler, uygulama ile web sunucusu arasında yer alır ve istekleri ve yanıtları izleyebilir ve değiştirebilir.

Bu Raf özelliğini kullanmak için Sinatra, web uygulamaları oluşturmak için dahili DSL'yi tanımlar. Felsefesi çok basittir: Rotalar HTTP yöntemleriyle temsil edilir, ardından bir kalıpla eşleşen bir rota gelir. İsteğin işlendiği ve yanıtın oluşturulduğu bir Ruby bloğu.

 get '/' do 'Hello from sinatra' end

Yol eşleştirme modeli ayrıca adlandırılmış bir parametre içerebilir. Rota bloğu yürütüldüğünde, bloğa params değişkeni aracılığıyla bir parametre değeri iletilir.

 get '/players/:sport_id' do # Parameter value accessible through params[:sport_id] end

Eşleşen desenler, parametre değerlerini params[:splat] aracılığıyla kullanılabilir hale getiren splat operatörünü * kullanabilir.

 get '/players/*/:year' do # /players/performances/2016 # Parameters - params['splat'] -> ['performances'], params[:year] -> 2016 end

Bu, Sinatra'nın rota eşleştirmeyle ilgili olasılıklarının sonu değil. Normal ifadeler ve özel eşleştiriciler aracılığıyla daha karmaşık eşleştirme mantığı kullanabilir.

Sinatra, bir REST API oluşturmak için gereken tüm standart HTTP fiillerini anlar: Get, Post, Put, Patch, Delete ve Options. Rota öncelikleri, tanımlandıkları sıraya göre belirlenir ve bir istekle eşleşen ilk rota, o isteğe hizmet eden rotadır.

Sinatra uygulamaları iki şekilde yazılabilir; klasik veya modüler stil kullanarak. Aralarındaki temel fark, klasik stil ile Ruby işlemi başına yalnızca bir Sinatra uygulamasına sahip olmamızdır. Diğer farklılıklar, çoğu durumda göz ardı edilebilecek kadar küçüktür ve varsayılan ayarlar kullanılabilir.

Klasik yaklaşım

Klasik uygulamayı uygulamak basittir. Sadece Sinatra'yı yüklememiz ve rota işleyicilerini uygulamamız gerekiyor:

 require 'sinatra' get '/' do 'Hello from Sinatra' end

Bu kodu demo_api_classic.rb dosyasına kaydederek, aşağıdaki komutu çalıştırarak doğrudan uygulamayı başlatabiliriz:

 ruby demo_api_classic.rb

Ancak, uygulama Passenger gibi Raf işleyicileri ile dağıtılacaksa, Raf yapılandırması config.ru dosyasıyla başlatmak daha iyidir.

 require './demo_api_classic' run Sinatra::Application

config.ru dosyası yerindeyken, uygulama aşağıdaki komutla başlatılır:

 rackup config.ru

Modüler yaklaşım

Modüler Sinatra uygulamaları, Sinatra::Base veya Sinatra::Application alt sınıflarına göre oluşturulur:

 require 'sinatra' class DemoApi < Sinatra::Application # Application code run! if app_file == $0 end

run! klasik uygulamada olduğu gibi ruby demo_api.rb ile uygulamayı doğrudan başlatmak için kullanılır. Öte yandan, uygulama Rack ile dağıtılacaksa, rackup.ru işleyici içeriği şöyle olmalıdır:

 require './demo_api' run DemoApi

devam filmi

Sequel, bu setteki ikinci araçtır. Ruby on Rails'in bir parçası olan ActiveRecord'un aksine, Sequel'in bağımlılıkları çok küçüktür. Aynı zamanda, oldukça zengin özelliklere sahiptir ve her türlü veritabanı işleme görevi için kullanılabilir. Sequel, alana özgü basit diliyle, geliştiriciyi bağlantıları sürdürmek, SQL sorguları oluşturmak, veritabanından veri almak (ve veriyi geri göndermek) ile ilgili tüm sorunlardan kurtarır.

Örneğin, veritabanı ile bağlantı kurmak çok basittir:

 DB = Sequel.connect(adapter: :postgres, database: 'my_db', host: 'localhost', user: 'db_user')

connect yöntemi bir veritabanı nesnesi döndürür, bu durumda Sequel::Postgres::Database , ham SQL'i yürütmek için daha fazla kullanılabilir.

 DB['select count(*) from players']

Alternatif olarak, yeni bir veri kümesi nesnesi oluşturmak için:

 DB[:players]

Bu ifadelerin her ikisi de temel bir Sequel varlığı olan bir veri kümesi nesnesi oluşturur.

En önemli Sequel veri kümesi özelliklerinden biri, sorguları hemen yürütmemesidir. Bu, veri kümelerini daha sonra kullanmak üzere saklamayı ve çoğu durumda zincirlemeyi mümkün kılar.

 users = DB[:players].where(sport: 'tennis')

Öyleyse, bir veri kümesi veritabanına hemen ulaşmazsa, soru şu ki, ne zaman olur? Sequel, sözde "yürütülebilir yöntemler" kullanıldığında veritabanında SQL'i yürütür. Bu yöntemler, birkaçını belirtmek gerekirse, all , each , map , first ve last .

Sequel genişletilebilir ve genişletilebilirliği, bir eklenti sistemiyle tamamlanan küçük bir çekirdek oluşturmaya yönelik temel bir mimari kararın sonucudur. Özellikler, aslında Ruby modülleri olan eklentiler aracılığıyla kolayca eklenir. En önemli eklenti Model eklentisidir. Kendi başına herhangi bir sınıf veya örnek yöntemi tanımlamayan boş bir eklentidir. Bunun yerine, bir sınıf, örnek veya model veri kümesi yöntemlerini tanımlayan diğer eklentileri (alt modüller) içerir. Model eklentisi, Sequel'in nesne ilişkisel haritalama (ORM) aracı olarak kullanılmasını sağlar ve genellikle "temel eklenti" olarak adlandırılır.

 class Player < Sequel::Model end

Sequel modeli, veritabanı şemasını otomatik olarak ayrıştırır ve tüm sütunlar için gerekli tüm erişimci yöntemlerini ayarlar. Tablo adının çoğul olduğunu ve model adının altı çizili bir versiyonu olduğunu varsayar. Bu adlandırma kuralına uymayan veritabanları ile çalışılması gerektiğinde, model tanımlanırken tablo adı açıkça ayarlanabilir.

 class Player < Sequel::Model(:player) end

Artık arka uç API'sini oluşturmaya başlamak için ihtiyacımız olan her şeye sahibiz.

API'yi Oluşturma

Kod Yapısı

Rails'in aksine Sinatra herhangi bir proje yapısı dayatmaz. Ancak, daha kolay bakım ve geliştirme için kodu düzenlemek her zaman iyi bir uygulama olduğundan, bunu burada da aşağıdaki dizin yapısıyla yapacağız:

 project root |-config |-helpers |-models |-routes

Uygulama yapılandırması, geçerli ortam için YAML yapılandırma dosyasından aşağıdakilerle yüklenecektir:

 Sinatra::Application.config_file File.join(File.dirname(__FILE__), 'config', "#{Sinatra::Application.settings.environment}_config.yml")

Varsayılan olarak, Sinatra::Applicationsettings.environment değeri development, ve RACK_ENV ortam değişkeni ayarlanarak değiştirilir.

Ayrıca uygulamamız diğer üç dizindeki tüm dosyaları yüklemelidir. Bunu çalıştırarak kolayca yapabiliriz:

 %w{helpers models routes}.each {|dir| Dir.glob("#{dir}/*.rb", &method(:require))}

İlk bakışta, bu şekilde yükleme uygun görünebilir. Ancak, kodun bu tek satırıyla, dizideki dizinlerdeki tüm dosyaları yükleyeceğinden dosyaları kolayca atlayamayız. Bu nedenle, her klasörde dizinden diğer tüm dosyaları yükleyen bir bildirim dosyamız init.rb olduğunu varsayan daha verimli bir tek dosya yükleme yaklaşımı kullanacağız. Ayrıca Ruby yükleme yoluna bir hedef dizin ekleyeceğiz:

 %w{helpers models routes}.each do |dir| $LOAD_PATH << File.expand_path('.', File.join(File.dirname(__FILE__), dir)) require File.join(dir, 'init') end

Bu yaklaşım biraz daha fazla çalışma gerektiriyor, çünkü her init.rb dosyasında gerekli ifadeleri korumamız gerekiyor, ancak karşılığında daha fazla kontrol alıyoruz ve bir veya daha fazla dosyayı manifest init.rb dosyasından kaldırarak kolayca dışarıda bırakabiliyoruz. hedef dizinde.

API Kimlik Doğrulaması

Her API'de ihtiyacımız olan ilk şey kimlik doğrulamadır. Yardımcı modül olarak uygulayacağız. Tam kimlik doğrulama mantığı, helpers/authentication.rb dosyasında olacaktır.

 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

Şimdi tek yapmamız gereken, helper manifest dosyasına ( helpers/init.rb ) bir require ifadesi ekleyerek bu dosyayı yüklemek ve authenticate! Sinatra'nın herhangi bir isteği işleme koymadan before yürütülecek olan kancadan önceki yöntemi.

 before do authenticate! end

Veri tabanı

Ardından, veritabanımızı uygulama için hazırlamamız gerekiyor. Veritabanını hazırlamanın birçok yolu var ama Sequel kullandığımız için migratörler kullanarak yapmamız doğal. Sequel, iki göçmen türüyle gelir - tamsayı ve zaman damgası tabanlı. Her birinin avantajları ve dezavantajları vardır. Örneğimizde, taşıma dosyalarının önüne bir zaman damgası eklenmesini gerektiren Sequel'in zaman damgası taşıyıcısını kullanmaya karar verdik. Zaman damgası taşıyıcı çok esnektir ve çeşitli zaman damgası biçimlerini kabul edebilir, ancak yalnızca yıl, ay, gün, saat, dakika ve saniyeden oluşan birini kullanacağız. İşte iki taşıma dosyamız:

 # 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

Artık tüm tabloları içeren bir veritabanı oluşturmaya hazırız.

 bundle exec sequel -m db/migrations sqlite://db/development.sqlite3

Son olarak, models dizininde sport.rb ve player.rb model dosyalarına sahibiz.

 # 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

Burada, Sport nesnesinin birçok oyuncuya sahip olduğu ve Player yalnızca bir spora sahip olabileceği model ilişkilerini tanımlamanın bir Devam yolu kullanıyoruz. Ayrıca, her model, serileştirilmesi gereken özniteliklere sahip bir karma döndüren to_api yöntemini tanımlar. Bu, çeşitli biçimler için kullanabileceğimiz genel bir yaklaşımdır. Ancak, API'mizde yalnızca bir JSON biçimi kullanacaksak, Ruby'nin to_json , serileştirmeyi gerekli özniteliklerle sınırlamak için only argümanla kullanabiliriz, yani player.to_json(only: [:id, :name, :sport_i]) . Elbette, Sequel::Model devralan ve tüm modellerin devralabileceği varsayılan bir to_api yöntemini tanımlayan bir BaseModel tanımlayabiliriz.

Artık gerçek API uç noktalarını uygulamaya başlayabiliriz.

API Uç Noktaları

Tüm uç noktaların tanımını routes dizini içindeki dosyalarda tutacağız. Dosyaları yüklemek için bildirim dosyalarını kullandığımızdan, rotaları kaynaklara göre gruplayacağız (yani, sporla ilgili tüm rotaları sports.rb dosyasında, tüm oyuncuların rotalarını routes.rb dosyasında vb. tutacağız).

 # 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

Tüm oyuncuları tek bir sport /sports/:id/players players içine almak için olduğu gibi iç içe rotalar, onları diğer rotalarla bir araya getirerek veya yalnızca iç içe rotaları içerecek ayrı bir kaynak dosyası oluşturarak tanımlanabilir.

Belirlenen rotalarla uygulama artık istekleri kabul etmeye hazırdır:

 curl -i -XGET 'http://localhost:9292/sports?client_id=<client_id>&client_secret=<client_secret>'

Uygulamanın helpers/authentication.rb dosyasında tanımlanan kimlik doğrulama sisteminin gerektirdiği şekilde, kimlik bilgilerini doğrudan istek parametrelerinde ilettiğimizi unutmayın.

İlgili: Üzüm Mücevheri Eğitimi: Ruby'de REST-Benzeri Bir API Nasıl Oluşturulur

Çözüm

Bu basit örnek uygulamada gösterilen ilkeler, herhangi bir API arka uç uygulaması için geçerlidir. Model-view-controller (MVC) mimarisine dayalı değildir, ancak benzer şekilde sorumlulukların net bir şekilde ayrılmasını sağlar; Sinatra'nın rota yöntemlerinde isteklerin işlenmesi yapılırken tüm iş mantığı model dosyalarında tutulur. Görünümlerin yanıt oluşturmak için kullanıldığı MVC mimarisinin aksine, bu uygulama bunu istekleri işlediği yerde yapar - rota yöntemlerinde. Yeni yardımcı dosyalarla, uygulama, sayfalandırma göndermek için kolayca genişletilebilir veya gerekirse, yanıt başlıklarındaki bilgileri kullanıcıya geri gönderir.

Sonunda, çok basit bir araç seti ile ve herhangi bir işlevsellik kaybetmeden eksiksiz bir API oluşturduk. Sınırlı sayıda bağımlılık, uygulamanın çok daha hızlı yüklenmesini ve başlatılmasını sağlamaya yardımcı olur ve Rails tabanlı bir uygulamanın sahip olacağından çok daha küçük bir bellek ayak izine sahiptir. Bu nedenle, bir dahaki sefere Ruby'de yeni bir API üzerinde çalışmaya başladığınızda, böyle bir kullanım durumu için çok güçlü araçlar olduklarından Sinatra ve Sequel'i kullanmayı düşünün.