I henhold til definitionen er DSL (Domain Specific Language) et computersprog, der er specialiseret til et bestemt anvendelsesområde. Det betyder, at det er udviklet til at opfylde specifikke behov.
Ved at læse denne artikel vil du lære, hvad DSL er, og hvad det har til fælles med Ruby.
DSL, sig velkommen!
I henhold til definitionen er DSL (Domain Specific Language) et computersprog, der er specialiseret til et bestemt anvendelsesområde. Det betyder, at det er udviklet til at opfylde specifikke behov. Der findes to typer DSL:
-
En ekstern DSL, som kræver sin egen syntaksparser. Et velkendt eksempel er SQL-sproget - det gør det muligt at interagere med en database på et sprog, som databasen ikke er skabt på.
-
En Internt DSL, som ikke selv har sin egen syntaks, men i stedet bruger et givet programmeringssprogs syntaks.
Som du nok kan gætte, vil vi fokusere på den anden DSL-type.
Hvad gør den?
Grundlæggende kan man skabe sit eget minisprog ved at bruge Ruby-metaprogrammering. Metaprogrammering er en programmeringsteknik, der gør det muligt at skrive et Kode dynamisk på kørselstidspunktet (on the fly). Du er måske ikke klar over det, men du bruger sikkert mange forskellige DSL'er hver dag. For at forstå, hvad et DSL kan, kan vi se på et par eksempler nedenfor - de har alle ét fælles element, men kan du udpege det?
Rails-routing
Rails.application.routes.draw do
rod til: 'home#index'
ressourcer :brugere do
get :search, on: :collection
end
end
```
Alle, der nogensinde har brugt Rails, kender en config/routes.rb fil, hvor vi definerer applikationsruter (kortlægning mellem HTTP-verber og URL'er til controller-handlinger). Men har du nogensinde undret dig over, hvordan det fungerer? Det er faktisk bare Ruby-kode.
Fabriksrobot
FactoryBot.define do
factory :user do
company
sequence(:email) { |i| "user_#{i}@test.com" }
sequence(:first_name) { |i| "Bruger #{i}" }
efternavn 'Test'
rolle 'manager'
slut
end
At skrive test kræver ofte, at man fremstiller objekter. For at undgå spild af tid ville det derfor være en rigtig god idé at forenkle processen så meget som muligt. Det er, hvad FactoryBot gør - nøgleord, der er nemme at huske, og en måde at beskrive et objekt på.
Sinatra
kræver 'sinatra/base'
klasse WebApplication < Sinatra::Base
get '/' do
'Hello world'
slut
slut
```
Sinatra er en ramme, som giver dig mulighed for at skabe webapplikationer fra bunden. Kunne det være nemmere at definere request method, path og response?
Andre DSL-eksempler kunne være Rive, RSpec eller Aktiv rekord. Det vigtigste element i hvert DSL er brugen af blokke.
Byggetid
Det er tid til at forstå, hvad der gemmer sig under motorhjelmen, og hvordan implementeringen kan se ud.
Lad os antage, at vi har en applikation, som gemmer data om forskellige produkter. Vi ønsker at udvide den ved at give mulighed for at importere data fra en brugerdefineret fil. Filen skal også gøre det muligt at beregne værdier dynamisk, hvis det er nødvendigt. For at opnå det beslutter vi at oprette DSL.
En simpel produkt repræsentation kan have følgende attributter (produkt.rb):
klasse Produkt
attr_accessor :navn, :beskrivelse, :pris
slut
I stedet for at bruge en rigtig database vil vi bare simulere dens arbejde (fake_products_database.rb):
class FakeProductsDatabase
def self.store(produkt)
puts [product.name, product.description, product.price].join(' - ')
end
end
Nu vil vi oprette en klasse, der er ansvarlig for at læse og håndtere filer, der indeholder produktdata (dsl/data_importer.rb):
modul Dsl
klasse DataImporter
modul Syntaks
def add_product(&block)
FakeProductsDatabase.store product(&block)
end
privat
def product(&block)
ProductBuilder.new.tap { |b| b.instance_eval(&block) }.product
slut
end
include Syntaks
def self.import_data(file_path)
new.instance_eval File.read(file_path)
slut
end
end
```
Klassen har en metode, der hedder import_data som forventer en filsti som argument. Filen læses, og resultatet sendes til instans_evaluering metode, som kaldes på klassens instans. Hvad er det, den gør? Den evaluerer strengen som en Ruby-kode inden for instansens kontekst. Det betyder, at sig selv vil være forekomsten af DataImporter klasse. Takket være det faktum, at vi kan definere den ønskede syntaks/nøgleord (for bedre læsbarhed er syntaksen defineret som et modul). Når tilføj_produkt metoden kaldes, evalueres den blok, der er givet til metoden, af Produktbygger instans, som bygger Produkt eksempel. Produktbygger klassen er beskrevet nedenfor (dsl/product_builder.rb):
modul Dsl
klasse ProductBuilder
ATTRIBUTES = %i[navn beskrivelse pris].freeze
attr_reader :product
def initialize
@product = Produkt.nyt
slut
ATTRIBUTES.each do |attribute|
define_method(attribute) do |arg = nil, &block|
value = block.is_a?(Proc) ? block.call : arg
product.public_send("#{attribut}=", værdi)
slut
slut
end
end
```
Klassen definerer den tilladte syntaks inden for tilføj_produkt blok. Med lidt metaprogrammering tilføjer den metoder, som tildeler værdier til produktattributter. Disse metoder understøtter også overførsel af en blok i stedet for en direkte værdi, så en værdi kan beregnes på kørselstidspunktet. Ved hjælp af attributlæseren kan vi få et bygget produkt til sidst.
Lad os nu tilføje importscriptet (import_job.rb):
requirerelative 'dsl/dataimporter'
kræverrelativ 'dsl/productbuilder'
requirerelative 'fakeproductsdatabase'
requirerelative 'produkt'
Dsl::DataImporter.import_data(ARGV[0])
```
Og endelig - ved hjælp af vores DSL - en fil med produktdata (dataset.rb):
```ruby
add_product do
navn 'Oplader'
beskrivelse 'Livreddende'
pris 19.99
slut
add_product do
navn 'Bilvrag'
description { "Vraget ved #{Time.now.strftime('%F %T')}" }
pris 0,01
end
add_product do
navn 'Lockpick'
beskrivelse 'Døre skal ikke kunne lukkes'
pris 7,50
end
```
For at importere data skal vi bare udføre én kommando:
ruby import_job.rb dataset.rb
Og resultatet er...
Oplader - Livreddende - 19.99
Bilvrag - Vraget kl. 2018-12-09 09:47:42 - 0.01
Lockpick - Døre skal ikke lukkes - 7.5
...succes!
Konklusion
Ved at se på alle eksemplerne ovenfor er det ikke svært at se, hvilke muligheder DSL giver. DSL gør det muligt at forenkle nogle rutineoperationer ved at skjule al den nødvendige logik bagved og kun vise brugeren de vigtigste nøgleord. Det giver dig mulighed for at opnå et højere abstraktionsniveau og tilbyder fleksible anvendelsesmuligheder (hvilket er særligt værdifuldt med hensyn til genanvendelighed). På den anden side kan du ved at tilføje DSL til din projekt bør altid være velovervejet - en implementering, der bruger metaprogrammering, er helt sikkert meget sværere at forstå og vedligeholde. Desuden kræver den en solid testpakke på grund af dens dynamik. Dokumentation af DSL gør det lettere at forstå, så det er bestemt værd at gøre. Selvom det kan være givende at implementere sit eget DSL, er det godt at huske, at det skal kunne betale sig.
Blev du interesseret i emnet? Hvis ja, så lad os det vide - vi vil fortælle dig om DSL, som vi for nylig har udviklet for at opfylde kravene i et af vores projekter.