I recently attended a Domain Driven Design (DDD) workshop and worked on a project which is based on DDD principles. This article will give you a very quick walkthrough of all major DDD concepts. So if you want to get a high level overview of what DDD actually is and what is included in it, you are at right place. :D
In this article we are going to look a short overview of following DDD topics :
- What is DDD
- Why DDD
- DDD Concepts
- Domain event
- Value Object
- Domain Services (verb)
- Bounded context
- UBIQUITOUS Language
- Context Maps
- Modular monoliths
- Event sourcing
- Data mesh
What is DDD:
Before rightly jumping in definition stuffs, lets just see a scenario. There is a team of developers who are developing a software for an enterprise, bank, or an e-commerce business. The developers developed whole application without thinking about domain and without proper designing the architecture of application. The software was working very smoothly. After few days the requirement came to add new features to application and to scale it. They took weeks to add a new feature. Why?
Because, there was no proper domain set up, design wasn’t proper there. There was dependency between classes and features and adding new change start to break dependent code. Sad right! The software initially which they developed within short time, now making changes to that is difficult. Extending software has became the issue. That’s where properly designed software come to rescue. We segregate the responsibility and develop software with every domain having responsibility defined.
If you see figure 1. Ideal Graph, this is what ideal case looks like (which is practically impossible :p ) but what it depicts is features developed per unit time is constant throughout.
In figure 2. For a software developed with proper design, initial rate of delivering features is little low, but later the rate of developing software become constant.
In figure 3. For a software developed without proper design, initial rate of delivering features is fast, but later the rate of developing software slows down and it takes a lot of time to integrate new feature. Sometime it worsen more and system start breaking and it turn like poorly developed software as shown in figure 4.
So what is DDD?
Before seeing DDD, lets see what domain is. Domain is specific area or topic that a software system focuses on. For example, in a banking application, the domain would be banking, loan, investment, payment, etc. Domain is like different categories of operations that are there in application.
Domain-Driven Design (DDD) is an approach to software development that aims to create software that solves real-world problems in an effective and efficient way. DDD emphasises understanding the business domain and modeling it in software, so that the software closely reflects the needs and requirements of the business. This concept comes from book Domain Driven Design published in 2003 book by Eric Evans. DDD aims to ease the creation of complex applications by connecting the related pieces of software into an ever-evolving model.
DDD helps in writing code closely to the business domain.
It helps to manage Domain Complexity
It help in writing more maintainable code with loose coupling and high cohesion
It also allow to Extend software with new features with lesser efforts.
Domain event :
Business people are interested in event happened in system. Like for a e-commerce site business is interested in information like user added product to cart, user made payment, order placed. It is also helpful for business to track all the actions performed by user and do some analysis over it.
So a domain event is an occurrence in the business domain that is important to the stakeholders of the system. Examples of domain events include “Order Placed”, “Payment Received”, “Customer Registered”, etc. The Kafka event/message we sent are also domain event. Domain event is also used as bridge between two domain for communication.
There are various definitions available on internet regarding entity. Even if you coming from Object oriented design word, you would have definitely heard about entity. But in simple terms entity is an object which has unique id. Consider a banking domain, in banking domain customer is an entity (every customer can be uniquely identified by its own id), account is an entity (every account is uniquely identified, no two customer can have same account no).
Value Object :
Value adding thing, thing that has some value but isn’t an entity. A value object in DDD is an immutable object that represents a concept or entity based on its attribute values rather than its identity i.e object which hold some value. For eg. Rupee is value object (both 10 rupee note mean same for us) but entity for rbi ( different note have unique id (serial number)
Domain Services :
When some domain related logic cant’t be put into Entity or Value object, Domain service comes in picture. A domain service in DDD is a stateless helper class/utility. It is place where you can put business rules for interaction between multiple objects or entities in the domain. Sound a little complicated right? Lets see with example:
So lets consider there is a banking system. There is domain called banking or financial service. The entities for banking are Account and Customer (both can be uniquely identified by id). There can be one to many relationship between customer and account because a customer can have multiple bank account. But lets consider one account for now. So there is business operation of transferring fund from one customer account to another customer account. The operation is payment or money transfer. The responsibility of both entities are:
Customer Entity: Stores and maintains customer data (name, contact, identification), manages relationships (associated accounts, beneficiaries), and handles preferences (language, notifications).
Account Entity: Tracks and manages account specifics (account number, type), balances, transaction history (date, amount, type), and enforces account-specific rules (minimum balance, transaction limits).
You can’t put logic of transfer in account or customer entities because it violates single responsibility priciple and increase domain complexity. There is complex process associated with payment like validating account balances, checking transaction limits, and updating multiple accounts simultaneously. So where to put payment logic? So for this we can have a domain service called payment service.
By using a PaymentService, you can keep the Customer and Account entities focused on their core responsibilities while maintaining a separate service to handle the complexities and variations of payment processing. This approach aligns with the principles of separation of concerns, abstraction, and flexibility in software design. You shouldn’t confuse domain service with application service in 3 tier architechture. We will cover in detail about Domain service in a separate article. For now this is enough to give you glimpse of why domain service is there and what its responsibility is.
Domain service take domain object as input, do some operation on it, and return domain object.
So, if you coming from computer science background you would definitely heard about ACID properties in database transaction. Let me just put in short about it:
A: Atomicity -> This property insures that the database transaction is treated as complete or none if any part of the transaction fails, maintaining the system in a consistent state.
C: Consistency -> The database must be consistent before and after transaction
I: Isolation -> Multiple concurrent transactions do not interfere with each other and provides the illusion that each transaction is executing in isolation.
D: Durability -> Durability ensures that changes made into db after transaction are permanent and survive any subsequent system failure.
So when entity claims to maintain consistency of another entity, i.e when an entity take care of atomicity and consistency the entity upgrades to aggregate. Lets better understand with an example:
Suppose we have a customer who has multiple bank account. Consider a scenario, customer update his address in his profile. We want to update his address in all his bank account. Customer is an entity, Account is separate entity. So how will you implement this functionality as developer. What i can think of is:
i. create a method in customer entity named changeAddress
ii. It will take new address as parameter.
iii. and update address of customer
iv. also customer also has one to many relationship as we discussed before in domain service. So there will be list of Bank Account.
v) in this method, it will traverse over bankAccount list and update address in every bank account.
Have you noticed here, Customer entity is taking care of maintaining atomicity and consistency of Account entity. When a entity take care of maintaining atomicity and consistency of other entity, it has become an aggregate. Whenever you found an aggregate you potentially found a new microservice -> customer microservice.
Bounded Context is about boundaries. In a e-commerce domain there are different sub-domain like Shopping, Product Catalog, Pricing, Payment. In every sub-domain we have Product. But product in all these sub-domain is different. What Object Oriented programming suggest is to reuse Product class. DDD says Product in Shopping cart is different than Product in Inventory. You are better off with having two different Product classes. Bounded Context is linguistic boundary. So each Bounded Context has its own ubiquitous language. Bounded Context in DDD is a well-defined and encapsulated portion of a software system that has its own domain model, vocabulary, and rules. You can say different teams in a software can be different bounded context considering different team are working on different sub domain.
Ubiquitous Language refers to a shared language used by all stakeholders involved in the development process. It is a common vocabulary that helps bridge the gap between domain experts, developers, and other team members. While naming domain and entities and designing system you should use a Ubiquitous Language, a common language that used by all stakeholders. Vocabulary of Ubiquitous Language include name of classes, domain events, prominent operations, documentation. Developer and domain expert talk in same language.
Ubiquitous Language applies within bounded context and NOT across bounded contexts. Bounded contexts exists because they have the Ubiquitous language. When language changes we get new bounded context. Each bounded context has its own Ubiquitous Language.
There is an application, lets say E commerce application. This app will have different parts or modules and different team for handling each module. Module is nothing but bounded context you can say from DDD point of view. For example, you might have a bounded context for handling user authentication, another for managing product inventory, and yet another for processing orders. A context map is like a map that shows connection between this different modules. In context map you can use different relationship to depict relationship between different bounded context.
The different kind of relationship that can be there are partnership, customer supplier, shared kernel, anti-corruption layer. You can learn in detail about it. I recently found a use case where anti-corruption layer relationship can be used and i think its very useful if one knows about it. It will definitely help you to avoid changing your internal code base of existing system, if there is change in other system.
Modular Monolith in DDD follows approach like monolith architecture pattern in which a single application is there which has separate modules based on bounded contexts(sub domain). For example, there is an e commerce application that has different bounded context i.e modules like cart, login, authentication, order, product etc. then in modular monolith all these different bounded context will be there in same application but as different modules or package and it will contain all related models, service with it. This modular approach helps in maintaining the separation of concerns and improves the scalability and maintainability of the system. This approach also provides a stepping stone towards a more distributed and microservices-based architecture for future if needed.
Event sourcing in Domain-Driven Design (DDD) is a pattern where the state of an application is represented as a sequence of immutable events stored in an event log. Instead of persisting the current state, events are recorded to capture changes or actions that have occurred over time. As a developer you might have seen examples of application that send kafka message (Event message) after every state change (process change) in application. That can be called as event sourcing.
Command Query Responsibility Segregation is an architectural pattern that separates the read(query) and write(command) operations of a system into separate models. In a CQRS system, the read model is optimized for querying data, while the write model is optimized for modifying data. This separation of concerns can help improve the performance, scalability, and complexity of a system.
Data Mesh is an organizational approach where data ownership and responsibility are distributed among teams or domains. Each team treats data as a product, manages their own data assets, and has autonomy over data infrastructure and governance. Collaboration and discoverability are encouraged through well-defined interfaces, and a federated data architecture allows integration when needed. The goal is to improve agility, scalability, and data-driven decision-making within the organization.
So that was a walkthrough of all major DDD concepts. I have written this article based on my understanding about DDD. Thank for reading till here. If you have any feedback and suggestions for me please do share.