كيف يحل Sequel و Sinatra مشكلة واجهة برمجة تطبيقات روبي
نشرت: 2022-03-11مقدمة
في السنوات الأخيرة ، زاد عدد أطر تطبيق JavaScript ذات الصفحة الواحدة وتطبيقات الأجهزة المحمولة بشكل كبير. وهذا يفرض طلبًا متزايدًا مقابل ذلك على واجهات برمجة التطبيقات من جانب الخادم. نظرًا لكون Ruby on Rails أحد أكثر أطر تطوير الويب شيوعًا اليوم ، فهو خيار طبيعي بين العديد من المطورين لإنشاء تطبيقات واجهة برمجة التطبيقات الخلفية.
ومع ذلك ، في حين أن النموذج المعماري لـ Ruby on Rails يجعل من السهل جدًا إنشاء تطبيقات API للجهة الخلفية ، فإن استخدام Rails فقط لواجهة برمجة التطبيقات يعد أمرًا مبالغًا فيه. في الواقع ، إنها مبالغة لدرجة أن فريق ريلز قد أدرك ذلك ، وبالتالي قدم وضعًا جديدًا لواجهة برمجة التطبيقات في الإصدار 5. مع هذه الميزة الجديدة في Ruby on Rails ، أصبح إنشاء تطبيقات API فقط في Rails أسهل. وخيار أكثر قابلية للتطبيق.
لكن هناك خيارات أخرى أيضًا. أبرزها نوعان من الأحجار الكريمة الناضجة جدًا والقوية ، والتي توفر معًا أدوات قوية لإنشاء واجهات برمجة تطبيقات من جانب الخادم. هم سيناترا وسيكسل.
كل من هذه الأحجار الكريمة لديها مجموعة ميزات غنية جدًا: تعمل Sinatra كلغة خاصة بالمجال (DSL) لتطبيقات الويب ، وتعمل Sequel كطبقة رسم الخرائط العلائقية (ORM). لذا ، دعونا نلقي نظرة سريعة على كل منهم.
سيناترا
Sinatra هو إطار تطبيق ويب قائم على الرف. The Rack هو واجهة خادم ويب Ruby معروفة. يتم استخدامه من قبل العديد من الأطر ، مثل Ruby on Rails ، على سبيل المثال ، ويدعم الكثير من خوادم الويب ، مثل WEBrick أو Thin أو Puma. يوفر Sinatra واجهة بسيطة لكتابة تطبيقات الويب بلغة Ruby ، وإحدى ميزاته الأكثر إقناعًا هي دعم مكونات البرامج الوسيطة. تقع هذه المكونات بين التطبيق وخادم الويب ، ويمكنها مراقبة الطلبات والاستجابات ومعالجتها.
لاستخدام ميزة الرف ، يحدد سيناترا 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
هذه ليست نهاية احتمالات سيناترا المتعلقة بمطابقة المسار. يمكنه استخدام منطق مطابقة أكثر تعقيدًا من خلال التعبيرات العادية ، وكذلك المطابقات المخصصة.
يفهم سيناترا جميع أفعال HTTP القياسية اللازمة لإنشاء واجهة برمجة تطبيقات REST: Get و Post و Put و Patch و Delete و Options. يتم تحديد أولويات المسار بالترتيب الذي تم تحديدها به ، والمسار الأول الذي يتطابق مع الطلب هو الذي يخدم هذا الطلب.
يمكن كتابة طلبات سيناترا بطريقتين ؛ باستخدام النمط الكلاسيكي أو المعياري. والفرق الرئيسي بينهما هو أنه مع النمط الكلاسيكي ، لا يمكن أن يكون لدينا سوى تطبيق سيناترا واحد لكل عملية روبي. الاختلافات الأخرى طفيفة لدرجة أنه في معظم الحالات ، يمكن تجاهلها ، ويمكن استخدام الإعدادات الافتراضية.
النهج التقليدي
تنفيذ التطبيق الكلاسيكي واضح ومباشر. علينا فقط تحميل سيناترا وتنفيذ معالجات الطريق:
require 'sinatra' get '/' do 'Hello from Sinatra' end
بحفظ هذا الكود في ملف demo_api_classic.rb
، يمكننا بدء التطبيق مباشرة عن طريق تنفيذ الأمر التالي:
ruby demo_api_classic.rb
ومع ذلك ، إذا كان التطبيق سيتم نشره باستخدام معالجات Rack ، مثل Passenger ، فمن الأفضل بدء تشغيله باستخدام ملف config.ru
بتكوين Rack.
require './demo_api_classic' run Sinatra::Application
مع وجود ملف config.ru
في مكانه ، يبدأ التطبيق بالأمر التالي:
rackup config.ru
نهج وحدات
يتم إنشاء تطبيقات سيناترا المعيارية عن طريق تصنيف فرعي إما 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
تتمة
Sequel هي الأداة الثانية في هذه المجموعة. على عكس ActiveRecord ، وهو جزء من Ruby on Rails ، فإن تبعيات Sequel صغيرة جدًا. في الوقت نفسه ، فهي غنية بالميزات ويمكن استخدامها لجميع أنواع مهام معالجة قاعدة البيانات. بفضل لغته البسيطة المحددة للمجال ، يخفف Sequel المطور من جميع المشاكل المتعلقة بالحفاظ على الاتصالات ، وإنشاء استعلامات SQL ، وجلب البيانات من قاعدة البيانات (وإعادة إرسال البيانات إليها).
على سبيل المثال ، يعد إنشاء اتصال بقاعدة البيانات أمرًا بسيطًا للغاية:
DB = Sequel.connect(adapter: :postgres, database: 'my_db', host: 'localhost', user: 'db_user')
تقوم طريقة الاتصال بإرجاع كائن قاعدة بيانات ، في هذه الحالة ، Sequel::Postgres::Database
، والذي يمكن استخدامه أيضًا لتنفيذ SQL الخام.
DB['select count(*) from players']
بدلاً من ذلك ، لإنشاء كائن مجموعة بيانات جديد:
DB[:players]
كل من هاتين العبارتين تنشئ كائن مجموعة بيانات ، وهو كيان Sequel أساسي.
واحدة من أهم ميزات مجموعة بيانات Sequel هي أنها لا تنفذ الاستعلامات على الفور. هذا يجعل من الممكن تخزين مجموعات البيانات لاستخدامها لاحقًا ، وفي معظم الحالات ، ربطها.
users = DB[:players].where(sport: 'tennis')
لذا ، إذا لم تصل مجموعة البيانات إلى قاعدة البيانات على الفور ، فإن السؤال هو ، متى يتم ذلك؟ يقوم Sequel بتنفيذ SQL في قاعدة البيانات عند استخدام ما يسمى "الأساليب القابلة للتنفيذ". هذه الطرق ، على سبيل المثال لا الحصر ، all
، each
، map
، first
، last
.
Sequel قابل للتوسعة ، وقابليته للتوسعة هي نتيجة لقرار معماري أساسي لبناء نواة صغيرة مكملة بنظام مكون إضافي. تتم إضافة الميزات بسهولة من خلال المكونات الإضافية التي هي في الواقع وحدات Ruby. أهم مكون إضافي هو Model
plugin. إنه مكون إضافي فارغ لا يعرف أي فئة أو عمليات مثيل في حد ذاته. بدلاً من ذلك ، فهو يتضمن ملحقات أخرى (وحدات فرعية) تحدد طرق مجموعة بيانات فئة أو مثيل أو نموذج. يُمكِّن المكون الإضافي للنموذج من استخدام Sequel كأداة رسم خرائط الكائن (ORM) وغالبًا ما يُشار إليه باسم "المكون الإضافي الأساسي".
class Player < Sequel::Model end
يقوم نموذج Sequel تلقائيًا بتوزيع مخطط قاعدة البيانات وإعداد جميع طرق الوصول الضرورية لجميع الأعمدة. يفترض أن اسم الجدول هو صيغة الجمع ونسخة بتسطير أسفل السطر من اسم النموذج. في حالة وجود حاجة للعمل مع قواعد البيانات التي لا تتبع اصطلاح التسمية هذا ، يمكن تعيين اسم الجدول بشكل صريح عند تعريف النموذج.
class Player < Sequel::Model(:player) end
لذلك ، لدينا الآن كل ما نحتاجه لبدء إنشاء واجهة برمجة التطبيقات الخلفية.

بناء API
هيكل الكود
على عكس ريلز ، لا يفرض سيناترا أي هيكل للمشروع. ومع ذلك ، نظرًا لأنه من الممارسات الجيدة دائمًا تنظيم التعليمات البرمجية لتسهيل الصيانة والتطوير ، فسنقوم بذلك هنا أيضًا ، باستخدام بنية الدليل التالية:
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
.
علاوة على ذلك ، يجب أن يقوم تطبيقنا بتحميل جميع الملفات من الأدلة الثلاثة الأخرى. يمكننا القيام بذلك بسهولة عن طريق تشغيل:
%w{helpers models routes}.each {|dir| Dir.glob("#{dir}/*.rb", &method(:require))}
للوهلة الأولى ، قد تبدو طريقة التحميل هذه مريحة. ومع ذلك ، مع هذا السطر الوحيد من الكود ، لا يمكننا تخطي الملفات بسهولة ، لأنه سيتم تحميل جميع الملفات من الدلائل في المصفوفة. هذا هو السبب في أننا سنستخدم نهج تحميل ملف واحد أكثر كفاءة ، والذي يفترض أنه في كل مجلد لدينا ملف بيان init.rb
، والذي يقوم بتحميل جميع الملفات الأخرى من الدليل. أيضًا ، سنضيف دليل هدف إلى مسار تحميل روبي:
%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
، ولكن في المقابل ، نحصل على مزيد من التحكم ، ويمكننا بسهولة ترك ملف واحد أو أكثر خارجًا عن طريق إزالتها من ملف init.rb
البيان في الدليل الهدف.
مصادقة 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
) واستدعاء authenticate!
الطريقة في Sinatra before
الخطاف والتي سيتم تنفيذها قبل معالجة أي طلب.
before do authenticate! end
قاعدة البيانات
بعد ذلك ، يتعين علينا إعداد قاعدة البيانات الخاصة بنا للتطبيق. هناك العديد من الطرق لإعداد قاعدة البيانات ، ولكن نظرًا لأننا نستخدم Sequel ، فمن الطبيعي القيام بذلك باستخدام برامج التهجير. يأتي Sequel بنوعين من المهاجر - يعتمد على الأعداد الصحيحة والطابع الزمني. كل واحد له مزاياه وعيوبه. في مثالنا ، قررنا استخدام مهاجر الطوابع الزمنية Sequel ، والذي يتطلب أن تكون ملفات الترحيل مسبوقة بطابع زمني. مهاجر الطابع الزمني مرن للغاية ويمكنه قبول تنسيقات مختلفة للطوابع الزمنية ، لكننا سنستخدم فقط التنسيق الذي يتكون من السنة والشهر واليوم والساعة والدقيقة والثانية. إليك ملفي الترحيل الخاصين بنا:
# 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
أخيرًا ، لدينا ملفات النموذج sport.rb
و player.rb
في دليل models
.
# 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
أن يمتلك رياضة واحدة فقط. أيضًا ، يحدد كل نموذج طريقة to_api
الخاصة به ، والتي تُرجع تجزئة بسمات تحتاج إلى إجراء تسلسل. هذا نهج عام يمكننا استخدامه لتنسيقات مختلفة. ومع ذلك ، إذا كنا سنستخدم تنسيق JSON فقط في واجهة برمجة التطبيقات الخاصة بنا ، فيمكننا استخدام Ruby's to_json
مع وسيطة only
لتقييد التسلسل إلى السمات المطلوبة ، player.to_json(only: [:id, :name, :sport_i])
. بالطبع ، يمكننا أيضًا تحديد BaseModel
الذي يرث من Sequel::Model
ويحدد طريقة 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
يمكن تحديد المسارات المتداخلة ، مثل المسار الخاص بضم جميع اللاعبين ضمن رياضة /sports/:id/players
، إما عن طريق وضعها مع مسارات أخرى ، أو عن طريق إنشاء ملف موارد منفصل يحتوي فقط على المسارات المتداخلة.
باستخدام المسارات المعينة ، يكون التطبيق جاهزًا الآن لقبول الطلبات:
curl -i -XGET 'http://localhost:9292/sports?client_id=<client_id>&client_secret=<client_secret>'
لاحظ أنه وفقًا لما يتطلبه نظام المصادقة الخاص بالتطبيق المحدد في ملف helpers/authentication.rb
، فإننا نقوم بتمرير بيانات الاعتماد مباشرة في معلمات الطلب.
خاتمة
تنطبق المبادئ الموضحة في هذا المثال البسيط للتطبيق على أي تطبيق للجهة الخلفية لواجهة برمجة التطبيقات. لا يعتمد على بنية وحدة التحكم في عرض النموذج (MVC) ، ومع ذلك فإنه يحافظ على فصل واضح للمسؤوليات بطريقة مماثلة ؛ يتم الاحتفاظ بمنطق الأعمال الكامل في ملفات النماذج أثناء معالجة الطلبات في طرق طرق سيناترا. على عكس هندسة MVC ، حيث يتم استخدام العروض لتقديم الردود ، يقوم هذا التطبيق بذلك في نفس المكان حيث يتعامل مع الطلبات - في طرق المسارات. باستخدام ملفات المساعد الجديدة ، يمكن توسيع التطبيق بسهولة لإرسال ترقيم الصفحات أو ، إذا لزم الأمر ، يحد الطلب من المعلومات إلى المستخدم في رؤوس الاستجابة.
في النهاية ، قمنا ببناء واجهة برمجة تطبيقات كاملة مع مجموعة أدوات بسيطة للغاية ودون فقدان أي وظائف. يساعد العدد المحدود من التبعيات على ضمان تحميل التطبيق وبدء تشغيله بشكل أسرع ، وله مساحة ذاكرة أصغر بكثير مما يمكن أن يحتويه التطبيق المستند إلى ريلز. لذلك ، في المرة القادمة التي تبدأ فيها العمل على واجهة برمجة تطبيقات جديدة في Ruby ، ضع في اعتبارك استخدام Sinatra و Sequel لأنهما أدوات قوية جدًا لحالة الاستخدام هذه.