How Domain-Driven Design Works in Microservices?
Friday, August 09, 2024Microservices were once very revered in the software industry because of the plethora of benefits they offered, such as system stability, clear ownership, flexibility, and more. However, over time, it started facing severe criticism for its increased complexities and difficulties in building even simple features. Nevertheless, everything changed with the advent of the Domain Driven Design (DDD) approach. When implemented with microservices, software development companies can leverage this methodology to maintain software flexibility and scalability while reducing the system complexities. This article explores the concept of Domain Driven Design Microservices, its workflow, advantages, disadvantages, challenges, and, in the end, a small comparison with similar software development methodologies.
1. What is Domain Driven Design?
Domain-driven design is an app design pattern in which developers build architecture models specific to the domains of their business goals. You have to solve a domain-specific issue to achieve a certain business goal. To solve this challenge carefully, you must have in-depth knowledge of the domain.
However, as brilliant as developers are, they can’t possibly become experts on every subject to write relevant code. Therefore, the situation demands that they collaborate with the domain expert to ensure that the code is aligned with the requirements.
Domain experts and developers use a unified language to share the domain knowledge, document, plan, and code. Domain Driven Design works as a framework for software development, enabling the developers to create goal-oriented applications. It can be described as a process that transforms the domain principles into software artifacts.
The main components of DDD include:
1.1 Bounded Context (BC)
Depending on the context, one can derive different meanings from the same word. For example, in one context the word “bank” means a financial institution where people keep their money, whereas in another it means the side of the river.
You have to use bounded context to give any term, a specific meaning from your chosen domain. A traditional development model would have you use these terms without specifying the meaning or differences between them.
But with domain-driven design, you can divide the domain into different sub-domains. It is used simply to tell you whether the system is using the word bank within a financial or geographical context.
1.2 Context Map
Bounded contexts create multiple sub-domains within the domain. For your app to smoothly function, these subdomains must communicate and work together. That’s where you need a context map. Let’s take an example of the eCommerce domain. You have product inventory, payment process, shipping process, and more subdomains with a bounded context.
For a successful sale, all of these subdomains need to work in sync. The inventory must be checked before selling the products to ensure a smooth financial transaction, collect shipping details, and verify the delivery is made correctly. A context map depicts the relations between all these processes.
With multiple subdomains in place that act as boundaries between each other, context mapping helps you plot these subdomains into different channels to ensure a seamless workflow.
1.3 Unified Language
You use a bounded context to provide a specific context. But what are you referring to in that context? Often, the terminologies used by the domain experts and the developers differ. The vocabulary of people varies based on their work and experience.
While developing software, coding requires precision. Hence, it is necessary to come up with a unified language that defines the bounded context. Defining the bounded context will help you build a standard vocabulary for your software. It acts as a base in the conversation between the domain experts and the developers.
2. How Domain-Driven Design Microservices Work?
The Domain Driven Design consists of two phases for designing microservices.
- Strategic phase: In this phase, define the bounded contexts and carry out context mapping.
- Tactical phase: Here, follow the business rules of the subdomains to model every bounded context.
Now, let’s have a comprehensive discussion on each phase.
2.1 Strategic Phase
It’s a brainstorming phase where the product owners, domain experts, developers, and analysts all come together to share knowledge, strategize, and create an initial plan. It’s an opportunity to clearly define the business requirements, models, scope of work, and project milestones.
DDD takes a high-level top-to-bottom approach. So, after defining the business goals, now create bounded contexts based on those goals. The logical boundaries of the microservices are identified based on these bounded contexts.
Upon completion of DDD analysis, we get a context map that outlines the details of the bounded contexts and their relationships.
2.2 Tactical Phase
Software development involves problem-solving, where real-life problems are modeled and then solved using code. We just determined the bounded contexts and their relationships in the strategic phase.
Now, this stage of domain-driven design focuses on modeling with the help of contexts. Not depending on the technology underneath, these models conceptualize the subdomains.
3. Benefits of Using Domain-Driven Design
Utilizing the domain-driven design for your microservices project has multiple benefits. Here are a few:
3.1 Clear Communication
When the common terms are established for the domain model of your project, communication becomes easier. Hence, using ubiquitous language can help prevent unnecessary jargon and miscommunication.
3.2 Enhanced Flexibility
DDD takes an object-oriented approach. So, your entire system is made up of various components, which makes your domain model encapsulated and modular. This enables you to make changes or updates to your system without harming its functionality.
3.3 Domain Over Interface
The term “domain-driven design” itself suggests that the core concept of this approach is the domain. So, DDD is used for creating apps that represent the domain and align with it under the guidance of the domain experts. However, this doesn’t mean that you have to leave out UX hanging. It is necessary to maintain the balance. Focusing on the domain helps you build a product that resonates with the targeted audience. Meanwhile, delivering better UX would mean that the targeted audience will appreciate the app they are using.
4. Limitations of Using Domain-Driven Design
Despite having a plethora of advantages, there are some downsides to using the DDD in microservices approach.
4.1 Requires Deep Domain Expertise
As we discussed earlier, it doesn’t matter how brilliant developers are, no one can be an expert on everything. Therefore, your team requires at least one domain expert who has a thorough understanding of the subject. Sometimes, it may be necessary to call out multiple domain experts to support you throughout the entire project.
4.2 Demands Continuous Integration
In many instances, iterative development is considered a benefit. But DDD comes with continuous integration to enable you to make adjustments to the product whenever necessary. If your organization is flexible and agile, you should not have a problem. But if not, then this software development approach can be very challenging.
4.3 Implementation becomes Difficult When Technical Complexities are High
If there are any complexities in the domain, DDD is an ideal solution for you. However, if you are facing high technical complexities, then DDD is not much of a suitable solution. Successfully implementing this process requires the oversight of a business-oriented domain expert.
They work out with the developers and prepare a ubiquitous language for the project that helps them communicate without any problem. Nevertheless, when the technical complexities are high, then it would be difficult for the expert to grasp it all.
When a team member can not completely understand the technical aspects or requirements of the project, then problems ought to arise down the line.
5. Domain-Driven Design Example
Let’s take eCommerce as an example. The business domain in this scenario would be to process online orders. Customers would have to search or browse through products to place their orders. Then they pick the one they want, verify the order, select the shipping type, and pay for the order. Now, the application will process the data provided by the client.
In this case, the eCommerce application comprises the following layers:
5.1 User Interface
This layer provides everything that the user wants. It analyzes the user actions and inputs to provide the requested information. The products and all the relevant information are available on this layer.
5.2 Application Layer
This component of the app serves as a guide for users as they navigate from one UI to another. It doesn’t have any business logic or data access, but it can perform a simple validation. Moreover, it can also communicate with the application layers of other systems. It is responsible for assigning duties to the domain objects and organizing them. Any bounded context can access this layer.
5.3 Domain Layer
Here in the concept of business domains, you can find business rules and all the information regarding the business case. Additionally, you can find the entities that have a unique identity guaranteed through a unique key. Even if there are any changes in their attributes, their unique identity won’t change.
This layer also consists of value objects. Although they don’t have any unique identity, they do represent attributes shared by various entities. Stateless services that are defined with the operational behavior, also known as domain services, are found here.
These services are named using a common language so that it doesn’t deprive the entities and value objects of their actions and accountability. Customers can freely use any given service instance despite its history. The domain layer is located in the center of the business app and it is independent of other layers and frameworks.
5.4 Infrastructure Layer
This layer manages the persistent data storage in domain entities and other databases. Hence, it can support layer-to-layer communication. In addition to that, the infrastructure layer also contains supporting libraries for the UI layer.
Have a look at this Tweet defining how these layers play a role in Domain-driven design for microservices.
6. Challenges in Implementing Domain-Driven Design for Microservices
DDD allows you to model your domain to craft a software system that reflects your business. However, there are some challenges along the way.
6.1 Aligning Business Domain Boundaries with Technical Constraints
Aligning the technical boundaries with that of the business domain is the primary challenge you will face when using the DDD. That is why developers work closely with domain experts to ensure that their software model aligns with both the organizational structure as well as technical constraints.
Domain Driven Design in microservices adopts a component-driven development approach that urges software teams to build the system as a set of components. Here, each component is developed and maintained independently by a separate team.
Every component of the system reflects a specific aspect of the domain. Their scopes and categories are managed as per their business responsibilities. This alignment helps in synchronizing organizational boundaries with technical constraints.
In such cases, clear communication and effective collaboration among the teams is critical for the success of the project. Once you break down the boundaries, it’s quite easy to craft a system that reflects your business requirements.
6.2 Implementing Reliable Events
Execution of reliable events is another major challenge you may face with DDD. You have a dedicated layer that persists entities and events decoupling dependencies in external systems from the domain layer to communicate with other systems.
Ideally, you would have to use an atomic way to commit database changes and send events. But the “Two-phase commit” comes with a coordinate failure and performance issues, which many modern systems don’t support. In short, it’s quite difficult to achieve this level of reliability.
If the event is sent before we commit the transaction, it might lead to an inconsistent state because there is no guarantee that you can commit a transaction afterward. Therefore, we must commit the database changes first and then send the events. Now it’s up to us to decide whether we want to:
- send an event with an “at most once” guarantee
- send an event with an “at least once” guarantee
You don’t have to do anything special when sending an event with the “at most once” guarantee. It’s quite straightforward. We have to acknowledge that if the app fails just before you attempt to send the event, it might be lost forever. Not every requirement would accept such a guarantee.
On the other hand, the implementation of an event with an “at least once” guarantee is complicated. Because you have to save the event as well as the database changes within the same storage transaction. You might also need to utilize dedicated jobs or specialized tools such as Kafka Connect or Debezium to deliver such an event. The pattern is called the Change Data Capture pattern. You can get unique solutions from cloud providers to stream these changes.
To save the event and primary changes, we must use the same transaction. This leads us to the initial question: Should we need transaction management on the application layer or Should we need a “consistent” save method that accepts an event and aggregates?
Transactions aren’t supported in the NoSQL databases, using them with Persistence-Oriented Repositories will complicate things. As a result, the best approach would be to monitor the changes on the database table and create events using the domain layer.
There isn’t any effective solution for this issue. So, your app wouldn’t be completely persistence-agnostic and may encounter challenges in swapping the database. You must at least have a good understanding of the database types and their guarantee to effectively execute the persistence layer for reliable events.
6.3 Overcoming Microservices Complexity
Although DDD is an organized approach to software development, implementing it with the microservices architecture can be a little complicated. The major difficulty you may face is to break down the entire system into various bounded contexts. This task requires a deep understanding of the domain and a clear distinction of business responsibilities. Even a small misstep can lead to heavy service dependencies.
Therefore, you have to maintain the modularity by clearly determining the boundaries for the bounded contexts. It helps you specify the business logic and reduce the risks of the right service coupling. You should also encourage all the teams engaged in the project to share their knowledge. It helps redefine microservices and identify potential pitfalls early in the development process.
7. Comparing Domain-Driven Design (DDD) with TDD and BDD
Domain-driven design is most suitable for developing microservices applications with a complex structure that requires extra effort in planning. For small systems, developers would prefer to use Test Driven Development, which is easy to start with for building apps consisting of a single or a few microservices.
It’s better to go with the Behavior Driven Development approach when dealing with medium-complexity systems. It leverages acceptance testing and integration testing to validate the system’s behavior. The BDD approach works well for systems with low to medium complexities, but it slows down once you reach a certain threshold.
If your requirements demand, you can combine all three approaches: DDD, TDD, and BDD in your project. You just have to pick the best approach for each stage of your software development lifecycle.
For example, DDD works best when it comes to identifying microservices and their relationships. Use TDD or BDD to build the services, depending on their complexities.
8. Conclusion
Solving domain-specific problems has now become easy, thanks to the software development methodology of domain-driven design. In this article, we discussed how you can implement the principles of DDD with microservices architecture.
We also discuss the advantages and disadvantages of using such an approach and browsed through the challenges in its effective implementation and how to overcome them. Our goal is to empower you with the information that will help you make better decisions regarding the development of your business applications.
FAQs
What is DDD in microservices and why it is required?
The DDD in microservices is a software development methodology in which all the components of the program reflect the specific aspects of the domain. It is used to model relevant use cases to solve domain-specific problems.
What is the purpose of DDD?
The purpose of the DDD approach to software development is to solve domain-specific problems. It does so by understanding and modeling the business domain while aligning the technical requirements with the business needs to deliver high-quality software.
What are the benefits of DDD architecture?
Among the many benefits it provides, DDD architecture simplifies microservices app development and enhances the scalability and flexibility of the system.
Comments