APIs. A DSL contains the essence of
the business rules, so a DSL user can
focus on a very small surface area of
the code base to model a problem domain artifact.
˲ ˲ DSL-based development can scale.
With a nontrivial domain model, DSL-based development can provide higher payoffs than typical programming
models. You need to invest some time
up front to design and implement
the DSL, but then it can be used productively by a mass of programmers,
many of whom may not be experts in
the underlying host language.
Disadvantages of using a DSL
As with any development model, DSL-based development is not without its
share of pitfalls. Your project can end
up as a complete mess by using badly
designed DSLs. Some of these disadvantages are:
˲ ˲ A hard design problem. Like API
design, DSL design is for experts.
You need to understand the domain
and usage pattern of target users and
make the APIs expressive to the right
level of abstraction. Not every member
of your team can deliver good-quality
DSL design.
˲ ˲ Up-front cost. Unless the project
is at least of moderate complexity,
designing DSLs may not be cost effective. The up-front cost incurred may
offset the time saved from enhanced
productivity in the later stages of the
development cycle.
˲ ˲ A tendency to use multiple languages. Unless carefully controlled,
this polyglot programming can lead
to a language cacophony and result in
bloated design.
Structure of a DSL
Here, we look at how to design an internal DSL and embed it within an
underlying host language. We address
the generic anatomy of an embedded DSL and discuss how to keep the
DSL syntax decoupled from the core
domain model. Finally, we develop a
sample DSL embedded in Scala.
A linguistic abstraction on top of a
semantic model. A DSL offers specialized syntactic constructs that model
the daily language of a business user.
This expressiveness is implemented
as a lightweight syntactic construct on
top of a rich domain model. Figure 1
It’s common
knowledge that
most projects that
fail do so because
they lack a proper
communication
structure between
the business
users and the
implementers.
provides a diagram of this anatomy.
In the figure, the base abstractions
refer to the domain model designed
using the idioms of the underlying
host language. The base abstractions
are implemented independent of the
DSL that will eventually sit on top of
them. This makes it possible to host
multiple DSLs on top of a single domain model. Consider the following
example of a DSL that models an instruction to do a security trade in a
stock exchange:
new _ trade ‘T-12435’ for account ‘acc-123’
to buy 100 shares of ‘IBM’,
at UnitPrice= 100, Principal=12000,
Tax=500
This is an internal DSL embedded within Ruby as the host language
and is very similar to the way a trader
speaks at a trading desk. Note that
since it’s an embedded DSL, it can
use the complete infrastructure that
Ruby offers such as syntax processing,
exception handling, and garbage collection.
The entities are named using a vocabulary a trader understands. Figure
2 annotates the DSL, showing some
of the domain vocabulary it uses and
some of the “bubble words” we have
introduced for the user, giving it more
of an English-like feeling.
To implement this DSL, you need
an underlying domain model consisting of a set of abstractions in Ruby.
This is what we call the semantic model
(or domain model). The previous DSL
code snippet interacts with the semantic model through a custom-built
interpreter specific to the language
we offer to our users. This helps decouple the model from the language
designed on top of it. This is one of the
best practices to follow when designing a DSL.
Developing an Embedded DSL
An embedded DSL inherits the infrastructure of an existing host language,
adapting it in ways that help you abstract the domain you are modeling.
As previously mentioned, you build
the DSL as an interpreter over the core
domain abstractions that you develop
using the syntax and semantics of the
underlying language.
Choosing the host language. A DSL