The hidden layer
between the Fachlichkeit and the -ilities
It is part of the DSL lore that before you start building the language
(and then during its development) you “analyze the domain”. What exactly does
this mean?
Understanding
One important aspect of
domain analysis is to build an understanding of the domain. All DSL development
projects I’ve ever been on started with me understanding what the language
should express. I talk to domain experts, look at existing code or other domain
artifacts, if available. A key component in understanding, at least for me, is
to listen actively. Active listening means that the process of
“listening” is actually a conversation: I explain what I’ve understood. I
extrapolate a little bit. And I actively ask for confirmation: “is this what
you meant?” — “Does this mean that … .”
Generalization
An important next step, and
really the starting point of any DSL, is to generalize from the examples that
are provided to me. Building early prototypes helps, of course, because it’s
very easy to talk past each other, especially if you just invented a new
abstraction that nobody else in the room really understands (yet).
Generalization helps me
solidify my understanding, but it also helps reveal outliers that do not fit
the generalization. A key ingredient in domain analysis is to clarify if these
exceptional cases are “bugs” (meaning that we should get rid of them in the new
world) or “features” (meaning that the DSL has to be able to express this case
as well). This really is crucial: classifying too many as valid might make the
language very complicated and hard to use (with too low-level abstractions),
but classifying too many as “out” might make the language useless for the
real-world. There’s no simple rule of thumb how to do this, but thinking about
this consciously is a good start.
Hidden Complexities
Here is maybe the most important
ingredient of good domain analysis, and the point I am trying to make in this
post. During domain analysis you have to look behind the obvious. Because this
is often where the gold is buried. Let me give you a few examples:
Consider a DSL for payroll. The obvious part is
that you have to perform calculations. And that you have to make decisions,
which, looking at the “configuration” of an employee, decide on the correct
amount of tax deductions or benefits. The non-obvious aspects include the fact
that the rules that govern those calculations and decisions change over time as
the applicable law evolves. And that the data on which these calculations and
decisions operate change over time as well: a salary is a temporal quantity, so
is the marriage status or the affiliation with a particular religion. The key
to making progress for a DSL (and to convince stakeholders that this is
worthwhile) was to put first-class support for these two key features into the
DSL.
Consider mobile apps for
coaching patients through a
treatment. At the core of these apps are algorithms that collect data, analyse
it, and make decisions on what to recommend ot the patient (“take one more
pill”, “eat less”, “call your medical team”). It’s obvious that the core of the
DSL has support for decision making, for analyzing data, and for expressing the
recommendations. Behind the obvious, important challenges relate to dealing
with the passing of time (“if the blood sure has been above X for three days”),
representing the abstractions with notations that medical experts can (and are
willing to) understand, and separating the “happy flow” from all the
exceptional and corner cases (because doctors, at least initially, want to
focus on the happy flow).
A DSL for public
administration workflows is my third example. I am not going to go into the
details of what the system does on the surface, but it turned out that dealing
with snapshots of data (before it then changes) to be able to reproduce
calculations in the past is key. And variant handling is another complexity:
how do you define variants of the behavior for the different cities you’re
going to sell this system to?
In each of these three
examples, the key to reaching one of the main goals of a DSL — more concise and
analyzable programs — crucially relies on identifying these “hidden”
challenges. I call them “hidden” because if you look at example artifacts and
talk to domain experts, they are not likely to identify those as key issues.
They often experience pain from tackling them every day, but they are often
unable to articulate them as a core feature of a to-be-built DSL or as a
central challenge in the domain. It is your job as the guy with the DSL
experience to reveal and systematize them.
From two layers to three
As DSL people we have always
known this basic separation of concerns: put the Fachlichkeit, the core domain
logic, into the models (using a DSL to concisely express it) and then let the
generator and runtime environments deal with the technical concerns. Technical
concerns in the payroll and public administration case are scalability,
reliability, and security — the Fachlichkeit will be executed in a distributed
microservice infrastructure. For the medical case a core challenge is to run
the algorithm on the two major mobile phone manufacturers (iOS and Android),
but also to ensure it works the same way on the huge variety of variants
especially in the Android category. And of course safety is crucial: the
language and generator infrastructure must not introduce additional unmitigated
risks.
Here is the important
realization, one that has become clear to me only over the last few years:
these hidden domain concerns are not obviously Fachlichkeit, but they can also
not be delegated to be handled, somehow, by the infrastructure. And it turns
out that these concerns are often the source of major complexity in a domain! A
DSL that helps the business people credibly deal with this complexity can make
a huge difference.
In fact, in several cases,
the code that people now write with the DSL is so much simpler than the legacy
code that people kinda asked “Why do we need a DSL? It’s simple!”. Well, it’s
only simple because the DSL handles the complexity in a
meaningful way.
Summary
Instead of looking at the
established two layer separation between domain logic (expressible by the DSL)
and the -ilities (handled by the generator and runtime), look at it as three
layers: the third layer is in the middle between the two and concerns the “hidden”
complexities in a domain. During the domain analysis, try hard to dig up these
complexities. Very often, these are the key to make real progress.