Αναφορικά με τον ορισμό, η DSL (Domain Specific Language) είναι μια γλώσσα υπολογιστών που εξειδικεύεται σε έναν συγκεκριμένο τομέα εφαρμογών. Αυτό σημαίνει ότι αναπτύσσεται για να ικανοποιήσει συγκεκριμένες ανάγκες.
Διαβάζοντας αυτό το άρθρο θα μάθετε τι είναι η DSL και τι κοινό έχει με τη Ruby.
DSL, καλωσορίστε!
Αναφορικά με τον ορισμό, η DSL (Domain Specific Language) είναι μια γλώσσα υπολογιστών που εξειδικεύεται σε έναν συγκεκριμένο τομέα εφαρμογών. Αυτό σημαίνει ότι αναπτύσσεται για να ικανοποιήσει συγκεκριμένες ανάγκες. Υπάρχουν δύο τύποι DSL:
-
Ένα εξωτερικό DSL η οποία απαιτεί το δικό της συντακτικό αναλυτή. Ένα καλό γνωστό παράδειγμα μπορεί να είναι η γλώσσα SQL - επιτρέπει την αλληλεπίδραση με τη βάση δεδομένων σε μια γλώσσα στην οποία η βάση δεδομένων δεν έχει δημιουργηθεί.
-
Ένα εσωτερικό DSL η οποία δεν έχει τη δική της σύνταξη αλλά χρησιμοποιεί τη σύνταξη μιας δεδομένης γλώσσας προγραμματισμού.
Όπως πιθανώς μπορείτε να μαντέψετε, θα επικεντρωθούμε στον δεύτερο τύπο DSL.
Τι κάνει;
Βασικά, κάνοντας χρήση ενός μεταπρογραμματισμού Ruby, επιτρέπει τη δημιουργία της δικής σας μίνι γλώσσας. Ο μεταπρογραμματισμός είναι μια τεχνική προγραμματισμού που επιτρέπει τη συγγραφή μιας κωδικός δυναμικά κατά την εκτέλεση (on the fly). Μπορεί να μην το γνωρίζετε αυτό, αλλά πιθανότατα χρησιμοποιείτε πολλές διαφορετικές DSL καθημερινά. Για να καταλάβετε τι μπορεί να κάνει μια DSL, ας ρίξουμε μια ματιά σε μερικά παραδείγματα παρακάτω - όλα αυτά έχουν ένα κοινό στοιχείο, αλλά μπορείτε να το επισημάνετε;
Δρομολόγηση Rails
Rails.application.routes.draw do
root to: 'home#index'
resources :users do
get :search, on: :collection
end
end
```
Κάθε άτομο που έχει χρησιμοποιήσει ποτέ το Rails γνωρίζει ένα config/routes.rb αρχείο όπου ορίζουμε τις διαδρομές της εφαρμογής (αντιστοίχιση μεταξύ των ρημάτων HTTP και των διευθύνσεων URL με τις ενέργειες του ελεγκτή). Αλλά έχετε αναρωτηθεί ποτέ πώς λειτουργεί αυτό; Στην πραγματικότητα, πρόκειται απλώς για κώδικα Ruby.
Εργοστάσιο Bot
FactoryBot.define do
factory :user do
company
sequence(:email) { |i| "user_#{i}@test.com" }
sequence(:first_name) { |i| "Χρήστης #{i}" }
last_name 'Test'
role 'manager'
end
end
Η συγγραφή δοκιμών συχνά απαιτεί την κατασκευή αντικειμένων. Ως εκ τούτου, για να αποφύγετε τη σπατάλη χρόνου, θα ήταν πραγματικά καλή ιδέα να απλοποιήσετε τη διαδικασία όσο το δυνατόν περισσότερο. Αυτό είναι το ζητούμενο του FactoryBot κάνει - εύκολες στη μνήμη λέξεις-κλειδιά και ένας τρόπος περιγραφής ενός αντικειμένου.
Sinatra
require 'sinatra/base'
class WebApplication < Sinatra::Base
get '/' do
'Hello world'
end
end
```
Sinatra είναι ένα πλαίσιο που σας επιτρέπει να δημιουργείτε εφαρμογές ιστού από το μηδέν. Θα μπορούσε να είναι ευκολότερο να ορίσετε τη μέθοδο αίτησης, τη διαδρομή και την απόκριση;
Άλλα παραδείγματα DSL θα μπορούσαν να είναι Τσουγκράνα, RSpec ή Ενεργό αρχείο. Το βασικό στοιχείο κάθε DSL είναι η χρήση των μπλοκ.
Χρόνος κατασκευής
Ώρα να καταλάβετε τι κρύβεται κάτω από την κουκούλα και πώς μπορεί να μοιάζει η εφαρμογή.
Ας υποθέσουμε ότι έχουμε μια εφαρμογή που αποθηκεύει δεδομένα για διάφορα προϊόντα. Θέλουμε να την επεκτείνουμε δίνοντας τη δυνατότητα εισαγωγής δεδομένων από ένα αρχείο που ορίζει ο χρήστης. Επίσης, το αρχείο θα πρέπει να επιτρέπει τον δυναμικό υπολογισμό των τιμών, αν χρειαστεί. Για να το πετύχουμε αυτό, αποφασίζουμε να δημιουργήσουμε DSL.
Ένα απλό προϊόν η αναπαράσταση μπορεί να έχει τα ακόλουθα χαρακτηριστικά (product.rb):
class Product
attr_accessor :name, :description, :price
end
Αντί να χρησιμοποιήσουμε μια πραγματική βάση δεδομένων, θα προσομοιώσουμε απλώς τη λειτουργία της (fake_products_database.rb):
class FakeProductsDatabase
def self.store(product)
puts [product.name, product.description, product.price].join(' - ')
end
end
Τώρα, θα δημιουργήσουμε μια κλάση που θα είναι υπεύθυνη για την ανάγνωση και το χειρισμό του αρχείου που περιέχει δεδομένα προϊόντων (dsl/data_importer.rb):
ενότητα Dsl
κλάση DataImporter
module Σύνταξη
def add_product(&block)
FakeProductsDatabase.store product(&block)
end
private
def product(&block)
ProductBuilder.new.tap { |b| b.instance_eval(&block) }.product
end
end
include Σύνταξη
def self.import_data(file_path)
new.instance_eval File.read(file_path)
end
end
end
```
Η κλάση έχει μια μέθοδο με όνομα import_data η οποία αναμένει μια διαδρομή αρχείου ως όρισμα. Το αρχείο διαβάζεται και το αποτέλεσμα μεταβιβάζεται στην εντολή instance_eval η οποία καλείται στην περίπτωση της κλάσης. Τι κάνει; Αξιολογεί τη συμβολοσειρά ως κώδικα Ruby στο πλαίσιο της περίπτωσης. Αυτό σημαίνει αυτο θα είναι η περίπτωση της DataImporter κατηγορία. Χάρη στο γεγονός ότι είμαστε σε θέση να ορίσουμε την επιθυμητή σύνταξη/κλειδιά (για καλύτερη αναγνωσιμότητα η σύνταξη ορίζεται ως ενότητα). Όταν η add_product η μέθοδος καλείται, το μπλοκ που δίνεται για τη μέθοδο αξιολογείται από την ProductBuilder παράδειγμα που χτίζει Προϊόν παράδειγμα. ProductBuilder περιγράφεται παρακάτω (dsl/product_builder.rb):
ενότητα Dsl
class ProductBuilder
ATTRIBUTES = %i[όνομα περιγραφή τιμή].freeze
attr_reader :product
def initialize
@product = Product.new
end
ATTRIBUTES.each do |attribute|
define_method(attribute) do |arg = nil, &block|
value = block.is_a?(Proc) ? block.call : arg
product.public_send("#{attribute}=", value)
τέλος
end
end
end
```
Η κλάση ορίζει τη σύνταξη που επιτρέπεται μέσα σε add_product μπλοκ. Με λίγο μεταπρογραμματισμό προσθέτει μεθόδους που αποδίδουν τιμές στα χαρακτηριστικά του προϊόντος. Αυτές οι μέθοδοι υποστηρίζουν επίσης τη μεταβίβαση ενός μπλοκ αντί μιας άμεσης τιμής, ώστε να μπορεί να υπολογιστεί μια τιμή κατά την εκτέλεση. Χρησιμοποιώντας τον αναγνώστη χαρακτηριστικών, είμαστε σε θέση να λάβουμε ένα κατασκευασμένο προϊόν στο τέλος.
Τώρα, ας προσθέσουμε το σενάριο εισαγωγής (import_job.rb):
requirerelative 'dsl/dataimporter'
requirerelative 'dsl/productbuilder'
requirerelative 'fakeproductsdatabase'
requirerelative 'product'
Dsl::DataImporter.import_data(ARGV[0])
```
Και τέλος - χρησιμοποιώντας την DSL μας - ένα αρχείο με τα δεδομένα των προϊόντων (dataset.rb):
```ruby
add_product do
name 'Charger'
description 'Life saving'
price 19.99
end
add_product do
name 'Car wreck'
description { "Συντρίβεται στο #{Time.now.strftime('%F %T')}" }
price 0.01
end
add_product do
όνομα 'Lockpick'
description 'Doors shall not close'
price 7.50
end
```
Για να εισάγουμε τα δεδομένα πρέπει απλώς να εκτελέσουμε μια εντολή:
ruby import_job.rb dataset.rb
Και το αποτέλεσμα είναι..
Φορτιστής - σωτήρια για τη ζωή - 19.99
Συντριβή αυτοκινήτου - Καταστράφηκε στις 2018-12-09 09:47:42 - 0.01
Κλειδαριά - Οι πόρτες δεν κλείνουν - 7.5
..επιτυχία!
Συμπέρασμα
Κοιτάζοντας όλα τα παραπάνω παραδείγματα, δεν είναι δύσκολο να παρατηρήσει κανείς τις δυνατότητες που προσφέρει το DSL. Η DSL επιτρέπει την απλούστευση ορισμένων λειτουργιών ρουτίνας, κρύβοντας όλη την απαιτούμενη λογική και αποκαλύπτοντας στο χρήστη μόνο τις πιο σημαντικές λέξεις-κλειδιά. Σας επιτρέπει να αποκτήσετε ένα υψηλότερο επίπεδο αφαίρεσης και προσφέρει ευέλικτες δυνατότητες χρήσης (κάτι που είναι ιδιαίτερα πολύτιμο όσον αφορά την επαναχρησιμοποίηση). Από την άλλη πλευρά, η προσθήκη DSL στο έργο θα πρέπει πάντα να εξετάζεται καλά - μια υλοποίηση που χρησιμοποιεί μεταπρογραμματισμό είναι σίγουρα πολύ πιο δύσκολο να κατανοηθεί και να συντηρηθεί. Επιπλέον, απαιτεί σταθερή σουίτα δοκιμών λόγω του δυναμισμού της. Η τεκμηρίωση της DSL προάγει την ευκολότερη κατανόησή της, οπότε σίγουρα αξίζει να γίνει. Παρόλο που η υλοποίηση της δικής σας DSL μπορεί να είναι ικανοποιητική, καλό είναι να θυμάστε ότι πρέπει να αποδίδει.
Ενδιαφερθήκατε για το θέμα; Αν ναι, ενημερώστε μας - θα σας πούμε για το DSL που δημιουργήσαμε πρόσφατα για να καλύψουμε τις απαιτήσεις σε ένα από τα έργα μας.