19 Summary One Microservice Design and Decomposition Principles to Adhere To

19 Summary One - Microservice Design and Decomposition Principles to Adhere To #

Hello, I’m Ou Chuangxin.

We have discussed a lot about the design methods and practical cases of Domain-Driven Design (DDD) before. Although the design philosophy and methods of DDD are very good, there may be differences in the implementation strategies of DDD and microservices due to the development process, technological advancements, and cultural differences within each enterprise. So, how should we apply DDD and microservices in the face of such differences? Today, let’s talk about the design principles and evolutionary strategies of microservices.

Evolution Strategies for Microservices #

When transitioning from a monolithic system to microservices, there are generally two strategies: the strangler strategy and the refurbisher strategy.

1. Strangler Strategy #

The strangler strategy is a gradual process of peeling off business capabilities and replacing the original monolithic system with microservices. It involves domain modeling the monolithic system and, based on domain boundaries, extracting new features and partial business capabilities outside of the monolithic system to construct independent microservices. The new microservices maintain loose coupling with the monolithic system.

Over time, most of the functionality of the monolithic system will be extracted as independent microservices, effectively “strangling” the original monolithic system. The strangler strategy is similar to building demolition, where new buildings are constructed before demolishing old ones.

2. Refurbisher Strategy #

The refurbisher strategy is a strategy that maintains the overall capabilities of the existing system while gradually optimizing the system’s overall capabilities. It involves isolating and extracting certain functions that impact the overall business of the system and make them independent microservices. This may include high-performance demanding functions or functions with low code quality or inconsistent version releases.

By isolating these functions, we can balance the overall and partial aspects of the system and resolve issues of overall system inconsistency. The refurbisher strategy is similar to renovating ancient buildings, where problematic parts of the building are reconstructed or repaired and then reintegrated into the original structure, maintaining the original appearance and functionality. Observers may not notice the changes from the outside, but the quality of the building is greatly improved.

In fact, there is a third strategy called “starting from scratch,” which, as the name suggests, involves completely rebuilding the original system. During the construction period, the original monolithic system continues to operate as usual, typically without developing new requirements. A new project team is organized to conduct domain modeling based on the functionality domain of the original system and develop new microservices. After completing the data migration, the old and new systems are switched.

For large-scale core systems, I generally do not recommend this strategy due to the instability after system refactoring, a large number of unknown potential technical risks, and the uncertainty factor of project team coordination under the new development mode, which greatly increases the difficulty of project implementation.

Domain Modeling Strategies in Different Scenarios #

Due to the various situations and development processes within enterprises, there are different scenarios when it comes to domain modeling. This includes transforming monolithic systems into microservices, modeling and designing new and unknown domains, as well as optimizing specific parts of legacy systems. In different scenarios, different strategies for domain modeling are required. Let’s explore how domain modeling is done in several types of scenarios.

1. New Systems #

New systems can be categorized into two scenarios: simple and complex domain modeling.

Simple Domain Modeling

For simple business domains where a domain represents a small sub-domain, the domain modeling process is relatively straightforward. The domain model can be constructed directly using the event storming method.

Complex Domain Modeling

In complex business domains, domain modeling may require multiple levels of decomposition before starting. The domain needs to be divided into sub-domains, and these sub-domains may need further decomposition. For example, in insurance, it needs to be divided into sub-domains such as underwriting, claims, payment, and reinsurance. The underwriting sub-domain can be further divided into sub-sub-domains like insurance application and policy management. If complex domains are not further subdivided, the engineering effort required for domain modeling will be significant. It is not easy to complete a large domain modeling task using the event storming method, and even if it is done, the result may not be satisfactory.

For complex domains, we can follow three steps to complete domain modeling and microservice design.

Step 1: Decompose Domains and Establish Domain Models

Based on the characteristics of the business domain and considering factors such as process nodes or functional aggregation modules, the domain is decomposed into appropriately sized sub-domains. Aggregates and bounded contexts are defined for each sub-domain using event storming, and the initial domain model for each sub-domain is established.

Step 2: Fine-tune the Domain Models

Review and fine-tune the domain models for all sub-domains. The focus of this process is to consider the restructuring of aggregates in different domain models. Simultaneously, the boundaries between domain models and aggregates, as well as the dependencies between services and events, are taken into account to determine the final domain model.

Step 3: Microservice Design and Decomposition

Based on the domain model and microservice decomposition principles, complete the decomposition and design of microservices.

2. Legacy Monolithic Systems #

When dealing with a legacy monolithic system, we only need to extract specific functionalities as microservices, while keeping the rest as a monolith. For example, we can split performance bottleneck modules into microservices. In this case, we can consider this particular functionality as a simple sub-domain and follow the approach for simple domain modeling. When designing microservices, we also need to consider the compatibility of services and business between the old and new systems, and if necessary, introduce a protective layer.

Misconceptions about using DDD #

After being exposed to microservices, many people tend to design all systems as microservices architectures. However, in some business scenarios, developing a monolithic architecture may have lower development costs and higher efficiency, making it a good choice. Similarly, although DDD is great, some traditional design methods still have their place in microservices design. Let’s talk about several misconceptions about using DDD.

1. Applying DDD to all domains #

After learning DDD, many people may try to apply it to all business domains, using DDD for the entire design. DDD is a relatively complex process, from strategic design to tactical design. First, the company needs to cultivate a culture of DDD. Second, the design and technical abilities of team members need to be relatively high. In situations with limited resources, it is advisable to focus on the core domain. It is recommended to start with the core domain of a rich domain model, instead of trying to apply it to all business domains at once.

2. Applying only DDD tactical design methods #

Different design methods have their own suitable environments, so we should choose the method that is most skilled in that specific scenario. DDD has many concepts and tactical design methods, such as aggregate roots and value objects. Aggregate roots use repositories to manage the consistency of entity data within aggregates. This method is very effective for managing new and modified data. For example, when modifying order data, it can ensure the consistency between the total amount of the order and the amounts of all item details. However, it is not good at handling larger amounts of data queries and can even have delayed loading which affects efficiency.

On the other hand, traditional design methods can often solve problems quickly with a simple SQL statement. For many business scenarios in sparse domain models, such as data statistics and analysis, many of DDD’s methods may not be needed or may not be convenient to use, while traditional methods can easily solve them.

Therefore, while adhering to principles such as domain boundaries and microservices layering, when designing at the tactical level, we should choose the most suitable method, not just DDD design methods. Of course, traditional design methods should also be considered. The priority should be to quickly and efficiently solve practical problems, rather than doing DDD just for the sake of it.

3. Overemphasis on tactical design and neglecting strategic design #

Many DDD beginners, with the primary goal of developing microservices, tend to focus more on the tactical design implementation of DDD. However, they may overlook the fact that DDD is a comprehensive solution from domain modeling to the implementation of microservices.

The domain model built during strategic design is the input for microservices design and development. It determines the boundaries, aggregates, code objects, and services of microservices. The clarity of the domain model’s boundaries and the explicit definition of domain objects will determine the quality of microservices design and development. Without the input of a domain model, it is impossible to talk about designing and developing microservices based on DDD. Therefore, while tactical design is important, strategic design should be given equal attention.

4. DDD is only suitable for microservices #

DDD became popular only after the emergence of microservices, so many people believe that DDD is only applicable to microservices. However, throughout the silent twenty-plus years of DDD, it has actually been applied in the design of monolithic applications as well.

During project implementation, it is necessary to take the core design principles and concepts of DDD into account, and combine them with specific business scenarios and team technical characteristics, using a combination of various methods and applying them flexibly, in order to solve practical problems in the right way.

Microservices Design Principles #

In microservices design principles, common design principles such as high cohesion, loose coupling, reusability, and single responsibility are not discussed here in detail. I mainly emphasize the following points:

First: Domain-driven design, not data-driven design or interface-driven design.

In microservices design, the domain model should be established first, and the logical and physical boundaries as well as the domain objects should be determined before starting the decomposition and design of microservices. It is not about defining data models and database structures first or adjusting the core domain logic code based on frontend interface requirements. When designing, external demands should be gradually consumed from outside to inside, minimizing the impact on the core domain logic.

Second: Clear boundaries of microservices, not tightly-coupled monoliths.

The functionality and code of microservices are not static after they are launched. With changes in requirements or design, the domain model will iterate and the microservice code will be recombined. Microservices with clear boundaries can quickly realize the reorganization of code. The domain services and database entities between cohesive aggregates should ideally have no mutual dependencies. You can achieve decoupling between aggregates through application service orchestration or event-driven design, facilitating the architectural evolution of microservices.

Third: Clear and layered responsibilities, not putting everything into a big basket.

In a layered architecture, each layer has clear responsibilities and can only depend on the layer below it, which means that it can only call services from the outer layer, and the inner layers are exposed gradually through encapsulation, composition, or orchestration. The granularity of services should be from fine-grained to coarse-grained. The application layer is responsible for the composition and orchestration of services, and should not have too much core business logic. The domain layer is responsible for the implementation of core domain business logic. Each layer should fulfill its own responsibilities, and the boundaries of responsibilities should not be confused. During service evolution, reusable capabilities should be consolidated in the lower layers as much as possible.

Fourth: Build microservices that you can handle, not over-decomposed microservices.

Over-decomposition of microservices will inevitably increase the maintenance costs of software, such as integration costs, operation and maintenance costs, and costs for monitoring and troubleshooting. Enterprises undergoing microservices transformation also need capabilities such as cloud computing, DevOps, and automated monitoring. However, it is difficult for general enterprises to improve these capabilities in a short period of time. If the project team lacks these capabilities, it will be difficult to handle these microservices.

If the initial microservices design follows the strategic design method of domain-driven design (DDD) and defines the logical boundaries within the microservices, and the architecture is well-layered, we actually don’t need to decompose too many microservices. Even a monolith is acceptable. With the accumulation of technology and improvement of capabilities, when we have these capabilities, due to the clear logical boundaries within the application, we can easily recombine new microservices at any time, without spending too much time and effort on it.

What factors need to be considered when splitting microservices? #

In theory, a domain model within a bounded context can be designed as a microservice. However, since domain modeling is mainly from a business perspective and does not consider non-business factors such as frequency of requirement changes, high performance, security, team and technology heterogeneity, these non-business factors also play a decisive role in the implementation of the domain model system. Therefore, we need to pay special attention to them when splitting microservices. Here are the main factors to consider:

1. Based on the domain model

Split based on the domain model, focusing on the single responsibility and functional completeness of the business domain.

2. Based on the frequency of business requirement changes

Identify the functions in the domain model that have frequent changes in business requirements, and consider the frequency of business changes and relevance. Separate the functions that have high business requirement changes and relatively stable functionality. This is because frequent changes in requirements will inevitably lead to frequent code modifications and version releases. This separation can effectively reduce the impact of frequently changing sensitive business on stable business.

3. Based on application performance

Identify functions in the domain model that have high performance pressure. Functions with high performance requirements may drag down other functions and have different resource requirements. In order to avoid the impact on overall performance and resources, we can separate the functions that have high performance requirements.

4. Based on organizational structure and team size

Unless there is a conscious optimization of the organizational structure, the splitting of microservices should avoid bringing adjustments to the team and organizational structure. This is to avoid adding a large and unnecessary communication cost between teams due to the reassignment of functions. The team size of the separated microservice project should be around 10-12 people.

5. Based on security boundaries

Functions with special security requirements should be separated independently from the domain model to avoid mutual interference.

6. Based on factors such as technology heterogeneity

Although some functions in the domain model are within the same business domain, there may be significant differences in technology implementation, which means that different functions within the domain model have technology heterogeneity. Due to business scenarios or technical constraints, some may use .NET, some may use Java, and some may even have a big data architecture. For functions with technology heterogeneity, consider splitting them based on technology boundaries.

Summary #

I believe that you will have a lot of gains and realizations when implementing microservices. As for DDD and microservices, what I would like to summarize is: deeply understand the design principles and connotations of DDD, grasp the principle of boundaries and layers, combine enterprise culture and technical characteristics, flexibly apply tactical design methods, choose the most suitable technologies and approaches to solve practical problems, and do not do DDD just for the sake of DDD.