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.