كيف يحل Sequel و Sinatra مشكلة واجهة برمجة تطبيقات روبي

نشرت: 2022-03-11

مقدمة

في السنوات الأخيرة ، زاد عدد أطر تطبيق JavaScript ذات الصفحة الواحدة وتطبيقات الأجهزة المحمولة بشكل كبير. وهذا يفرض طلبًا متزايدًا مقابل ذلك على واجهات برمجة التطبيقات من جانب الخادم. نظرًا لكون Ruby on Rails أحد أكثر أطر تطوير الويب شيوعًا اليوم ، فهو خيار طبيعي بين العديد من المطورين لإنشاء تطبيقات واجهة برمجة التطبيقات الخلفية.

ومع ذلك ، في حين أن النموذج المعماري لـ Ruby on Rails يجعل من السهل جدًا إنشاء تطبيقات API للجهة الخلفية ، فإن استخدام Rails فقط لواجهة برمجة التطبيقات يعد أمرًا مبالغًا فيه. في الواقع ، إنها مبالغة لدرجة أن فريق ريلز قد أدرك ذلك ، وبالتالي قدم وضعًا جديدًا لواجهة برمجة التطبيقات في الإصدار 5. مع هذه الميزة الجديدة في Ruby on Rails ، أصبح إنشاء تطبيقات API فقط في Rails أسهل. وخيار أكثر قابلية للتطبيق.

لكن هناك خيارات أخرى أيضًا. أبرزها نوعان من الأحجار الكريمة الناضجة جدًا والقوية ، والتي توفر معًا أدوات قوية لإنشاء واجهات برمجة تطبيقات من جانب الخادم. هم سيناترا وسيكسل.

كل من هذه الأحجار الكريمة لديها مجموعة ميزات غنية جدًا: تعمل Sinatra كلغة خاصة بالمجال (DSL) لتطبيقات الويب ، وتعمل Sequel كطبقة رسم الخرائط العلائقية (ORM). لذا ، دعونا نلقي نظرة سريعة على كل منهم.

API مع Sinatra و Sequel: Ruby Tutorial

روبي API على نظام غذائي: إدخال Sequel و Sinatra.
سقسقة

سيناترا

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 ، فإننا نقوم بتمرير بيانات الاعتماد مباشرة في معلمات الطلب.

الموضوعات ذات الصلة: دروس جوهر العنب: كيفية إنشاء واجهة برمجة تطبيقات تشبه REST في روبي

خاتمة

تنطبق المبادئ الموضحة في هذا المثال البسيط للتطبيق على أي تطبيق للجهة الخلفية لواجهة برمجة التطبيقات. لا يعتمد على بنية وحدة التحكم في عرض النموذج (MVC) ، ومع ذلك فإنه يحافظ على فصل واضح للمسؤوليات بطريقة مماثلة ؛ يتم الاحتفاظ بمنطق الأعمال الكامل في ملفات النماذج أثناء معالجة الطلبات في طرق طرق سيناترا. على عكس هندسة MVC ، حيث يتم استخدام العروض لتقديم الردود ، يقوم هذا التطبيق بذلك في نفس المكان حيث يتعامل مع الطلبات - في طرق المسارات. باستخدام ملفات المساعد الجديدة ، يمكن توسيع التطبيق بسهولة لإرسال ترقيم الصفحات أو ، إذا لزم الأمر ، يحد الطلب من المعلومات إلى المستخدم في رؤوس الاستجابة.

في النهاية ، قمنا ببناء واجهة برمجة تطبيقات كاملة مع مجموعة أدوات بسيطة للغاية ودون فقدان أي وظائف. يساعد العدد المحدود من التبعيات على ضمان تحميل التطبيق وبدء تشغيله بشكل أسرع ، وله مساحة ذاكرة أصغر بكثير مما يمكن أن يحتويه التطبيق المستند إلى ريلز. لذلك ، في المرة القادمة التي تبدأ فيها العمل على واجهة برمجة تطبيقات جديدة في Ruby ، ​​ضع في اعتبارك استخدام Sinatra و Sequel لأنهما أدوات قوية جدًا لحالة الاستخدام هذه.