Productivity has improved each time programming languages have raised the level of abstraction. This trend continues today with languages that narrow the scope they address -- referred to as domain-specific languages (DSLs). However, many of these DSLs are built by developers for developers and tend to concentrate on the solution domain, like code.
Even better results can be achieved when a DSL is closely aligned with the problem domain -- raising abstraction beyond the code. Such languages foster collaboration, enable domain experts to verify and validate their designs, and, in many cases, allow the generation of code, configurations, tests, reports, and more.
Let me illustrate this with an example from the medical field. Imagine a machine designed to create custom medicines via a mixing process. The machine has a needle that moves along a transport belt, three input cups for ingredients that are mixed in internal vibrating cups during the mixing process, and two types of filters that can be applied when using ingredients. Occasionally, the needle needs to be cleaned as well.
A doctor, nurse, or lab technician, as domain experts in this case, could describe a part of the mixing process for a specific medicine as: "Take from the second cup 5 units with filter A and put 2 units to cup 6 and 3 units to cup 7 and then clean the needle."
This textual DSL, with just four command types, is simple and clearly raises the level of abstraction compared to traditional programming languages. However, it still has limitations. For example, it requires the user to remember the needle's position and manually calculate the units to be moved.
Even worse, this DSL allows the creation of mixing processes that would make no sense or even dangerous, such as:
And there are countless other similar issues like ruining the filter with cleaning fluid, hitting the needle against the safety shutters, cleaning the needle while it still contains medicine, or even specifying a mixing process that will never produce any viable medicine. This language largely ignores the problem domain.
While we can expect testing to catch the above-mentioned issues, some of them require domain-specific knowledge. For instance, can we apply the filter when adding ingredients to internal cups, or should it only be applied to the output cup? Or does it depend on the type of ingredient?
A good domain-specific language that focuses on the problem domain incorporates domain rules, reducing the need for testing by making many typical errors impossible to specify. More importantly, a DSL that aligns closely with the problem domain enables domain experts to verify, validate, or even directly specify the wanted functionality.
So, what would a DSL targeting the problem domain look like? To enable collaboration and ensure domain experts can participate, the language must use the same terms doctors and lab technicians use, such as "take" and "clean" -- as in the original problem description quoted earlier. An example of this language is shown below. It specifies the same mixing process discussed earlier. Most likely, any domain expert familiar with the machine could read the model and check the mixing process for accuracy.
Additionally, this language enforces many of the domain rules mentioned earlier, like preventing the needle from being cleaned if the previous operation has been taking medicine into it. From this model, we can still generate the code, including the textual DSL shown earlier.
While this language improves the situation, it still has limitations. Users still need to manually calculate the needle's position, ensure the correct cup is used next, and verify that the process will actually produce any outcome -- a medicine.
As there's room for further improvement, we can raise the abstraction further by proposing a language like below.
This DSL describes the same portion of the mixing process as the previous examples, but now there's no need to calculate positions, and explicitly showing the cups makes the model self-explanatory for domain experts. It is also important to notice that by raising the level of abstraction and embedding domain rules in the language, models become easier to create and easier to modify. For example, while the previous DSL required specifying 16 items for the small mixing process, this version only needs 8. The first textual DSL had 12 commands with 12 parameters.
Although this example is small, the language can scale to handle larger processes. It can also be extended with additional features of the domain, such as wait times, mandatory safety procedures (e.g., closing the safety shutters to prevent anyone from reaching inside while the machine is operating), and more. The figure below illustrates a slightly larger mixing process, and from this model, code and other artifacts can still be automatically generated.
By raising the level of abstraction closer to the problem domain, the effort to create desired functionality becomes faster. Collaboration is also easier, as domain experts can read, verify, and validate the model. In many cases, and depending on the case and DSL, they can even create the models themselves.
In all three cases, the specification created with the DSL can be translated to the running program code. The effort to create each of the three small DSLs along with tooling was less than 1 hour each, but the results they can provide are very different.
If you are the only person using the DSL it does not matter so much, but if there are multiple people that need to follow the domain rules, or you expect that domain experts should be better involved in development, then create languages for problem domain rather than for solution domain.
The example of medical mixing shows just a small language, but the idea scales well to 100 times bigger languages and naturally to other domains, too. Based on my experience from various cases the effort to implement real-world DSLs with proper tool support is about 1-3 weeks. Below are four examples from various domains that enable non-programmers to read, verify, validate, and even create formal specifications that are applied to generate code: 1) heating system (top left), 2) automation of assembly lines (top right), 3) fish farm automation systems (bottom left) and 4) web app testing. These languages are illustrated as diagrams and maps because the problems they address are easy to express with these representations, but language can show models as a matrix, table, text, or their mixture -- all depends on the problem domain.
Domain-specific languages require tooling -- both to define the languages and to use them. When creating DSLs for domain experts, it's unrealistic to expect them to work with IDEs or tools typically used by programmers. Instead, they need something that is easy to learn and use. This is especially important because domain experts usually don't necessarily interact with their DSLs on a daily basis, unlike developers with programming languages.
Domain experts also expect that collaboration, reviewing, and managing changes within the DSL will be simpler and more aligned with the problem domain, rather than focusing on tracking and merging changes through character-level differences in files. In this sense, both the DSLs and their tooling, as well as the usage processes, need to be tailored to the specific needs of domain experts.
Domain-specific languages have become widely used, but too often they focus on the solution domain rather than the problem domain. Since the effort to create a DSL and build its tooling is quite the same, we should prioritize creating languages for domain experts, not just for programmers.
When language is closely aligned with the problem domain, many typical development tasks -- such as requirements specification, checking, and validation -- can be handled by domain experts. In fact, domain experts can often create the specifications themselves, which can then be used to generate code, configurations, tests, deployment instructions, and more.
This approach leads to better quality, enables early feedback, and significantly improves collaboration and productivity -- just as raising the level of abstraction has always done.