Raw Portuguese version is here: https://tech-pills.github.io/2025-08-17-estudos-em-ddd-entre-pescadores-e-desenvolvedores/
Hello there! Hope you’re doing well.
I’ve been studying Domain Driven Design more deeply in recent weeks, and I’m working on a TypeScript monorepo template where I started applying my knowledge.
I had already studied the theory and even started reading the blue book a few times, but at various moments, I didn’t invest enough energy and didn’t go much beyond theory.
We’ve also started some internal meetings at Codeminer42 to discuss design, architecture, and design patterns, and fortunately, this coincided perfectly with this moment.
What is this DDD thing?
I heard somewhere that software development is like building sand castles in the air. Somehow, we’ll have representations of the real world in our code, on a large or small scale; it doesn’t matter.
As we advance in our careers, we realize that the most challenging problems to solve are those concerning communication and understanding the business and its domains.
And as developers, in general, we like to spend most of our time writing code (I believe that initially in our careers, this approach is positive because we need a larger amount of tools/experiences in our toolbox), but the more we think this way, the less we focus on how problems need to be designed and planned.
It’s that story about sharpening the axe for longer than you’re cutting the tree…
So, for example, if we’re going to work on an online sales system for fishing articles, we need to have a person (or more) with knowledge about this domain. An Expert, so to speak.
So if we’re starting this project today, we need to focus on which domain we’ll operate in. With this in mind, we can guide our development in harmony with the domain and represent it (partially or not) with the necessary amount of characteristics to solve the problem.
It’s important that a person new to the project can read the code and acquire knowledge about fishing articles through how we designed our domain model. And we’ll represent this domain (obviously) through an abstraction with our code.
Having the Expert working side by side with the team is the only way to achieve success. As we obtain this “raw” knowledge, how can we abstract this model that we’re gradually building initially in our heads, into code? And, at this moment, surely what I have in my head differs from the rest of the team, as well as probably from what the expert conveyed, and from what the “real” actually represents.
In this initial conversation, the talk starts in a tone like:
Well, we are the world’s largest distributor of fishing articles. Until today, we’ve always used third-party systems. We sell rods, reels, baits, you know… fishing stuff. People come here looking for lambari rods, but that’s not really a thing… I mean, you can use any rod for lambari, but some are better… And don’t even get me started on licenses, some people need them, it can depend on the breeding season and where they live, but it could be legal, we can centralize and outsource this type of service…”
In a way, we start to understand our “domain”, but we can’t immediately translate this into code (and we shouldn’t). And how to represent this abstraction? In the form of a model of this domain, an organized form of that knowledge. And as we start this representation, we realize that it becomes (mostly) something too big to hold in our hands, so we need to break it into better pieces and categorize them; maybe we don’t even need all those parts to be represented.
We’ll have customers from various parts of the world, so knowing their locality seems important, but at first, knowing about their musical taste doesn’t seem necessary.
“What does the fish do when asked for jokes?”
“It’s not enough to go to the river wanting to fish; you need to bring a net.”
So, we have to find a way to communicate this model among all involved parties: developers, designers, product people and experts, so there are no ambiguities. The more understanding at this moment, the fewer walls we have to demolish in the future.
DDD will combine design and development practices and show how they can walk side by side to have good products.
Building our Domain Knowledge
We know then that we’re going to build this system for the world’s largest fishing articles store, so we have already started building this knowledge base.
We brought to this meeting (and many others) these domain experts, we have an experienced fisherman who is one of the partners, a lawyer who works with licenses worldwide, an ichthyologist, a motor representative, etc. None of them with knowledge about software development, and it’s up to us to extract what is necessary.
We have to start somewhere…
We begin with a sketch of the abstractions from the conversation so far:
Products: Rods, Reels, Baits, Lines, Accessories, Fish Food ...
Customers buy products
Products with compatibility rules
Licenses: Amateur, Permanent, Shore-based, Boat-based, Commercial ...
...
Expert: “We want the system to be ‘intelligent’! Being able to indicate and alert about compatibility in the same purchase.”
You: “Right! Tell me a bit more about these compatibilities…”
Expert: “Well, a heavy rod needs heavy line and large baits. You can’t put a small trout bait on a bass rod; it won’t hook properly. And reels don’t work well with heavy baits…”
You: “What makes a rod heavy or light?”
Expert: “It’s the power and action. Power is the force needed to bend the rod, ultralight, light, medium, heavy…”
So we take some notes:
Equipment Compatibility System:
- The rod has power (ultralight → heavy) and action (fast → slow)
- This determines the lure weight range and line test range
- Reels have capacity and line type (spinning vs. casting)
- Fishing technique influences equipment selection
- Target species influences technique selection
Phew, so at this moment we can also represent this model not just as a diagram but also as an idea:
“Successful fishing equipment selection is determined by matching equipment characteristics with fishing techniques, which are chosen based on target species and environmental conditions, with compatibility rules ensuring that equipment works effectively together.”
It may be that at this moment we want to build a prototype regarding what we already have, and it’s very important to understand that this work is not a one-way street; the experts involved also need feedback and good questions so that this knowledge extraction can be done in the best possible way.
More and more elements can be added to the understanding:
“Seasonal patterns affect bait selection, water conditions influence technique choice, and the angler’s skill level limits equipment complexity…”
Ubiquity what?
I mentioned that maybe right after an initial understanding, developers might want to prototype something, because that’s our nature! But it’s very important to remember that it’s just a prototype, so that the common understanding of all parties gets closer and closer (and, like a good prototype, can be discarded).
Citing our nature again, it’s inevitable that classes, methods, algorithms, etc. pop up in our heads while the entire conversation extends, and I don’t think this is bad, but until this moment we’ve possibly stumbled upon many terms and concepts for the first time, and on the other side of the table, nothing related to software development crossed their minds, so how can we speak the same language? How do we avoid having an understanding about something centralized and clear?
How difficult is it to explain a computing concept to someone completely lay? Have you ever seen a colleague sending “pieces” of code in a chat with product people? How disastrous is that? We tend to use our own dialect when we are in a certain context (writing the Portuguese version of this article, I realized how difficult it is to avoid English words for certain terms, because in daily life most communication is in English and somehow most of the material and knowledge sources in our field are also in this language).
A fundamental principle of DDD is to use a language based on the model. Since the model is the point of contact, the point where software meets the domain, it’s appropriate to use it as the basis for this language. So from this, we try to use it at all possible contact points, in code writing, in our documentation, diagrams, when speaking with the client, etc.
The Ubiquitous Language connects all parts of this design and orchestrates this good communication and understanding. It’s certainly hard work and (maybe even) endless, as the team understands each other and evolves in this design.
A dictionary is created to determine each term in the project, and it’s very important to understand which terms might even have been used to represent the same thing; ambiguities or inconsistencies can lead us to structure things wrongly.
And the language will only be ubiquitous given a context, if we don’t have limits in our contexts.
At first, I thought that applying DDD everywhere in the project would be the best approach, but actually, we have to analyze where we’ll really benefit from the implementation.
Did you notice that in the conversation simulations already described here, we could somehow already identify and build our language, but obviously, in real life, this dialogue is much more verbose and complex (I even added more details to the UML above so we have more to analyze).
Just thinking about fishing equipment, we can represent something like:
# Fishing Equipment Domain
## Equipment Types
- Spinning Rod: designed for spinning reels, with guides facing down
- Casting Rod: designed for baitcasting reels, with guides facing up
- Spinning Reels: Fixed spool reel, easier for beginners
- Casting Reels: Revolving spool reel, more accurate casts
## Business Concepts
- Equipment Combo: Complete and compatible set of rod + reel + line + lures
- Target Species: The fish species the angler wants to catch
- Fishing Technique: The fishing method (casting, trolling, fly fishing)
## Rules
- Spinning rods must be paired with spinning reels
- Casting rods must be paired with baitcasting reels
- Rod power must be compatible with lure weight range
...
Did you also notice how difficult the UML reading starts to become as more concepts appear? At the same time, if we maintain documentation centralizing all our language, how quickly will it become obsolete? It becomes equally important the moment when we transform the knowledge acquired from Experts into software development, having a great model doesn’t necessarily transform this moment and the final product with similar quality.
During the development phase, there will certainly be moments when concepts or relationships will be incomplete, even though we invested enough time with the Experts. So I want to emphasize once again that this work of feedback and obtaining and clarifying doubts and concepts never ends. If somehow we try to create imaginary paths or shortcuts between the contextual language we have at hand, we will certainly fail to maintain its consistency! We should never assume that something that seems obvious really is:
- Assume that familiar words have familiar meanings;
- Not to examine into the mechanics or business rules;
- Confirm assumptions instead of exploring the domain.
Certainly, we still have all the other concerns that relate to software development itself, but if we fail at this moment when we’re translating language into code, the abstractions become fragile and difficult to change, and any high tide collapses our castle…
Developer: “So you have light rods and heavy rods…”
Expert: “Yes, light power for trout, heavy power for bass, for example…”
Developer: “I see, so heavier rods for bigger fish.”
Expert: “Exactly!”
If at this moment we assume that actually a rod with heavy power is also linked to its physical weight, we’ll be making a mistake.
Developer: “Wait, you said this high-power rod is lighter than the low-power rod?”
Expert: “Yeah, power has to do with stiffness, not weight. A high-power rod doesn’t bend much, even with a big fish pulling. A low-power rod bends easily.”
Developer: “So ‘power’ has to do with… stiffness?”
Expert: “Right! It’s the backbone of the rod. Heavy power for fish that fight hard, light power for small fish, so you can feel their bite.”
Do you understand that assuming something is always a mistake? And actually, this work of getting better answers about the domain falls more on our shoulders than on the Expert’s. It’s more about asking the right questions, and certainly, this process becomes more robust the more we do it.
A domain can be expressed with various models, and each model can be expressed in countless ways in our code, certainly, we still depend a lot on how the language and framework we’re using can abstract layers and complexity at the code level, but today I want to avoid talking about code.
What frequently happens is that whoever writes the code didn’t exactly participate in the design/modeling of this model, and at this point, the language can start to get lost. If any decision at the code level changes how the model represents something, the model should also be modified, understand? There can’t be a disparity between the language represented in the model and in the code, and having these people distant from each end certainly impacts a ubiquitous language that loses strength. It’s always a two-way street between the representation of our domain knowledge model and its implementation. One must cast a shadow on the other; each decision and discovery in code influences the domain structure, and vice versa.
It’s quite easy to fail
I tried to make it clear that each model has a context, and while we work with just one, the context is implicit and “easier” to understand, but a real application will interact with more code, and often even external to our codebase.
How do we define and delimit where one model ends and another begins? There’s no magic to divide a large model into smaller models. Maybe a good North Star is to include elements in the model that are related to its natural concept; it should be small and independent enough so that we can assign it to a team.
We call Bounded Context the pattern of strategically defining the boundaries between each model and teams. It became clear that we can’t have internal contradictions in the model, so the best way is to divide responsibilities in the best possible way.
You: “So, when a customer looks for a ‘product’…”
Catalog Manager: “They’re looking for fishing equipment with specifications.”
Inventory Manager: “Wait, I call that ‘items’ – things whose quantities I monitor.”
Expert: “I think of them as ‘equipment components’ that can be combined.”
Sales Manager: “To me, they’re ‘SKUs’ with prices and promotions.”
The same concept, with different names and concerns! We can map it to something like:
1. Equipment Catalog Context
Concern: Managing fishing equipment specifications and compatibility
They have:
- Fishing Equipment (not "Product")
- Compatibility Rules
- Technical Specifications
- Equipment Categories
2. Recommendation Context
Concern: Matching equipment to fishing scenarios and angler needs
They have:
- Equipment Component (not "Product")
- Angler (not "Customer")
- Equipment Combo
- Fishing Scenario
3. Sales Context
Concern: Product listings, pricing and e-commerce operations
They have:
- Product Listing (not "Equipment")
- SKU
- Price
- Promotional Campaign
4. Inventory Context
Concern: Stock management, storage and fulfillment
They have:
- Stock Item (not "Product" or "Equipment")
- Stock Level
- Warehouse Location
- Replenishment
This way, if we can clearly delimit each boundary, each team can work as independently as possible (please don’t take this as if it weren’t necessary to continue having communication).
- The catalog team focuses on technical specifications;
- The recommendation team focuses on fishing effectiveness;
- The sales team focuses on conversion and pricing;
The inventory team focuses on fulfillment and stock.
The main teaching is that each context has its own domain model that serves its specific needs. Understand each CONTEXT very well.
Anemic Domain Model
Now, thinking that we started a new project today, in general, we don’t have the context of how and why things were written a certain way, even more so if in this case the lack of information and clarity of the code itself brings more doubts than certainty. A feeling that always hits us is: “I wish I could rewrite all of this from scratch!”
Most of the time, we’re not even given a presentation of “What is the project’s intention?” How many times do we summarize it as something like “it’s basically just CRUDs”? And honestly, if a project is summarized as just migration modeling for the database, probably someone understood something very wrong (I’m not saying there aren’t projects that can be summarized “just” to that, but they’re hardly so).
This weak and unstructured model will look like a real model, may even have abstractions and code that’s easier to change and we’ll certainly benefit from this in most projects, but with this, we sometimes add more complexity than perhaps necessary but we fail to maintain boundaries and strategies that allow us to keep the model and the code (or whatever) still speaking the same language.
Headaches from abstractions
I tried to repeat that, in the end, we have to benefit from communication.
It’s about listening and being heard, not keeping this on a one-way street (maybe that’s even why LLMs won’t steal the job of good architects).
People won’t answer us something like “You are absolutely correct!” at every step of a dialogue (or at least they shouldn’t).
Taking a step back whenever possible and (once again) keeping the doubt tick away from our domain, starving without blood spilled from ambiguities.
- “What happens before this?”
- “What happens after this?”
- “Who else cares about this concept?”
- “In what situations would this behave differently?”
- “What properties does X have that Y doesn’t?”
- “When would you use this word instead of that one?”
- “Etc”
It’s very clear that if we had only accepted the understanding without any questioning, we would then design and apply this truth in code, staying distant from what the real representation should be (once again). And if in the end, we have only layers and more layers of abstractions that don’t faithfully align with the domain, it’s just headaches and more lines of code for maintenance and (certainly) demolition in the future…
And don’t get me wrong, code shouldn’t be written in stone, far from it, but if we don’t stay faithful to our ubiquitous language, all the sweat will have been in vain.
Correct levels of isolation
Maybe more and more we’ll stumble upon legacy systems that need to be consumed somehow, but the rules and abstractions inside don’t match much with what’s expected, both at a technical level and nomenclature (imagine it could even have been written in a different language from other projects at our client).
How to isolate this so we can then abstract it and consume what’s necessary for our understanding and the patterns of our project?
Continuing with the example, we now have to connect to a very old Inventory system for our fishing articles (written in COBOL).
At first, we feel that the vocabulary is confusing, and we don’t have any “native” developers from this codebase to help us.
ITEMREC - Item record, maybe?
QTYONHAND - Quantity on hand, certainly
QTYCOMMIT - Committed quantity, what does this mean?
QTYHELD - Held quantity, what's the difference with on hand?
If we simply accept and consume these truths from the other system directly, we’ll pollute our domain.
Certainly, the best thing before proceeding with coding is to bring at least one Expert, since the knowledge about the code has been lost (and what’s written doesn’t tell us much).
You: “So, we receive the available quantity and the committed quantity from the old system…”
Expert: “Wait, what’s ‘committed quantity’? We don’t use that term.”
You: “That’s what the old system calls it…”
Expert: “Ah! You mean Reserved Stock! It’s stock we’ve promised to customers, but haven’t shipped yet.”
And the conversation continues…
At this moment, we can build an Anti-corruption Layer. Besides understanding, we need to “translate” these legacy terms to our language and avoid them spilling into our domain.
If we have repository abstractions that depend on others to work, and worse, are tied to external knowledge, we’re already failing with our decoupling.
How much of our personal understanding are we putting at our fingertips and when someone new joins the project, maybe we have to explain how some nuances work, because we made some translations of requirements instead of really using the ubiquitous language.
And our domains can’t have a dependency on anything external. And not just them, if, for some reason, our repository previously dealt with a specific type of data or connected to a specific type of database, what happens if this needs to change, and we didn’t define the boundaries where they really should be?
Technical debt is never paid
I commented about building sand castles in the air because our work in building software can be so abstract, and in the end, each grain of sand that represents the knowledge and understanding deposited there can somehow be used to build another castle or figure in the sand tomorrow. Tearing down walls and building new points in our castle is less costly than doing it (mostly) in real life, with concrete walls and steel bridges.
We know that every line of code we write carries with it the need to be revisited in the future. And not necessarily because we made intentional mistakes, but because our understanding of the domain and the techniques employed evolves.
Often this debt is even known while we send it to production, but due to business reasons or any others, we can’t pay it immediately, and honestly, that’s not even the problem. The problem is that this debt is never paid; in short, it’s never a good time to go back to “finished” code, but we know how much this debt can grow and haunt us, sooner or later.
The best of worlds is to combine, even beforehand, moments when the team can focus on paying these debts. Once a week? Once per Sprint? One task per month? Somehow it has to be done. This, without thinking about debts that will arise, is dependent on advances in employed technologies and not just the evolution of our domain and understanding.
Well, did you notice that much of what we covered here today isn’t just related to DDD but about how we can make good decisions day-to-day, with (once again) lots of communication and hints of pragmatism.
Making it clear that this is a snippet of my studies!
If any fishing expert finds any atrocity committed on my part regarding terminology, please keep swimming.
All reflections here were while I consumed these materials:
- 7 Reasons Why DDD Projects Fail
- DDD Quickly (Chapters 1 and 2)
See you on GitHub! 🚀
We want to work with you. Check out our Services page!