Enligt definitionen är DSL (Domain Specific Language) ett datorspråk som är specialiserat på en viss applikationsdomän. Det innebär att det är utvecklat för att tillgodose specifika behov.
Genom att läsa den här artikeln kommer du att lära dig vad DSL är och vad det har gemensamt med Ruby.
DSL, säg välkommen!
Enligt definitionen är DSL (Domain Specific Language) ett datorspråk som är specialiserat på en viss applikationsdomän. Det innebär att det är utvecklat för att tillgodose specifika behov. Det finns två typer av DSL:
-
En extern DSL som kräver sin egen syntaxparser. Ett välkänt exempel kan vara SQL-språket - det gör det möjligt att interagera med en databas på ett språk som databasen inte skapades på.
-
En intern DSL som inte har någon egen syntax utan istället använder syntaxen i ett visst programmeringsspråk.
Som du förmodligen kan gissa kommer vi att hålla fokus på den andra DSL-typen.
Vad gör den?
Genom att använda Ruby-metaprogrammering kan du skapa ditt eget minispråk. Metaprogrammering är en programmeringsteknik som gör det möjligt att skriva en kod dynamiskt vid körning (i farten). Du kanske inte är medveten om detta, men du använder förmodligen många olika DSL:er varje dag. För att förstå vad en DSL kan göra, låt oss ta en titt på några exempel nedan - alla dessa har ett gemensamt element, men kan du peka ut det?
Rails routing
Rails.application.routes.draw do
rot till: 'hem#index'
resurser :användare do
get :search, on: :samling
slut
slut
```
Varje person som någonsin har använt Rails känner till en config/routes.rb fil där vi definierar applikationsvägar (mappning mellan HTTP-verb och URL:er till kontrolleråtgärder). Men har du någonsin undrat hur det fungerar? I själva verket är det bara Ruby-kod.
Fabrik Bot
FactoryBot.define do
fabrik :användare do
företag
sequence(:email) { |i| "user_#{i}@test.com" }
sequence(:first_name) { |i| "Användare #{i}" }
last_name "Test
roll "chef
slut
slut
Att skriva tester kräver ofta att man tillverkar objekt. För att undvika slöseri med tid skulle det därför vara en riktigt bra idé att förenkla processen så mycket som möjligt. Det är vad Fabriksrobot does - nyckelord som är lätta att komma ihåg och ett sätt att beskriva ett objekt.
Sinatra
kräver 'sinatra/base'
klassen WebApplication < Sinatra::Base
get '/' do
'Hej världen'
slut
slut
```
Sinatra är ett ramverk som gör det möjligt att skapa webbapplikationer från grunden. Kan det vara enklare att definiera request method, path och response?
Andra DSL-exempel kan vara Rake, RSpec eller Aktivt register. Det viktigaste inslaget i varje DSL är användning av block.
Byggtid
Dags att förstå vad som döljer sig under huven och hur implementeringen kan se ut.
Låt oss anta att vi har en applikation som lagrar data om olika produkter. Vi vill utöka den genom att ge möjlighet att importera data från en användardefinierad fil. Filen ska också göra det möjligt att beräkna värden dynamiskt om det behövs. För att uppnå det bestämmer vi oss för att skapa DSL.
En enkel Produkt representation kan ha följande attribut (produkt.rb):
klass Produkt
attr_accessor :namn, :beskrivning, :pris
slut
Istället för att använda en riktig databas kommer vi bara att simulera dess arbete (falsk_produktdatabas.rb):
klass FakeProductsDatabase
def self.store(produkt)
puts [produkt.namn, produkt.beskrivning, produkt.pris].join(' - ')
slut
slut
Nu ska vi skapa en klass som ska ansvara för att läsa och hantera filer som innehåller produktdata (dsl/data_importör.rb):
modul Dsl
klass DataImporterare
modul Syntax
def add_product(&block)
FakeProductsDatabase.store produkt(&block)
slut
privat
def produkt(&block)
ProductBuilder.new.tap { |b| b.instance_eval(&block) }.product
slut
slut
include Syntax
def self.import_data(fil_ sökväg)
new.instance_eval File.read(file_path)
slut
slut
slut
```
Klassen har en metod som heter import_data som förväntar sig en filsökväg som argument. Filen läses och resultatet skickas till instans_eval metod som anropas på klassinstansen. Vad är det den gör? Den utvärderar strängen som en Ruby-kod inom instansens kontext. Detta innebär att själv kommer att vara en instans av DataImporterare klass. Tack vare detta faktum kan vi definiera önskad syntax/nyckelord (för bättre läsbarhet definieras syntaxen som en modul). När lägg till_produkt metoden kallas det block som ges för metoden utvärderas av Produktbyggare instans som bygger Produkt instans. Produktbyggare klassen beskrivs nedan (dsl/produkt_byggare.rb):
modul Dsl
klass ProduktByggare
ATTRIBUTES = %i[namn beskrivning pris].freeze
attr_läsare :produkt
def initialisera
@produkt = Produkt.ny
slut
ATTRIBUTES.each do |attribut|
define_method(attribute) do |arg = nil, &block|
värde = block.is_a?(Proc) ? block.call : arg
product.public_send("#{attribut}=", värde)
slut
slut
slut
slut
```
Klassen definierar syntax som är tillåten inom lägg till_produkt block. Med lite metaprogrammering läggs det till metoder som tilldelar värden till produktattribut. Dessa metoder har också stöd för att skicka ett block i stället för ett direkt värde, så att ett värde kan beräknas vid körning. Med hjälp av attributläsaren kan vi få en byggd produkt i slutet.
Låt oss nu lägga till importskriptet (import_jobb.rb):
kravrelaterad 'dsl/dataimporter'
Kravrelaterat 'dsl/produktbyggare'
Kravrelaterat 'fakeproductsdatabase'
kravrelativ 'produkt'
Dsl::DataImporter.import_data(ARGV[0])
```
Och slutligen - med hjälp av vår DSL - en fil med produktdata (dataset.rb):
```ruby
add_product do
namn 'Laddare'
beskrivning 'Livräddande'
pris 19,99
slut
add_product do
namn 'Bilvrak'
description { "Havererade vid #{Time.now.strftime('%F %T')}" }
pris 0,01
slut
add_product do
namn 'Lockpick'
beskrivning "Dörrar ska inte stängas
pris 7,50
slut
```
För att importera data behöver vi bara utföra ett kommando:
ruby import_job.rb dataset.rb
Och resultatet är...
Laddare - Livräddande - 19,99
Bilvrak - Förlist 2018-12-09 09:47:42 - 0,01
Lockpick - Dörrar ska inte stängas - 7,5
...framgång!
Slutsats
Genom att titta på alla exemplen ovan är det inte svårt att se vilka möjligheter DSL erbjuder. DSL gör det möjligt att förenkla vissa rutinoperationer genom att dölja all nödvändig logik bakom och endast exponera de viktigaste nyckelorden för användaren. Det ger en högre abstraktionsnivå och flexibla användningsmöjligheter (vilket är särskilt värdefullt när det gäller återanvändbarhet). Å andra sidan, om du lägger till DSL i din projekt bör alltid övervägas noga - en implementation som använder metaprogrammering är definitivt mycket svårare att förstå och underhålla. Dessutom kräver den en solid testsvit på grund av sin dynamik. Att dokumentera DSL gör det lättare att förstå, så det är definitivt värt att göra. Även om det kan vara givande att implementera sin egen DSL är det bra att komma ihåg att det måste löna sig.
Blev du intresserad av ämnet? Om så är fallet, låt oss veta - vi kommer att berätta om DSL som vi nyligen har skapat för att uppfylla kraven i ett av våra projekt.