Opening Keynote by Eric Evans - DDD Europe 2026
Eric Evans explores how Domain-Driven Design principles can be adapted using LLM agents and statistical code analysis to build testable domain models of legacy codebases.
Highlights
4 clips · 9 min of 58 min-
- Samples code randomly across repositories to avoid developer confirmation bias.
- Combines discrete pipeline tools invoked via scripts by an LLM agent.
- Replaces brittle pre-LLM frequency analysis scripts with robust LLM capabilities.
-
- Extracts raw executable code phrases rather than abstract words for better repeatability.
- Verifies that extracted terms deterministically exist within the source code.
- Tokenizes and filters cleaned words using targeted LLM prompts to isolate domain vocabulary.
-
- Formulates empirical similarity predictions based on historical architectural context maps.
- Expects anti-corruption layers to show high vocabulary similarity to both connected contexts.
- Tests architectural assumptions against hard statistical metrics instead of relying solely on intuition.
-
- Vibe coding hits hard limits, making rigorous domain models and boundaries necessary.
- Future architectures will blend deterministic code with explicitly constrained stochastic LLMs.
- Module and function boundaries serve as crucial attachment points for verification and safety.
That’s all the highlights.
Questions this video answers
10 · every answer cites the moment it was said01 How can LLMs be applied to domain-driven design experiments? 2 sources · 10:43
Eric built an experimental pipeline using LLMs to look at code supposedly in a bounded context and extract its actually used language. This statistical approach helps overcome the brittleness of pre-LLM tools like stop-word lists and TF-IDF.
02 How does the domain language surveyor pipeline extract vocabulary? 2 sources · 13:30
The pipeline grabs random chunks of code, classifies them by domain, and uses an LLM to extract domain phrases. It then verifies terms against pure code, tokenizes them, and filters them with an LLM for relevance.
03 Why use random sampling instead of targeted code inspection? 2 sources · 17:01
Random sampling avoids human biases that lead us straight to where we think domain logic should be located. It ensures you find the domain logic wherever it actually resides in the codebase.
04 How do you prevent LLM hallucinations during vocabulary extraction? 2 sources · 19:13
The pipeline uses multiple verification steps with deterministic code to ensure every extracted term actually exists in the code sample. This prevents hallucinations from causing real problems downstream.
05 What similarity metrics are used to compare code vocabularies? 2 sources · 25:14
The system uses Jaccard similarity to measure overlap between term sets and document frequency to build term vectors. These metrics allow you to compare the consistency of vocabularies within and across repositories.
06 How can similarity score comparisons act as a system sanity check? 2 sources · 28:50
You can compare a codebase's similarity to itself versus its similarity to other systems to ensure valid relationships. If a system isn't more similar to parts of itself than to random external pieces, it indicates a major problem.
07 Can historical context maps be treated as testable hypotheses? 2 sources · 37:11
Yes, you can draw a context map based on a system's documented history and derive expected similarity scores for relationships. Comparing these predictions against actual empirical similarity scores provides hard evidence for or against your architectural assumptions.
08 How can a coding agent orchestrate the analysis workflow? 2 sources · 38:41
You can write an instruction file and build a skill so an agent like Claude Code can orchestrate the pipeline end-to-end. The agent handles loops like resampling when data is insufficient while keeping raw processing details out of its main context.
09 How do you balance token costs across different model tiers? 2 sources · 41:07
Expensive top-tier models like Opus are reserved for high-level orchestration, while cheaper models handle specific tasks. For example, classification can be done by Haiku and vocabulary extraction by Sonnet, minimizing overall token costs.
10 How do domain models change when incorporating statistical metrics? 2 sources · 27:47
Domain models do not have to be limited to traditional object-relation diagrams and can instead be statistical models of phenomena occurring in code. This allows you to capture actual usage patterns and characteristics through metrics like corpus definitions and cohort term frequencies.
Section-by-section summary
11 parts
Reflecting on a decade of community and disruption
Celebrating ten years of DDD Europe highlights the resilience of the community through global crises. The profession now faces another massive upheaval with the advent of artificial intelligence. Rather than yielding to paralysis, developers must adapt through practical utility.
Rejecting hype and doomerism through curiosity
Discussions around artificial intelligence are frequently plagued by extreme hype or doomerism. Predicting specific future outcomes is futile and paralyzing for practitioners. Cultivating curiosity and experimentation serves as a much better guide for navigating uncertainty.
Experimenting with LLMs for domain-driven design
Exploring how modern AI tools can assist with domain-driven design leads to practical experimentation. Previous pre-LLM attempts at extracting domain language from codebases were brittle and required excessive setup. Modern language models provide the right capabilities to solve this extraction problem reliably.
Building a multi-stage vocabulary extraction pipeline
Extracting domain terms involves random code sampling to avoid confirmation bias. The pipeline extracts code phrases, verifies them deterministically against the source, and uses LLMs to filter relevant domain vocabulary. This multi-step process ensures high repeatability across hundreds of samples.
Applying statistical similarity metrics to code vocabularies
Extracted vocabulary lists are compared using Jaccard similarity and document frequency vectors. These statistical models allow developers to measure consistency within a codebase and divergence between different repositories. Domain models do not need to be object-relations; statistical models offer powerful insights into code phenomena.
Treating historical context maps as testable hypotheses
Analyzing the complex history of the OpenMRS project allows the creation of an architectural context map. Developers can formulate empirical hypotheses about expected vocabulary similarities between core systems and anti-corruption layers. Testing these hypotheses provides concrete evidence for or against architectural assumptions.
Orchestrating analysis workflows with coding agents
A Claude Code skill orchestrates the entire multi-step surveying process autonomously across multiple repositories. Cost efficiency is maintained by routing tasks to appropriate model tiers, such as Opus for orchestration and Haiku for classification. The agent handles loops and retries while presenting a concise narrative report.
Validating architectural assumptions with empirical similarity scores
Empirical measurements show high domain prevalence in core modules and anti-corruption layers. Similarity scores confirm that integration layers closely match external standards while maintaining lower similarity to internal core code. This empirical approach replaces anecdotal guesswork with hard metrics.
Formalizing a statistical domain model for code analysis
The surveyor architecture is itself structured around a formal domain model containing corpora, samplers, classifiers, and cohorts. While the overall system relies on deterministic code and well-defined contracts, only specific components require LLMs. This separation enables agents to reason about the codebase using a shared, precise language.
Adapting domain-driven design for heterogeneous AI systems
Domain-driven design remains vital as vibe coding reaches its practical limits. Future systems will combine deterministic components and stochastic LLM behaviors explicitly defined by contracts. Bounded context boundaries become crucial attachment points for verification and cost control.
Embracing continuous learning as a professional adventure
Attempting to predict long-term technological plateaus or outcomes is entirely futile. Developers must accept that specialized skills will frequently become obsolete and require relearning. Treating this cycle of continuous learning as an adventure ensures resilience in a rapidly changing landscape.
n next · p previous clip · t highlights/full
< > slower/faster · / search what was said
-
Domain-Driven Design8× · first 0:18
An approach to software development focused on modeling software to match a domain.
-
Bounded Context4× · first 2:01
A boundary within which a domain model applies and terms have specific meanings.
-
Mixture of Experts1× · first 9:16
An alternative neural network architecture where different subnetworks handle different inputs.
-
TF-IDF1× · first 12:50
A numerical statistic reflecting how important a word is to a document in a corpus.
-
Jaccard Similarity3× · first 25:14
A metric used to gauge the similarity and diversity of sample sets.
-
Anti-Corruption Layer5× · first 36:00
A translation layer between different subsystems to prevent conceptual pollution.
7,038 words · about 28 min to read
Reflecting on a decade of community and disruption
Thank you. And welcome to DDD Europe. It's been ten years that we've had this conference. Isn't that something? And it's been quite a ten years, a kind of difficult ten years in many ways, right? We've had a pandemic and we've had some political upheavals, and there's a lot going on, and all that disrupts a community. It is quite possible that through the lockdown we could have sort of lost contact, and there would have been a total reset afterwards.
But actually there was continuity. And it is owing to organisations like, and especially, the Domain-Driven Design Europe conference, that had events during and after the pandemic. I think it's easy to forget about that, or we try to forget about it. And then, of course, there's the technical upheavals.
Ten years ago, microservices was a new thing and we didn't really know how to. There were a lot of people who said things like, you know, the design in a microservice doesn't really matter because you just throw another one up there if something breaks. And then eventually people came around and said, "Oh, I guess it does matter.
Maybe more than ever we have to pay attention to things like bounded contexts." But that conversation had to happen somewhere, right, in a community, and communities have to have spaces and they have to meet. And so I think appreciating these ten years of this great conference is good, and I think let's give Aardling and all these people a little hand for the years. Now, toward the next ten years, there have been a
Rejecting hype and doomerism through curiosity
lot of challenges, as I mentioned, and now we have a new one. I think everyone thinks about AI a lot, and as most of you know, I've been sort of obsessed with it for years now. For the most part, I'm quite excited about the possibilities of it from an intellectual point of view. It is the most interesting thing that's happened in my career.
But of course, there are also worries. There are things that could go wrong in our society and in our profession. And so we worry about these things, and sometimes it paralyses us even, and that's not useful. If we keep focused on usefulness, right, humans are very adaptable creatures and we'll adapt to this one too. But we have all sorts of mechanisms for that.
And this situation we're in, it is very unusual. I mean, we haven't invented a whole new kind of intelligence before. But there have been periods of time when people were very unsettled. There's a famous opening of the book, *A Tale of Two Cities*, where, you know, "it was the epoch of belief, it was the epoch of incredulity. It was the spring of hope. It was the winter of despair." And a part that people tend to forget from that quote is: "in short, the period was so far like the present that some of its noisiest authorities insisted on its being received for good or for evil in the
superlative degree of comparison only." In other words, hype, or the inverse of hype, which is doomerism. One or the other. That's 1859. Reflecting on an even earlier time, and now 2026, it still seems like, yeah, the noisiest authorities insist on these superlatives. So what can we do? I mean, we do not know the future.
And I think it's important to return to that idea that a lot of this stuff, a lot of the worries we have, but also some of the crazy hype, is based on predictions of the future. And we don't know that. We know some things about the future.
We know there's going to be a lot of change. That's kind of saying the obvious, but it's a useful thing to keep in mind. But predicting specific things is a foolish thing, I think, and I do it too. I try to catch myself. I try to bring myself back to how can I prepare for the unknown. And humans are good at this. We have sort of instincts in us, I think, that are tailored for this. I think that we have all sorts of feelings and urges, but some of the ones that can be cultivated in a time like this, when the underpinnings of our profession are really shaking.
Curiosity, I think, is as good a guide as any. It's much better than trying to predict the future. What do you find interesting? And I followed that guide. What do you find fun? What fills you with wonder?
Experimenting with LLMs for domain-driven design
And let that happen a little bit, because it is a wonderful thing, even if it does turn out, as it surely will, to have some bad consequences. So, let me talk about what I've been doing, because I deal with this just like everyone else, and I have studied this thing for a few years now, and I think my study has fallen into, broadly speaking, these four categories.
I've gone deep on the theory of it, how does this thing actually work. I do believe, but I can't prove, that this is a useful thing to do when things are changing very fast, or even when they're not. There's value in deeper understanding. This is why CS programmes teach theory, and not just how to program, but now the equivalent would be to study the architecture of LLMs and such. There's a certain amount of maths needed for that, and my maths background was pretty strong a few decades ago, but I had to refresh some of that and learn some new things, very interesting
things. I also studied some of the alternative architectures. The LLMs that we have now are all pretty much based on the same principle, which I won't go into, but I think most of you know something about it. I think that things are changing so fast, right, a new model comes out every few weeks.
Most of these are refinements of the existing ones, but every now and then they bring in a really new concept. Mixture of experts was one of these alternative architectures, until it started being part of the standard architecture. We might have more fundamental changes than that in the future. But also, to understand a thing, it helps to know something about the alternatives, the family of things that it came from.
And then, my personal favourite, the one that I think might have real practical applications to our work, the design of complex systems, or the design of systems for complex domains, really what I mean is the interpretability. You know, to look in that black box and say what's really happening in there. You build this LLM architecture, you run it through this training process, and then, in those weights, a model forms, a domain model of the language that you're interacting in, and of the concepts necessary to string those words together in a meaningful way. And last year at this conference I did talk about some of the details there. And then the other thing that I've been doing is experimenting
with trying to apply these tools myself. What does Domain-Driven Design look like if you are using LLMs? This is an experiment. It's a very messy, exploratory side project of mine. So, don't expect too much polish in this. But it's following up on an idea that I had many years ago. People used to ask me, well, what kind of tools should we build for Domain-Driven Design? And I didn't have too many ideas, but I had some. One of them I had tried to do a couple of times, which was this idea of having an empirical way to look at the actual language being used in the system. So, first of all, why did I do this one?
I needed a non-trivial task to work on, and something that would actually benefit from LLMs. LLMs are good at some things and not good at others. And so this task, as you'll see, I think is a good one. And I didn't want, or at least I didn't end up with, a super disciplined plan where I had a clear goal and then worked my way toward it.
I had a lot of side quests and everything. As I mentioned, I tried this before. I said, "What if you could look at the part of the code that is supposedly in this bounded context, and extract the language from it, and see the patterns of that, and see if it really is following what we expect from a bounded context."
And it was complicated. I had stop word lists. I used techniques like TF-IDF for finding the important words. I visualised it in various ways. And it was very brittle. Every time, for any system, you had to sort of do so much setup that you might
1 Highlight · Building a multi-stage domain language surveyor
as well have just done the analysis yourself. It just didn't produce a satisfying result. And I think it turns out that an LLM is the right tool for this job. So I built this thing I call the surveyor, the domain language surveyor. And it's a kind of a pipeline. You take some code base, like a repo, and you aim this thing that I call the sampler at it, and it grabs chunks of code at random from that repo, or whatever corpus of code you pointed at. This gets run through the classifier, to find the chunks that are related to the domain you're interested in, or to whatever domains, and then another tool in the pipeline extracts the vocabulary, and then another tool compares the vocabulary of different, say, one repo to another.
Suppose that I believe that this repo is one bounded context and this one is another bounded context, and I say, well, then that should mean that the language usage in those two should be different, and specifically I mean the language usage as it pertains to the domain that these applications are addressing. This was one of the problems with my old ones, the pre-LLM ones. They looked at all the language, right?
So if you were using an event-driven Java system, it would be full of event sort of language and so on. And that was essentially noise for my purposes. So finally, you put all this together. Each of these pieces is a kind of self-contained operation that can be invoked from a script. And this script could be activated by a person or by an agent.
And I've found the agent to be extremely useful in this. My original concept was that the application would have a UI and be pretty conventional in the way that you interacted with it. Or maybe it would be a plugin for an IDE. I did experiment with that for a while. But what I've come down to is a set of command line tools that can be invoked by an agent, and I interact with that
agent mostly, and if I want to I interact directly with the tools, and the agent and I have great conversations about what's going on. All right, let's drill in on this, but first, just to point out that I'm talking about actual usage, right? In fact, this is the way dictionaries are made, right? Pedantic people sometimes say that's not the proper use of that word.
But dictionary writers look at it a different way. They collect statistics, and they say this is how these words are actually used. And that's what I'm trying to do. So, now let's get into it, though.
Building a multi-stage vocabulary extraction pipeline
All right, let's drill in on the vocabulary extractor. So, you take a sample, right? That's one of those chunks of code that was pulled randomly. And I think the randomness is important, by the way, because we have so many biases. We go looking for where we think that the domain logic should be. And maybe it's not there. Maybe it's somewhere that it shouldn't be. But I'm looking for where it really is.
And I'll find it because I'm sampling randomly. So anyway, I take just one of those samples and a category. So, for example, a chunk of code and the category of healthcare, and the vocabulary extractor produces a list of terms. This is a list of terms that are actually used in that particular chunk of code to talk about that particular domain category.
This is something that would be very hard to do in conventional code, and it's not completely trivial with an LLM either, by the way. I'll get to that. Now, actually, the way I do it, well, I am getting to it, so
2 Highlight · Deconstructing vocabulary extraction into verifiable steps
first, of course, you have your sample and your category, and you ask an LLM to extract. So I could just say give me the words, but I found that what works better is to go in smaller steps: extract code phrases that are related to this domain. In other words, don't ask it what the abstract words are.
Just say find actual pieces of code that relate to this. I tend to get a much more repeatable result that way. I tried a lot of different stuff, of course. Now, that could change, by the way, with different models in the future. Now we've got that list. Now I chop, well, first thing I do before I go any further, is I make sure all the things in that list are actually in the code.
Maybe it made something up. Sometimes it does that. It's just trying so hard to be helpful. So I verify that with pure code, completely deterministic, old-fashioned code. And then I take that and I tokenise it with more pure code. I chop it up into words, you know, if it's camel case or whatever. And now I use an LLM again to say, okay, here's some chopped up, cleaned up words. Tell me which of those are actually related to this domain.
And now I have a nice clean, relevant list of terms that are actually being used in that piece of code. This is the kind of thing you can actually compare across samples, right? Why does it need to be so
regularised, so many steps? Because I want to be able to take hundreds of these and compare them, combine them. What if I just asked the LLM, I just said, you know, find me the terms related to the domain, blah blah blah? And that actually does work. It actually works fairly well.
But there's some important variation, as you can, you know, here's a little miniature example, whereas when I use my pipeline, I find that I get almost every time the same answer. Now, my pipeline needs some work. But it is a very repeatable thing. So, yeah, just to kind of quantify that, I try, one of my things I'm trying to do is quantify things, measure things.
So anyway, here are some of the terms that it tends to pull out on a particular sample, if you run it many times. And you see that the pipeline is more consistent, but also narrower, like it's maybe missing a few things. So I'll get back to that in a minute. What if we give it a sample that just doesn't address that domain at all?
Here's a sample that just has nothing to do with healthcare at all. And, you know, oh look, you know, it found some healthcare stuff. So this explains, you know, this is a classic hallucination. And I can make this happen if I run this about a hundred times. It's what it takes. I mean, that's pretty good.
I think that, I mean, that's pretty low. It's still high enough, though, to cause real problems in my system. And it won't happen in the pipeline because you've got those multiple verification steps. So it would just be an empty list coming from the pipeline. All right. So, as I said, it needs some refinement.
Like, you could try refining the direct prompt. You could, um, do like, you know, how it found certain words sometimes and not others. You'd probably do a better job of that if you iterate, if you ran it several times and accumulated the results. And then you could try to emphasise in the prompt that it only wanted the executable.
One of the things I noticed was that some of the things that it finds, that the pipeline doesn't find, one or two of those it should find, but some of them are things that are mentioned in comments and such. And I specifically don't want that. I want the language that's really being used to make the system do what it does.
And so that's tricky for an LLM that's reading it like text. All right, so, which, by the way, if you ask it to find phrases that are chunks of executable code that are related to this domain, you see how that makes the problem easier. If you were trying to do this, you might find that a nice step to have. But if I wanted to make the pipeline better, I think one thing I would want to do, because one of the problems I see, is you have this nice clean list, and you run it through this filter that says which one is that, these are healthcare terms, and it filters out words that sound like healthcare words by themselves, but in context you realise they are.
So, pass in the sample again, so that it can see the context while it's filtering. I think that's a refinement I will make. You can fix any of those little pieces, change them, experiment, as long as you keep the overall contract of the vocabulary extractor the same. As long as, at the beginning, you're passing in a sample and a domain category, and at the end you're getting a list of terms, with the constraint that all
Applying statistical similarity metrics to code vocabularies
those terms exist in that sample. You can do whatever else you want. Let's look at another one of the elements of the pipeline. This one's pure code, the vocabulary similarity. Now I have hundreds of lists of terms. What am I going to do with this? So, I created a few similarity metrics. They're all pretty, they're all things that exist already, although I'm using them maybe in a little unusual way. And so, one of them is Jaccard similarity.
It's basically how much overlap is there in these sets. And so you take two of the lists and you compare them, and you just compare pairs. You can either take a list from this repo and a list from that repo, and compare them, and do that many, many times. Or, if you want to see sort of the self-similarity, the consistency of the thing with itself, you can grab two random pairs from within the same corpus. That's one of the methods.
And then another one, and the one that I actually ended up liking better, is document frequency: how often does this term appear in a sample, and make a kind of vector out of that. So that if you took all of the vocabulary lists you made from this corpus, you'd get a vector something like this. And then you do the same thing for another corpus.
And then you compare these two, you know, with a completely standard calculation, and you get a similarity. You can also look at how consistent a thing is, by randomly chopping one of the corpora in two and comparing those two halves, and then randomly chopping it in two again and comparing those two halves. And you build up, you can do that many times and find out how similar the thing is to itself, sort of, which is important, because otherwise we don't really have a way of knowing what our number means.
Like, if I say that the similarity is 6, that doesn't mean anything, unless I can say, well, but the similarity to itself is 7, or whatever. Okay, this is very statistical. I find, I do like statistical models. I want to point out that domain models don't have to be like object-relation diagrams. They can be statistical models. And there are reasons that I think domain models of the phenomena happening in the code can be really powerful things. They may get us closer to the actual characteristics of this thing, as opposed to looking at lots of what you might say, anecdotal examples, taken from the code. Let me show you an example now, a real one.
Here are two open-source healthcare management programmes that I took from GitHub, and I compared them using Jaccard similarity, and you see that OpenMRS is, now, you don't know what these numbers mean, but you can compare them to each other, right? So the similarity of OpenMRS to itself is 0.04, the similarity of OpenEMR to itself is 0.02, but the similarity of one to the other is 0.017.
In other words, this is kind of a sanity check. Are they more different from each other than they are random pieces of themselves to other random pieces of themselves? If that didn't happen, if you didn't see that, you'd think, "Oh, there's something really wrong with this." A thing can't be more similar to something else than it is to other parts of itself.
But all right, there's your Jaccard similarity. And here's the cosine similarity of the document frequency. Again, you've got OpenMRS is 0.7, OpenEMR is almost 0.7, and comparing the two to each other is 0.45. Again, the numbers don't mean a lot, but the comparison of the numbers does mean a lot. An interesting thing you can do with this document frequency comparison, though, is you can find the contributors, which words actually make up that 0.45. So like here are some of the top contributors.
The word "patient" is the most prominent one. Fully, like, a third of the similarity, or a quarter of the similarity, is just from that one word. Then "encounter" is the next most common one, and "service" and "provider" and "order." All of this comes out of the pipeline, right, with prompts that talk about using, looking for words in the domain, and so on.
I didn't do complicated setup. Okay, now something more real. I have always worked in enterprise projects mostly, and a couple of open-source projects on the internet doesn't really capture that, but actually, sometimes you find something a bit like an enterprise system, like OpenMRS, which is actually a pretty big system that was developed over a long span of time, as in, bringing in different parts that were developed at different times by different people.
So it is a lot like an enterprise system, though not fully. So I thought, oh, if I could use this technique on this, it would be a bit more of a test
Treating historical context maps as testable hypotheses
of, could this work in the way I really would like to use it. So here's a bit of a timeline for OpenMRS. What I'm going to try to do is draw a context map of a system that I don't personally know much about, that I've never had any experience with, based on the history. Its history is pretty well documented, better than most, than any enterprise system that I've seen. And so digging through this, and really, I mean, having Claude dig through the history and helping me sort it out and picking a few kind of prominent, there's a whole bunch of repos in the OpenMRS organisation, right? So we picked out like five to focus on.
So first was OpenMRS core. It is also one of the first pieces that was developed, and so as of 2004 OpenMRS core existed. Let's put that on our context map. Now, there were a bunch of other things being built, but let's skip to 2008, when the MDR-TB, which is the multi-drug-resistant TB, tuberculosis, that's what that stands for, and there's a whole interesting backstory about that, actually, but in 2008 they brought in a new system for that. So let's put that on the map.
Now, what is its relation to OpenMRS core? Well, from reading the documents, it seems like they took the experience they got from an earlier system, but they didn't take very much of the actual code, or if any of it, they rebuilt using OpenMRS core. So my guess is that they conformed, that their intention was to make it all consistent. So that's, uh, two contexts.
Now looking at 2011, this is a bit of a side story, because there's a standards organisation called HL7. Anyway, they started developing a standard for exchanging healthcare information. It's called FHIR, and it has nothing to do with OpenMRS core at this point in time. But then, in 2014, someone developed a Java implementation of FHIR, called HAPI FHIR, which made it easy for a Java project to integrate with FHIR-compatible systems. And this HAPI project is actually
a separate open-source project, also on GitHub. So I'll put that on my map. It's basically the implementation of a published language, right? That's clear. That's an easy one. And at about the same time, only a few months later, the OpenMRS organisation had this implement, this connector to FHIR, which I'll call legacy FHIR.
I'm not going to try to incorporate it, though, because it got superseded. I think that would be an interesting thing to look at later on, but I haven't got around to it. Basically, there's no connection at this point in time between the two contexts. But then, finally, in 2019, they replaced the legacy FHIR with FHIR2, and it uses HAPI, and it allows OpenMRS to talk to a FHIR system, but without changing OpenMRS to use the language of FHIR. That sounds like an anti-corruption layer to me, right? So I decided FHIR2 is an anti-corruption layer connecting OpenMRS to the published language as built by the HAPI Project.
And then, one more to throw in there, is this module called the patient chart. It's actually a UI, and I'm going to say that it conforms to OpenMRS. Now, we make those kind of diagrams based on our knowledge of the project, based on our knowledge of its history.
Is it right? Like, maybe there's lots of language leaking over that anti-corruption layer. Maybe OpenMRS is starting to look a lot like FHIR.
3 Highlight · Treating context maps as testable hypotheses
So how could we know this? So I'm going to take this as a kind of hypothesis of what I expect to see in the language of the system, and I'm going to put down my predictions of this, like, if I compare HAPI and FHIR2, how, what do I expect to find on the similarity score? Well, HAPI is supposed to be a faithful implementation of FHIR. FHIR2 is meant to connect our system to HAPI.
So, I expect to see a high similarity, right? Meanwhile, the core, the whole point of that anti-corruption layer is to keep the core from getting mixed up with the language of FHIR. So I would expect HAPI and the core to look quite different. So I'll say low similarity on that one, and so on. I'm not going to go through this in detail, but you get the idea. For every relationship we say how much similarity would that imply, and now let's go test it. I got tired of doing all this, and I would make lots of mistakes. I'd forget to do this step or that step.
I made a skill so that Claude Code could orchestrate this whole thing.
Orchestrating analysis workflows with coding agents
And I then I wrote up the thing I just showed you and I said we're taking these repos, and we're comparing them like this. And I just wrote that instructions file, and I said do that, and it worked for three hours and orchestrated this thing end.
For example, if you do the sampling, and you take a bunch of samples, and then you do the classifying, and you realise that you don't have enough of healthcare from that sample, then from that particular corpus, it'll go back and sample some more, and do that as a kind of impromptu loop, very much the same thing I would have done, until it has what it needs, or until it concludes that the prevalence of that domain in that corpus is so low that we really can't, and that's then the conclusion we needed anyway, right? If a certain corpus just doesn't have any healthcare stuff in it, that's what we wanted to know, so it will do all that stuff, and then it does the, you know, it doesn't do the vocabulary extraction.
It kicks off the same script I would kick off, which runs and also does LLM calls. But none of this clogs up the context of the agent, right? It doesn't have to keep track of all that detail. It gets a final report that was actually designed for me. So it's very concise, only information that is useful for the final analysis. And then there's the narrative report, which is basically structured, that I'm saying this is what I want to see at the end, but then it improvises and puts it all together, and it's like, it's pretty good.
I'd like to say it's not as good as what I would have written myself, but I'm actually not sure about that. I'm not so good at the rote, you know, the tedious stuff. So it's important that this conserves the tokens, you know, because they're expensive tokens. So much of the work is done by much lower-level models. This orchestration level needs Opus, but classifying can be done by Haiku, and the vocabulary extraction can be done by Sonnet, not quite by Haiku. And how do I know that? Because I created an eval to find out.
All right, let's look at some results then.
Validating architectural assumptions with empirical similarity scores
So, it found healthcare stuff, right? This is the prevalence of healthcare in these various parts. It finds that, you can see that in core it's pretty high. It's like 58% of the samples have healthcare-related stuff. FHIR2, the anti-corruption layer, is even higher. It's, you know, it's really high. And then the TB specialist module is the highest of all. Now let's look at the really interesting part, those similarity scores. So HAPI and FHIR are very much alike, and so that's consistent with the idea that FHIR2 conforms to HAPI. Now, when I say conforms to, that sounds a little funny. I said that this is an anti-corruption layer.
But from this point of view, what an anti-corruption layer does is essentially conforms to two different contexts, from a language similarity point of view. It would have high similarity to both of the things it's connecting. And I think that this is a place where the language needs to get a little ironed out, frankly. And then, what about HAPI and the core, the thing we're trying to prevent corruption of, right? Well, it has low similarity, only 0.3 versus 0.6, and so on. It's a pretty long list. I couldn't fit it on one slide.
A few of these things didn't quite fit, like, the similarity of the patient chart and the TB part was actually kind of medium similarity, whereas I hadn't thought it would be very high, and so on. I think saying what you expect, before you, and then getting some actual empirical information.
It's something we don't often do, and that's pretty useful. So let's think about this now, back to domain modelling.
Formalizing a statistical domain model for code analysis
I built this thing, right, but what is the domain model of this thing? Well, it has these kinds of concepts, like, the corpus is the code we're selecting from, and we need that to be clearly defined. It might be a bounded context. We're going to find out, or we're trying to get evidence for that, I should say. We take samples, and the sampler, you know, follows a very specific sampling method, so we can try to avoid bias. We have a classifier that takes a taxonomy and chooses categories from that taxonomy, in relation to a certain sample.
So it's constrained. We have, it's constrained, but it does vary, and it's controlled by an LLM. We have cohort, which just means all the samples that had a certain category prevalence, of that cohort in a domain. The vocabulary is the terms that appear in a sample and relate to the given category. I know this, you know, this sounds like just words, but these are all really carefully defined, and the relationships are enforced in the code.
And then similarity, we have already two. You could have others. With most of these things, you have flexibility about the implementation, as long as you follow the contract. In this case, for example, we need to go between zero and one, and we need to ignore samples that have a low, we need to avoid doing the calculations when we don't have enough data, like if the prevalence of that domain in a certain corpus is just too low. Only one of these things really demands the LLM, though.
All of it is part of the model. One piece of it really needs an LLM. And then the orchestrating agent uses this model, and it knows that language. And this is part of why the orchestration runs so smoothly. It's not just making stuff up as it goes along. It's operating within this well-defined language.
So what I'm saying is I can view a context map as a hypothesis about the system, and then I can actually say, well, then there are certain things I'm claiming about what would be similar to other things, and I can actually try to measure that. I can get statistical properties of the vocabularies, and I can say does that support my hypothesis. It doesn't prove it, and I wouldn't claim that, but it's actual evidence for or against.
By the way, it doesn't run well on itself. I think part of it is that it doesn't quite know what to do with a prompt. Is that like code, or is that like a comment? Got to think about that. I don't know. And then, the thing it is doing, the domain it addresses, is not in any of the taxonomies of domains that have healthcare and education and that stuff. I've never found a taxonomy that has anything like what this tool does.
So, you'd probably have to make a custom taxonomy to see if it fit the category, and so on. It's also very small. To do a statistical thing, you need some size, right? And maybe the surveyor is just too small. But, you know, I always like to try to apply my
Adapting domain-driven design for heterogeneous AI systems
4 Highlight · Domain boundaries in a heterogeneous AI era
own thing to itself. Didn't work too well. All right, let me try to bring some conclusions here. Does, you know, sometimes I ask, does DDD even still going to be relevant in this new world? And I think so far the answer is yes. It broadens our reach. There are tasks that were impractical before that we can now do, and we need a model. I think vibe coding is going to have some very hard limits. The model actually helps the LLM, the agent, to do its job.
We need an apt language for the task. We need abstractions fit to the purpose, but we have to loosen up a little about the kind of model we're looking for. The models are going to look different.
Maybe they'll be more statistical. Maybe they won't say exactly how the thing is done, but they'll say the result needs to look like this. It needs to follow certain rules, like, you can pick whatever categories, but the category has to be in that taxonomy, you know. And we'll have a heterogeneous system where some parts are deterministic and some parts have an LLM operating. Sometimes the uncertainty of the LLM will be tightly wrapped up with verifiers and stuff. Sometimes it will have a stochastic kind of contract, and that will be explicit in the rest of the system, so that you can say, you know, describe that stochastic behaviour. So I think that the boundaries within the system, the module boundaries, the function boundaries that we typically make,
are super important. That's where you attach your verification. All this kind of description of the rules is scoped to say a function that has an LLM inside of it. So, and then we have to remember, though, that if we're going to bind it up like that, we are hiding some things. And so we have to make sure that the performance characteristics are characterised, so that you don't say call an LLM and then you're surprised that your system uses a thousand dollars' worth of tokens and takes hours and hours to run. But this is not a new thing.
We have to do this anyway. Every time we have some part of our system that calls the network versus local things, we have to be very aware of which things we do are local and which things are remote. And we do that all the time. That was a thing we had to learn relatively early in my career, part of really distributing systems. So I imagine that the model would create little pieces that would be legible to agents, and that whereas one of the things that makes Domain-Driven Design powerful is that you have a kind of library of these things you can compose together to solve many related problems.
We could go to a new level with that, where these things could be composed at runtime by an agent. This happens in a small way in the surveyor, as the thing puts together, calling the different scripts. It's a modest illustration of this, but it's kind of the supple design thing emerging in a new form. So anyway, there's a lot coming, and I think that, you know, we're about to probably be under a deluge of vibe-coded code with balls of mud, like humans are not fully capable of producing without some help.
And so maybe we will actually need some of these things just to help us emerge from that. There's also things we need to start thinking about, like, if we're writing our code with agents, and we don't have that hands-on feel, and, you know, one of the principles of DDD is this hands-on modeller.
Although you model these abstract things all the time, you're still very in touch with that concrete building of the system. And if we don't have that, how are we going to replace that with other things? And I think that's something we'll have to work out. And I think we will not figure all these things out completely by ourselves. Coming back to the community again, we need that community. We need to be exchanging what worked and didn't work, as we figure these things out. And so a community like DDD Europe is, I think, more important now than it was ten years ago. So just to come back to the, you know,
Embracing continuous learning as a professional adventure
this is a new world, blah blah blah, we are always trying to predict the future. It's irresistible. I do this and I catch myself, and I try to think about something else, or try to reorient myself. Wishing about the future is another version. I might wish that we will hit a plateau for a while, that systems like the ones we have, or the technology similar to what we have, more refined, but basically like what we have, will kind of be what we have for the next 15 years.
And it gives society a chance to adapt, and it gives us a chance to learn to use these things, and really use them, because humans are still really important to this. Yeah, I wish that. But that has nothing at all to do with what's going to happen next, right, any more than me wanting something. I mean, me predicting something. I think that this will happen. I don't think that AGI will happen immediately.
Whether I think so or not has nothing to do with it, right? And so if we can resist that a little, and think, well, what should we be doing instead, and I think the answer is we should be learning and experimenting. Learning and experimenting with what we have now, and I think that the deeper kind of learning, the more theoretical kind of learning I mentioned, is the kind that tends to hold up well.
The latest prompting technique will be obsolete next week, and already we see, like, generations of prompting technique has been churned through, but I'm not dismissing that we need to learn that stuff too. We kind of have to accept that we're going to have to learn a lot of stuff, and then the things we learn at such effort are going to become obsolete, and we have to learn again, and then that will become obsolete, and we have to learn again, and we have to do that, again and again, and be persistent, and look at that as a kind of adventure. And then I think we will be able to adapt and be resilient parts of whatever it is that turns out to happen next, and actually enjoy this, this really special time that we're in right now.
So I want to thank you again for your attention, and for participating in this great conference and community, and let's all go start that second decade. Thanks. Thank you, Mr Eric Evans. Thank you.


