Software Philosophy - Part I: Domain Driven Design

In 2019, I was part of the Statsbomb team and the company was really taking off with it's football collection and analytics platform. We were asked to re-design the data collection platform to support realtime events. This was a big task that got everyone across the tech team (in 2 countries) to have deep discussions about every single UI component, service, database and tool. We had to design the system from the ground up.
Our goal was clear, but we had no idea how to achieve it, there were a lot of options and tons of tooling that could "theoretically" get us there. Therefore, lots of different opinions.
My first idea was to stream changing events from our core DB into a new Kafka cluster and have them delivered to our processing services. Well, looking at this now, it's not such a terrible idea, and if I remember correctly, after all the heated discussions we got this implemented somehow.
The Journey Begins
What I strongly remember are the long and deep discussions I had with team about how we should think about the problem itself.
This was my first real interaction with topics like FP, Domain Driven Design, EDA, Streaming Systems and most of the main areas that I have worked on ever since.
About This Series
In this series of blogs, I'll try to go over one topic at a time and write a comprehensive guide about it and share some resources that I find invaluable.
I'll cover topics such as:
- ✅ Domain Driven Design
- 🔄 Event Driven Architecture
- 🔄 Functional Programming
- 🔄 Reactive Programming
Why Start With DDD?
Specifically, in this blog, I've decided to start with Domain Driven Design. If you look at any SWE bookshelf, you will probably notice a large blue book. This is the DDD book by Eric Evans.
I decided to start with DDD since it builds the foundation for how we can think about building systems that solve problems. DDD sets the scene for how we can translate business domains into software components. Later in this series we will cover different topics that are adjacent and complementary for DDD like event driven architecture, event storming, reactive and streaming systems.
What's Domain-Driven Design
Domain-Driven Design (and the book by Eric Evans) is a set of software design principles and rules that allows you to design and build system based on a solid model of domain understanding.
This is especially important when you are building software for a complex domain that often requires deep experience to understand and is usually messy.
The Problem
When building software, the toughest part is usually getting communication right. You have the business side, product side and engineering side. And depending on your organization size the communication channel between these sides can get really messy.
But the one thing that's common in most cases regardless of the organization size is that each side of these has a different understanding and view of the problem and the solution.
Often, there are a wide range of reasons why software projects fail. I tried to capture it in this visualization:

Domain Driven Design tries to solve some of the problems in this Venn diagram. It sets a set of rules that helps the engineers build on a modeled understanding of the domain provided by the business and product sides.
The Approach
Looking at the Venn diagram, you can see that a lot of problems come from misalignment and communication gaps. DDD tries to tackle this by establishing a common ground between all sides.
Instead of having engineers build what they think is right, product people designing what they think users want, and business folks trying to explain complex domain knowledge - DDD introduces a systematic approach to get everyone on the same page.
Domain
This word gets thrown around a lot in tech (like many other words), but what does it actually mean? Think about it this way - if you're building a system like Airbnb, your domain isn't just about APIs or databases. It's about understanding how hospitality works, what makes a great guest experience, how hosts manage their properties, and what makes people trust staying in a stranger's home. Your domain is basically your problem space - it's what your business actually does, not the technical implementation of it. This is why engineers who deeply understand their business domain are usually more valuable than those who just write code (even good code).
Ubiquitous Language
This is probably my favorite part of DDD, and I wish I knew about it earlier in my career. The idea is simple - everyone involved in the project should speak the same language.
No more meetings where business calls it "user engagement" while engineers call it "activity_metric" and product calls it "interaction index". When teams use different terms for the same concept, it creates confusion and wastes time in translations. I've seen this happen countless times especially in data projects where data scientists, engineers, and business analysts all use different terms for the same metrics.
Implementing this requires a larger effort than just aligning on names. This means that your system should be designed using the common language. Each service, repo, function, etc. should have a business meaning.
A Must Read if you haven't already.
Model-Driven Design
This is where things get interesting. Once you have your domain understanding and common language, you start modeling your software around it. But unlike traditional approaches where we often model around technical constraints, in DDD you model around domain concepts. For instance, when building a real-time analytics system, instead of thinking about database tables and message queues first, you focus on modeling what an "event" actually means in your domain. How does it change? What rules govern it? This helps make better technical decisions later because your foundation is solid.

Design Principles
After understanding the core ideas behind DDD, we can dive into how to actually implement these ideas. Since DDD pushes us to cover the 2 major areas for any technical project (design and implementation), We can split domain-driven design principles into 3 layers.

Strategic vs Tactical Principles
(DDD) consists of two main design approaches: Strategic and Tactical. Here's the key difference:
Aspect | Strategic Design | Tactical Design |
---|---|---|
Focus | Big picture system organization | Implementation details |
Components | Bounded Contexts, Context Maps, Subdomains | Aggregates, Entities, Value Objects, Events |
Scale | System architecture | Within single bounded contexts |
Who Uses It | Architects and business stakeholders | Technical teams |
Goal | Define system boundaries and interactions | Implement domain logic and rules |

Strategic Design
Strategic design is about understanding the big picture of your system. It's about defining the boundaries of your system and how different parts of your system work together.
This is the solution space and how you can design your services to work together. This is not a technical debate of which tools you should use, but rather it's a modeling approach of how you can design your system to actually reflect the domain.
Here's the key concepts:
1) Bounded Contexts
I like to think of bounded contexts as the building blocks of the system. Take Uber for example - you have the ride-hailing context, the payment context, and the driver management context. Each of these contexts has its own rules, its own data model, and even its own language. The beauty of bounded contexts is that they help you handle complexity. Instead of building one massive system where everything is connected to everything else (we've all been there), you build smaller, focused systems that communicate through well-defined interfaces.
And don't get me wrong, this doesn't mean you can only implement this in a microservices architecture. The idea is about designing the solution space, it's a higher level than the actual system design. You can take this solution space and implement it in any software setup. Monolithic, Microservices, Event-Driven, etc. Even a local desktop application can follow this approach.
2) Context Mapping
Now we've split the system into bounded contexts, you need to figure out how they talk to each other. Context mapping is basically documenting these relationships. This is where you define the boundaries of your system and how different parts of your system work together. In general, we are trying to define the business relationships between the bounded contexts. You might have contexts that share data, contexts that translate between different models, or contexts that need to stay in sync. Understanding these relationships helps you build better integration points and avoid the "everything is connected to everything" mess.
3) Subdomains
Subdomains are the natural divisions in your business domain. It's what differentiates your business from the competition. You usually have:
- Core subdomains (the stuff that makes your business unique)
- Supporting subdomains (important but not unique)
- Generic subdomains (stuff everyone needs)
For instance, Netflix's core subdomain would be their content streaming and recommendation system. This is what gives them edge over their competitors. Their billing system? That's probably a supporting subdomain - important but not what makes them special.
Tactical Design
After laying-out the strategic design (High Level), we usually dive into the tactical design (Implementation Details). This is where we actually start building the system.
Like Strategic Design, Tactical have its own key concepts:
Entities & Value Objects
Entities and Value Objects are similar to classes in OOP, functions in FP, Pods in Kubernetes, etc. You get the idea.
These are the foundational building blocks in DDD. The key distinction lies in how we treat their identity and lifecycle. Entities maintain their identity throughout their lifecycle. No matter what changes happen to their attributes, they're still the same entity. This is not just a technical concept - it's deeply rooted in how we naturally think about things in our domain. When domain experts talk about something that maintains its identity despite changes, that's probably an entity.
Value Objects are a bit more interesting. They have no identity of their own - they're defined entirely by their attributes. When you modify a Value Object's attributes, you're effectively creating a new Value Object. This immutability isn't just a technical choice - it reflects how these concepts work in the real world. Value Objects are perfect for representing domain concepts where the values themselves matter more than any sense of identity.
The real power of Value Objects shows up when you start using them to encapsulate domain rules. Instead of having business logic scattered throughout your codebase, you can bundle it right where it belongs. This makes your code not just cleaner, but also more aligned with the business and product side.
Aggregates
When you study DDD, aggregates are one of the most confusing concepts. But when you think about it from the business side, aggregates are just any group of entities that are tightly coupled. Martin Fowler puts it this way:
Aggregate is a pattern in Domain-Driven Design. A DDD aggregate is a cluster of domain objects that can be treated as a single unit. An example may be an order and its line-items, these will be separate objects, but it's useful to treat the order (together with its line items) as a single aggregate. An aggregate will have one of its component objects be the aggregate root. Any references from outside the aggregate should only go to the aggregate root. The root can thus ensure the integrity of the aggregate as a whole. Aggregates are the basic element of transfer of data storage - you request to load or save whole aggregates. Transactions should not cross aggregate boundaries. DDD Aggregates are sometimes confused with collection classes (lists, maps, etc). DDD aggregates are domain concepts (order, clinic visit, playlist), while collections are generic. An aggregate will often contain multiple collections, together with simple fields. The term "aggregate" is a common one, and is used in various different contexts (e.g. UML), in which case it does not refer to the same concept as a DDD aggregate.
Source: Martin Fowler
Designing aggregates
The thing that took me years to really get is that aggregates should be way smaller than what we initially think. We often try to model too much in a single aggregate because it feels safer to have everything transactional (i.e. changes affect everything immediately), like we're ensuring consistency across everything. But that's a trap - it leads to these massive objects that are hard to work with and even harder to keep consistent (Eventually a massive tech debt). And you later realize that you are not actually benefiting from using aggregates.
In fact, aggregates are all about transactional consistency. If two things need to be consistent with each other all the time, they probably belong in the same aggregate. If eventual consistency is acceptable, they probably don't. This simple rule has saved me from creating monster aggregates many times. One of the things that you have to push when talking with the business side about entities is that if transactional/strong consistency is really required. They usually think that, but when you understand the problem and the domain, most of the time, eventual consistency is enough.
Since aggregates are a set of entities together, it's important to think about an aggregate's design as a tree of entities. (Similar to how a set is implemented, right?).
Events
Events are probably the most popular (and widely-used) topic from DDD. I'll cover events in detail in a different blog post since they span to a wide range of technical topics. For now, it's important to understand that events in DDD represent a log of what happens in the domain and it's objects. If you scan the logged domain events you have a history of what happens.
Events are basically every where. If you login the netflix app. that's an event. Clicking on a new movie to watch, that probably spans a few events.

Repositories & Factories
These two patterns are probably the most practical and straightforward concepts in DDD. They're designed to handle different aspects of working with domain objects - how we store them and how we create them.
You probably used those thousands of different times. In ORMs, Logging packages, API Frameworks, literally everywhere. It'
Repositories Repositories represent how we store our objects. And no, we are not talking about which database to use. It's more about how you separate the concept of storing our entities. It's more about how do you separate concept of storing our entities and objects from the details of how to actually do it. Repositories are focused on the former, They provide a simple way to store and retrieve domain objects while hiding all the technical details of how this actually happens.

The main value of this if you haven't guessed it already is to keep your domain entities logic clear since you don't want to clutter the domain logic with infrastructure details.
Factories
Factories solve a different problem - they handle object creation when it gets complex. And I don't mean complex in terms of technical complexity. I'm talking about business complexity, where creating a valid domain object requires understanding and applying business rules. When you study DDD, factories might seem like overengineering. And for most cases, they are. But once you work with complex domains, you start to appreciate them. They shine when creating an object requires business rules, multiple steps, or coordination between different parts of your system.
Services
Services in DDD are pretty simple to understand - they handle operations that don't fit into entities or value objects. If you are building a bank and trying to design the logic of moving money from one account to another, this is not the responsibility of either account. This is a domain operation that happens for both of them. Services cover this kind of business operations. It has to be stateless and represented with a business name from the ubiquitous language.
They are different from the backend services we are familair with them. A domain service can be a function, a class, an API, basically any implementation that fits the use case.
Conclusion
Now that we covered most topics in DDD, one of the most common mistakes that I made since I learned it is trying to apply it everywhere. I'm a big fan of DDD, but it's not a silver bullet nor is it easy to do. In fact, domain-driven design is a fantastic framework when you are trying to solve a business problem or implement business rules. I've seen it work very well in that case, even though it takes a lot of time, requires deep understanding of the business, and has a steep learning curve.
But in data systems or data-intensive applications, it doesn't work at all. Because in those types of systems, you are more concerned about managing the data than the logic. This is where the efficiency and correctness of the system is your concern, not the logic.
But the reason I like DDD so much is that you don't have to do it all the way. You can use any of its concepts on its own. For instance, using a ubiquitous and common language is usually a good practice in all cases. The event-driven architecture philosophy is an adjacent topic for DDD - if you understand EDA, you're most likely familiar with DDD.

Resources
Here are some resources to learn more about DDD
Member discussion