In this article we are going to understand the microservices architecture patterns in easy words, So without wasting any time let’s have an understanding.
The motivation for using microservices
Microservices are providing much better decoupling. They therefore help to modularize and isolate software modules (see Advantages). Microservices are, however, modules of a larger system. They must therefore be integrated. This is a challenge for the architecture:
- On the one hand, the architecture of microservices must ensure that microservices can work together to form the overall system.
- On the other hand, the freedom of microservices should not be too limited, since this would compromise their isolation and independence, which are required for most of the benefits of the microservice architecture.
This principle is shown in the drawing above. The overall macro architecture refers to all microservices, while the micro architecture deals with different microservices such that each microservice has its own micro-architecture.
- The division of domain logic into micro-services. Domain-driven architecture and bounded sense are perfect approaches to this division.
- Decisions that are part of the technological micro and macro architecture and how the DevOps paradigm impacts those decisions.
- The dilemma of who splits the choices into micro and macro architectures and who constructs the macro architecture.
Bounded context and strategic design
As far as domain architecture is concerned, the idea of micro and macro architecture has long been a standard practice. The macro architecture splits the domains into gross-grained modules. These modules are further broken down as part of the micro architecture.
For eg, the e-commerce structure can be divided into modules and sub-modules as follows:
- Customer registration
- Order process
- Data validation
- Freight charge calculation
However, the internal architecture of the order process module is shielded from the outside and can be changed without impacting other modules. This flexibility to modify one module without changing the other modules is one of the key benefits of modular software development.
An example of a domain architecture
The drawing above provides an example of how the device is separated into several domain modules. Each module has its own domain model in this division. Let’s talk about each one.
- Data, such as specifications, pictures, or prices, must be preserved for the goods to be searched successfully. Important consumer data can include, for example, feedback that can be determined on the basis of previous orders.
- To process orders in the order process module, the contents of the shopping cart have to be tracked. For products, only basic information is required such as name and price. Similarly, not too much data concerning the customer is necessary. The most important component of the domain model of this module is the shopping cart. It is then turned into an order that has to be handed over and processed by the other bounded contexts.
- For payment, payment-associated information like credit card numbers has to be kept for each customer.
- For shipping, the delivery address is required information about the customer while the size and the weight are necessary information about the product.
- This list reflects that the modules require different domain models. Not only does the data concerning customer and product differ but so does the entire model and the logic.
This list reflects that the modules require different domain models. Not only does the data concerning customer and product differ but so does the entire model and the logic.
Domain-driven design: defination
Domain-driven design (DDD) offers a collection of patterns for the domain model of a system. For microservices, the patterns in the area of strategic design are the most interesting. They describe how a domain can be subdivided.
Bounded context: defination
Domain-driven design speaks of a bounded context. Each domain model is valid only in a bounded context.
Consequently, search, order process, payment, and shipping are such bounded contexts because they each have their own domain model.
Multiple bounded contexts
It would be conceivable to implement a domain model that comprises multiple bounded contexts. However, such a model would not be the easiest solution.
For example, a price change affects search; however, it must not result in a price change for orders that have already been processed in payment. It is easier to store only the current price of a product in the bounded context search and to store the price of the product of each order in payment, which can also comprise rebates and other complex logic.
Therefore, the simplest design consists of multiple specialized domain models that are valid only in a certain context. Each domain model has its own model for business objects such as customers or products.
Domain events between bounded contexts
For the communication between bounded contexts, we can use domain events.
Events can be useful for integrating bounded contexts. Domain events are a part of the domain model as they represent something that happened in the domain. That means they should also be relevant to domain experts.
- Ordering a shopping cart can be modeled as such an event.
- This event is triggered by the bounded context order process and is received by the bounded contexts shipping and payment to initiate shipping and invoicing of the order.
Bounded contexts and microservices
Bounded contexts divide a system by domains. They do not have to be microservices. They can also be implemented as modules in a deployment monolith.
If the bounded contexts are implemented as microservices, this results in modules that are independent at the domain and technical level. Therefore, it is sensible to combine the concepts of microservices and bounded contexts.
The dependencies of the bounded contexts as part of strategic designs, as we’ll learn in the next lesson, limit this independence. However, since the microservices are part of a larger system, dependencies between the modules cannot be completely avoided.
There are a number of reasons why new bounded context, and therefore new microservices, might be created:
- Over time, new functionalities might justify new bounded contexts.
- It might become apparent that one bounded context should really be split into two. That might be the case because new logic is added to the bounded context, or the team understands the bounded context better.
- New microservices might be created by dividing a current one due to a technical reason (recall division by technicality!).
- One reason may be to make scalability easier. A microservice may be split in two since the resulting microservices will be smaller and therefore easier to scale. Such reasons might also lead to a larger number of microservices.
Strategic design pattern for microservices
The division of the system into different bounded contexts is part of strategic design, which belongs to the practices of domain-driven design (DDD). The strategic design describes the integration of bounded contexts
The drawing above shows the fundamental terms of strategic design.
- The bounded context is the context in which a specific domain model is valid.
- The bounded contexts depend on each other. Usually, each bounded context is implemented by one team.
- The upstream team can influence the success of the downstream team. However, the downstream team cannot influence the success of the upstream team.
- For example, the success of the team responsible for payment depends on the order process team.
- If data such as prices or credit card numbers are not part of the order, it is impossible to do the payment.
- However, the order process does not depend on the payment to be successful.
- Therefore, order processing is upstream. It can make payment fail.
- Payment is downstream since it cannot make the order process fail.
DDD describes in several patterns how exactly communication takes place. These patterns not only describe the microservices architecture pattern, but also the cooperation within the organization.
The customer/supplier design pattern for microservices
With this customer/supplier pattern, the supplier is upstream and the customer is downstream. However, the customer can factor their priorities into the planning of the upstream project.
Example of customer/supplier design pattern for microservices
In the drawing below, for example, payment uses the model of the order process. However, payment defines requirements for the order process. Payment can only be done successfully if the order process provides the required data.
So, payment can become a customer of the order process. That way the customer’s requirements can be included in the planning of the order process.
The conformist design pattern for microservices
Conformist means that a bounded context simply uses a domain model from another bounded context.
Example of conformist design pattern for microservices
In the drawing below, the bounded contexts, statistics, and order process, both use the same domain model. The statistics are part of a data warehouse. They use the domain model of the order process bounded context and extract some information relevant to store in the data warehouse.
However, with the conformist pattern, the data warehouse team does not have a say in case of changes to the bounded context.
The data warehouse team could not demand additional information from the other bounded context. However, it is still possible that they would receive additional information out of altruism. Essentially, the data warehouse team is not deemed important enough to get a more powerful role.
The anti-corruption layer design pattern for microservices
In the case of an anti-corruption layer (ACL) pattern, the bounded context does not directly use the domain model of the other bounded context, but it contains a layer for decoupling its own domain model from the model of the bounded context.
This is useful in conjunction with the conformist pattern to generate a separate model decoupled from the other model.
Example anti-corruption layer design pattern for microservices
The drawing below shows that the bounded context shipping uses an ACL at the interface to the bounded context legacy so that both bounded contexts have their own independent domain models.
This ensures that the model in the legacy system does not affect the bounded context shipping. Shipping can implement a clean model in its bounded context.
The separate ways design pattern for microservices
With the separate ways design pattern for microservices the bounded contexts are not related at the With the separate ways design pattern for microservices, the bounded contexts are not related at the software level although a relation would be conceivable.
Example of separate ways design pattern for microservices
Let’s assume that in the e-commerce scenario, a new bounded context, purchasing, for the purchasing department is added. This bounded context could collect the data for listing products, but it is implemented differently.
With the pattern of the separate way, the purchasing would be separate from the remaining system. When the goods are delivered, a user would use another bounded context like listing to enter the necessary data and list the products. The purchasing causes the shipping, which in turn triggers the delivery, and thereby triggers the user to list the product with a different bounded context.
purchasing → shipping → delivery → list product
The shipping of the products is one event in the real world, however, in the software, the systems are separate. Consequently, the systems are independent and can be evolved completely independently.
The shared kernel design pattern for microservices
The Shared Kernel Pattern describes a common core that is shared by multiple bounded contexts.
The data of a customer is an example of such a scenario.
However, the shared kernel comprises shared business logic and shared database schema and therefore should not be used in a microservices environment.
It is an anti-pattern for microservices systems. But because DDD can also be applied to deployment monoliths, there are still scenarios in which a shared kernel makes sense.