Laut Definition ist eine DSL (Domain Specific Language) eine Computersprache, die auf einen bestimmten Anwendungsbereich spezialisiert ist. Das bedeutet, dass sie entwickelt wird, um spezifische Bedürfnisse zu befriedigen.
In diesem Artikel erfahren Sie, was die DSL ist und was sie mit Ruby gemeinsam hat.
DSL, herzlich willkommen!
Laut Definition ist eine DSL (Domain Specific Language) eine Computersprache, die auf einen bestimmten Anwendungsbereich spezialisiert ist. Das bedeutet, dass sie entwickelt wird, um spezifische Bedürfnisse zu befriedigen. Es gibt zwei Arten von DSL:
-
Eine extern DSL, die einen eigenen Syntaxparser benötigt. Ein bekanntes Beispiel ist die SQL-Sprache - sie ermöglicht die Interaktion mit Datenbanken in einer Sprache, in der die Datenbank nicht erstellt wurde.
-
Eine intern DSL, die selbst keine eigene Syntax hat, sondern stattdessen eine Syntax einer bestimmten Programmiersprache verwendet.
Wie Sie sich wahrscheinlich denken können, werden wir uns auf den zweiten DSL-Typ konzentrieren.
Was bewirkt es?
Grundsätzlich kann man mit der Ruby-Metaprogrammierung seine eigene Mini-Sprache erstellen. Metaprogrammierung ist eine Programmiertechnik, die es erlaubt, eine Code dynamisch zur Laufzeit (on the fly). Sie sind sich dessen vielleicht nicht bewusst, aber wahrscheinlich verwenden Sie jeden Tag viele verschiedene DSLs. Um zu verstehen, was eine DSL leisten kann, sehen wir uns ein paar Beispiele an - alle haben ein gemeinsames Element, aber können Sie es erkennen?
Rails-Routing
Rails.application.routes.draw do
root to: 'home#index'
resources :users do
get :search, on: :collection
end
end
```
Jede Person, die jemals Rails benutzt hat, kennt eine config/routes.rb Datei, in der wir Anwendungsrouten (Zuordnungen zwischen HTTP-Verben und URLs zu Controller-Aktionen) definieren. Aber haben Sie sich jemals gefragt, wie das funktioniert? Im Grunde ist es nur Ruby-Code.
Fabrik-Bot
FactoryBot.define do
factory :user do
Firma
sequence(:email) { |i| "user_#{i}@test.com" }
sequence(:first_name) { |i| "user #{i}" }
letzter_name 'Test'
Rolle 'Manager'
end
end
Das Schreiben von Tests erfordert oft die Herstellung von Objekten. Um Zeitverschwendung zu vermeiden, wäre es daher eine wirklich gute Idee, den Prozess so weit wie möglich zu vereinfachen. Das ist es, was die FactoryBot tut - leicht zu merkende Schlüsselwörter und eine Möglichkeit, ein Objekt zu beschreiben.
Sinatra
erfordern 'sinatra/base'
Klasse WebAnwendung < Sinatra::Base
get '/' do
'Hallo Welt'
end
end
```
Sinatra ist ein Framework, mit dem Sie Webanwendungen von Grund auf erstellen können. Könnte es einfacher sein, Anfragemethode, Pfad und Antwort zu definieren?
Andere DSL-Beispiele könnten sein Harke, RSpec oder Aktiver Datensatz. Das Schlüsselelement jeder DSL ist die Verwendung von Blöcken.
Bauzeit
Zeit zu verstehen, was sich unter der Haube verbirgt und wie die Umsetzung aussehen kann.
Nehmen wir an, wir haben eine Anwendung, die Daten über verschiedene Produkte speichert. Wir möchten sie um die Möglichkeit erweitern, Daten aus einer benutzerdefinierten Datei zu importieren. Außerdem soll die Datei die Möglichkeit bieten, Werte bei Bedarf dynamisch zu berechnen. Um dies zu erreichen, beschließen wir, eine DSL zu erstellen.
Eine einfache Produkt Darstellung kann folgende Attribute haben (produkt.rb):
Klasse Produkt
attr_accessor :name, :beschreibung, :preis
end
Anstatt eine echte Datenbank zu verwenden, werden wir ihre Arbeit nur simulieren (fake_products_database.rb):
class FakeProductsDatabase
def self.store(produkt)
setzt [produkt.name, produkt.beschreibung, produkt.preis].join(' - ')
end
end
Nun werden wir eine Klasse erstellen, die für das Lesen und Verarbeiten von Dateien mit Produktdaten zuständig ist (dsl/data_importer.rb):
Modul Dsl
Klasse DataImporter
Modul Syntax
def add_product(&block)
FakeProductsDatabase.store product(&block)
end
privat
def produkt(&block)
ProductBuilder.new.tap { |b| b.instance_eval(&block) }.product
end
end
include Syntax
def self.import_data(file_path)
new.instance_eval File.read(file_path)
end
end
end
```
Die Klasse hat eine Methode namens import_data die einen Dateipfad als Argument erwartet. Die Datei wird gelesen und das Ergebnis wird an den instance_eval Methode, die in der Klasseninstanz aufgerufen wird. Was macht sie? Sie wertet die Zeichenkette als Ruby-Code im Kontext der Instanz aus. Das bedeutet selbst wird die Instanz von DataImporter Klasse. Dank der Tatsache, dass wir in der Lage sind, gewünschte Syntax/Schlüsselwörter zu definieren (für eine bessere Lesbarkeit ist die Syntax als Modul definiert). Wenn die add_product Methode aufgerufen wird, wird der für die Methode angegebene Block ausgewertet durch ProduktBaukasten Instanz, die die Produkt Instanz. ProduktBaukasten Klasse wird im Folgenden beschrieben (dsl/product_builder.rb):
Modul Dsl
Klasse ProduktBaukasten
ATTRIBUTES = %i[Name Beschreibung Preis].freeze
attr_leser :produkt
def initialisieren
@Produkt = Produkt.neu
end
ATTRIBUTES.each do |attribute|
define_method(attribute) do |arg = nil, &block|
Wert = block.is_a?(Proc) ? block.call : arg
Produkt.public_send("#{Attribut}=", Wert)
end
end
end
end
```
Die Klasse definiert die zulässige Syntax innerhalb von add_product Block. Mit ein wenig Metaprogrammierung werden Methoden hinzugefügt, die den Produktattributen Werte zuweisen. Diese Methoden unterstützen auch die Übergabe eines Blocks anstelle eines direkten Wertes, so dass ein Wert zur Laufzeit berechnet werden kann. Mit dem Attribut-Reader können wir am Ende ein gebautes Produkt erhalten.
Fügen wir nun das Import-Skript hinzu (import_job.rb):
requirerelativ 'dsl/dataimporter'
requirerelativ 'dsl/productbuilder'
requirerelativ 'fakeproductsdatabase'
requirerelativ 'produkt'
Dsl::DataImporter.import_data(ARGV[0])
```
Und schließlich - unter Verwendung unserer DSL - eine Datei mit Produktdaten (dataset.rb):
```ruby
add_product do
name 'Ladegerät'
description 'Lebensrettend'
Preis 19,99
end
add_product do
name 'Autowrack'
description { "Wrack um #{Time.now.strftime('%F %T')}" }
Preis 0,01
end
add_product do
name 'Dietrich'
description 'Türen sollen sich nicht schließen'
Preis 7,50
end
```
Um die Daten zu importieren, müssen wir nur einen Befehl ausführen:
ruby import_job.rb dataset.rb
Und das Ergebnis ist...
Ladegerät - Lebensrettend - 19.99
Autowrack - Zu Schrott gefahren am 2018-12-09 09:47:42 - 0.01
Dietrich - Türen sollen nicht schließen - 7.5
...Erfolg!
Schlussfolgerung
Wenn man sich die obigen Beispiele ansieht, ist es nicht schwer zu erkennen, welche Möglichkeiten DSL bietet. DSL ermöglicht es, einige Routineoperationen zu vereinfachen, indem die gesamte erforderliche Logik dahinter verborgen wird und dem Benutzer nur die wichtigsten Schlüsselwörter offengelegt werden. Sie ermöglicht eine höhere Abstraktionsebene und bietet flexible Einsatzmöglichkeiten (was besonders wertvoll im Hinblick auf die Wiederverwendbarkeit ist). Andererseits kann das Hinzufügen von DSL zu Ihrer Projekt sollte immer gut überlegt sein - eine Implementierung mit Metaprogrammierung ist definitiv viel schwieriger zu verstehen und zu warten. Außerdem erfordert sie aufgrund ihrer Dynamik eine solide Test-Suite. Die Dokumentation der DSL trägt zum besseren Verständnis bei und ist daher auf jeden Fall lohnenswert. Auch wenn die Implementierung einer eigenen DSL lohnend sein kann, sollte man nicht vergessen, dass sie sich auszahlen muss.
Haben Sie Interesse an dem Thema gefunden? Wenn ja, lassen Sie es uns wissen - wir werden Ihnen von DSL erzählen, das wir kürzlich für die Anforderungen in einem unserer Projekte entwickelt haben.