Skip to main content
Blog10 min read

DSLs that give domain experts ownership

The highest-leverage work of my career has been designing small declarative languages that let domain specialists own their own rules.

The highest-leverage work I have ever delivered did not look like engineering at the time. It looked like sitting in a room with underwriters arguing about what a question on a form actually meant, and writing down their answers in a shape a machine could later read back to them. The code that came out of those rooms was small. The thing that mattered was who owned it afterwards.

At BrightInsight we built an indemnity-insurance product on a JSON-based domain-specific language. The rules — what questions an applicant saw, which earlier answers gated which later questions, how the answers combined into an indemnity decision — lived as data, not as code. Underwriters created new products and modified existing ones by editing structured documents that the engine consumed. The application binary did not know, and did not need to know, that a new product had been launched on Tuesday. It just loaded the latest definitions and ran them deterministically.

That last word is the one that matters in this industry. Indemnity insurance carries a legal compliance obligation that, for any defined period, the same answers to the same questionnaire must produce the same decision. Same inputs, same outputs, every time, traceable on demand. Traditional code release cycles fight against this constraint, because every refactor, every dependency bump, every clever optimisation is a potential silent change in behaviour. The usual compromise is to slow releases down and pile up ceremony around them, which is how regulated software ends up six months behind its market.

We did the opposite. We released to production daily. We could release daily because the parts of the system that needed to be legally deterministic were not in the release at all — they were in the data, version-controlled separately, and replayed against every previously answered questionnaire in CI as a regression gate. If a change anywhere in the engine or the rule set produced a different decision for a historical questionnaire, the build failed. The rule was simple: you cannot ship a change that retroactively changes a decision we have already given a customer.

The engineering job, once that frame was set, narrowed into something almost boring. Hold the engine boringly stable. Hold the schema boringly stable. Hold the determinism guarantees boringly stable. Let the underwriters move quickly on top.

That arrangement is what I mean by giving domain experts ownership. It is not the same as putting a low-code form-builder in front of a business user. It is not "no-code." The DSL was a real language, with a grammar and a type system and an opinion about what made a well-formed product. Underwriters had to learn it, the way they had already learned the legalese of policy wordings — a constrained, formal, technical idiom that exists because the domain is itself constrained, formal and technical. What the DSL gave them was a way to express the rules they already held in their heads without a translation layer in the middle. Without me. Without a ticket queue. Without an engineering sprint between idea and live product.

I have seen the inverse arrangement many times. A product manager writes a Word document describing a new rule. An engineer reads it, asks three clarifying questions, gets two answers and a guess on the third. The engineer encodes a slightly-wrong rule. A QA person tests it against the slightly-wrong spec. Six weeks later the underwriter notices, in production, that the third case is misbehaving. The fix takes another sprint. Everyone is professionally polite about it. Nobody owns the rule end-to-end, which means nobody is fully accountable for it, which means the feedback loop is measured in weeks instead of hours.

The DSL collapsed that loop. An underwriter could draft a new product, run it against the regression suite locally, see exactly which historical decisions would change (none, in a clean change; some, deliberately, in an intentional policy update), and commit it. The engineering team's role shifted from "translator and gatekeeper" to "language designer and platform operator." We owned the grammar, the evaluator, the test harness and the audit trail. They owned the products.

The earlier draft of this idea, for me, was at Thoughtworks, on a different underwriting platform. We built a smaller, less ambitious DSL for underwriters over a polyglot microservices stack — .NET Core, NancyFX, Go, Node.js — with contract testing via Pact holding the seams together. The DSL there was iterated alongside the domain experts from the start. Each fortnight a new piece of grammar emerged because an underwriter had tried to say something the previous version did not let them say. The platform team's posture, I remember vividly, was to resist the natural engineer's instinct to generalise. We added the verb the underwriter needed, in the shape they used in their daily work, and nothing more. The DSL grew slowly and stayed legible because of that discipline.

Looking back, the Thoughtworks engagement was where I learned that a DSL is fundamentally a piece of language design, not a piece of software design. The hardest decisions are about vocabulary, not implementation. What do you call this concept? Does the noun the underwriters use in conversation match the noun in the DSL? When a clinician or an underwriter or an accountant reads a rule cold, can they tell whether it does what it claims to do? If they cannot, the DSL has failed regardless of how elegant the parser is.

There is a second strand of the same idea that I keep using, on the testing side. At BrightInsight, alongside the indemnity rules DSL, we ran a unified Gherkin-based testing language across our QA pipeline — unit, contract (Pact) and BDD layers — that produced traceable test artefacts mapped directly to ISO 13485 and IEC 62304 audit requirements. The DSL there is, on its face, just Given/When/Then. But the discipline is the same: a clinician or a regulatory auditor can read a scenario and judge whether the behaviour described is the behaviour they expect, without an engineer in the room to translate. When the audit happens — and in medical devices it always happens — the evidence package is generated by the test runs themselves. There is no separate compliance theatre. The tests are the document.

That mattered for two reasons. The first is the obvious one: audit readiness shifted from a quarterly panic to a continuous property of the build. The second is subtler and, in my experience, more valuable: when the people who actually understand the constraint can read and challenge the executable description of the system, the description gets better. Engineers stop accidentally encoding their own misreading of the rules. The QA artefacts become a conversation, not a barrier.

So the indemnity DSL and the Gherkin DSL are two halves of the same instinct. One lets the domain experts define what the system should do. The other lets them verify what it actually does. In both cases, the goal is not to remove engineers — it is to put engineers in the right place. Behind the language, holding the substrate, not in front of it gatekeeping every rule change.

It is worth saying that this is not a new idea, and I am not claiming it as one. The pattern is older than software. Lawyers have been writing executable rules in a constrained natural-language idiom for hundreds of years; the law is, in effect, a DSL with judges as its evaluator. Accountants run on double-entry bookkeeping, which is also a DSL — a tiny notation with strong invariants that anyone trained in the trade can read, write and verify. Actuarial tables are a DSL. Music notation is a DSL. The legal indemnity questionnaire we encoded at BrightInsight was, before we touched it, already a DSL on paper: a constrained form of English with implicit semantics that underwriters had been authoring for decades.

What software gives us is the ability to make the evaluator concrete, fast, repeatable and auditable. The rule was always executable in the human sense. The DSL just makes it executable by a machine in the same shape the domain experts already write it.

Martin Fowler's book on domain-specific languages is the canonical reference and I will not try to compress it here. The one principle from that tradition I keep returning to is the distinction between internal DSLs (a fluent API in a host language) and external DSLs (a separate grammar with its own parser). Both are valid. The choice is determined by who has to read the rule. If the reader is an engineer comfortable in the host language, internal is usually cheaper. If the reader is an underwriter, a clinician, an accountant or an auditor, external is almost always worth the parser cost, because the host language's syntactic noise is the thing standing between them and ownership of the rule. At BrightInsight we went external. JSON, in that case, was the substrate, but the meaningful grammar sat on top of it.

The other principle I keep returning to is from Domain-Driven Design: the ubiquitous language. The DSL should not be a separate vocabulary that the engineering team negotiates with the domain experts. It should be the vocabulary the domain experts already use, lifted into a form a machine can read. If you find yourself inventing names for things — names that are technically convenient but that no underwriter would ever say out loud — you have drifted, and the DSL will pay for that drift with every subsequent rule change. Sit next to the specialists and use their words. That is the whole game.

There is a failure mode worth naming, because I have walked into it myself and watched other teams walk into it. The DSL becomes the engineer's plaything. It accumulates features for engineering reasons — performance hints, abstraction levels, helper functions — that drift the grammar away from the domain. After a year of this, the underwriters can no longer read a rule without an engineer next to them. The DSL has become a programming language, and the original benefit has evaporated. The discipline, then, is to treat the DSL as the domain experts' property and the runtime as the engineers' property, and to police that line ruthlessly. If a change to the grammar does not make the domain expert's life directly better, it does not belong in the grammar. Push it down into the runtime.

A second failure mode is over-reach. Not every problem wants a DSL. The cost of a DSL is real: a grammar, a parser, an evaluator, tooling, documentation, a learning curve for the domain experts who have to live in it. The cost is justified when the rate of rule change is high, the domain is well-bounded, the rules need to be readable by non-engineers, and there is a determinism or audit requirement that benefits from rules-as-data. Indemnity insurance hits all four. Clinical decision support hits all four. Underwriting hits all four. A one-off feature flag does not. A bespoke pricing rule that changes twice a year does not. The honest test is: would the domain experts edit this themselves, given the chance, and would they edit it often enough to learn a notation? If the answer is no, write the code, ship the feature, and move on.

When the answer is yes, though, the leverage is hard to overstate. I have watched an underwriter, in a session that was originally scheduled to brief us on a new product, open the rules editor and have the product draft assembled before the meeting ended. The regression suite ran. Two scenarios needed clarification. We clarified them in the room. The product went to UAT that afternoon. There was no engineering sprint for the product launch because the product launch did not require engineering. We were on the platform team, holding the engine, the schema and the audit trail steady so that this conversation could happen.

That moment — the underwriter editing the rule, the engineer next to her holding the substrate, the regression gate enforcing the legal contract automatically — is the picture I keep in my head of what platform engineering is for. Not infrastructure for its own sake. Not an internal product catalogue with too many YAML files. A substrate on which the people closest to the domain can directly express what the business actually does, with the determinism and traceability the regulators require, and without an engineer-shaped bottleneck between them and production.

The patterns underneath travel. I have used the same instinct on clinical workflows at Philips, where the rules in question were HL7/FHIR message interpretations and clinicians were the domain experts; on test artefacts at BrightInsight, where the rule-writers were QA and regulatory; and in different shapes elsewhere. The tooling changes, the host language changes, the regulator changes. The pattern does not.

I think we — the industry — keep rediscovering this idea under different names because it is genuinely hard to do well, and the temptation to keep the rules in code is enormous. Code is where engineers feel competent. Handing the rules over to underwriters or clinicians or accountants feels, briefly, like a loss of control. It is not. It is the opposite. It is the engineers finally getting to do engineering — language design, evaluator correctness, regression safety, audit traceability — while the people who actually understand the domain own the part of the system that encodes the domain. Roles line up with expertise. Feedback loops shorten. Compliance becomes a property of the substrate rather than a ceremony around it.

The work I am proudest of in twenty-five years of software is this. Not the largest system. Not the most fashionable stack. A small JSON grammar, a deterministic evaluator, a regression gate over every historical decision we had ever given a customer, and a team of underwriters who, for the first time in their careers, did not have to wait for an engineering sprint to ship a product. That is leverage. The trick is recognising when the problem in front of you is shaped like that, and being disciplined enough not to keep the rules to yourself.

Madu