Microservices Design Patterns and Principles
What is microservices?
Microservices, also known as microservice architecture, is an architectural style in which an application is organized as a collection of small, self-contained services based on a business domain. Each service in a Microservice Architecture is self-contained and implements only one business capability.
Design Principles for Microservice Architecture:
The following are the design principles for Microservices:
- Services that are self-contained and autonomous
- Load Balancing in Real-Time
- Resilient Services
- DevOps integration ensures continuous delivery.
- Continuous Monitoring and Seamless API Integration
- Isolation from Setbacks
Microservices Design Patterns:
Aggregator Pattern: In the computing world, an aggregator is a website or program that collects and displays related pieces of data. As a result, even in Microservices patterns, Aggregator is a simple web page that invokes various services to obtain the required information or achieve the desired functionality.
Because the source of output is divided when a monolithic architecture is broken down into microservices, this pattern is useful when combining data from multiple services. The two services have their own database, an aggregator with a unique transaction ID would collect data from each individual microservice, apply business logic, and then publish it as a rest endpoint.
Asynchronous Messaging: All of the services in this type of microservices design pattern can communicate with each other, but they do not have to communicate sequentially. So, take a look at three services: Service A, Service B, and Service C. The client’s request can be sent directly to Service C and Service B at the same time. These requests will be routed to a queue. Aside from that, the request can be sent to Service A, and the response does not have to be sent to the same service from which the request originated.
API Gateway: API gateways are capable of converting protocol requests from one type to another. Similarly, it can offload the microservice’s authentication/authorization responsibilities. API Gateway also serves as an entry point for all microservices and generates fine-grained APIs for various types of clients.
Microservices use Service Discovery as a guide to determine the best path for communication between them. Microservices then communicate with one another through a stateless server, such as HTTP Request/Message Bus.
Event Sourcing: The event sourcing design pattern generates events when the application’s state changes. Additionally, these events are saved as a sequence of events to assist developers in tracking which changes were made when. As a result, you can always adjust the application state to accommodate previous changes. The changes in the application state can be seen on the presentation layer once the events are published.
Command Query Responsibility Segregator: A database per service model or a shared database per service is used in every microservices design. However, because data access is limited to a single database in the database per service model, we are unable to implement a query. In this case, the Command Query Responsibility segregator pattern can be used. The application will be divided into two parts based on this pattern: Command and Query. The command part will handle all requests (such as create, delete, and delete), while the query part will handle materialized views.
Database or Shared Data: The microservice API can access a database or shared data per service. As a result, each microservice will have its own database ID, which will prevent other services in the system from accessing that database. Apart from that, you can choose shared databases per service to align more than one database for each microservice to solve the issue of de-normalization. This will aid data collection for monolithic applications that have been broken down into microservices. However, the databases should be limited to 2-3 microservices; otherwise, scaling these services will be difficult.
Chained or Chain of Responsibility: Chained or Chain of Responsibility Design Patterns combine multiple chained outputs into a single output. So, if you have three services linked together in a chain, Service A receives the client’s request first. After that, this service communicates with Service B and gathers data. Finally, in order to generate the consolidated output, the second service communicates with the third service. For messaging, all of these services use synchronous HTTP requests and responses. Furthermore, the client receives no output until the request has passed through all of the services and the appropriate responses have been generated. As a result, it’s always a good idea to avoid making a long chain because the client will wait until it’s finished.
Decomposition: It is used to decompose an application based on business capability or based on sub-domains. Consider an e-commerce application: if you decompose by business capability, you can have separate services for orders, payment, customers, and products.
You can employ Domain-Driven Design, which divides the domain model into sub-domains. Then, for each of these sub-domains, a unique model and scope will be developed (bounded context). When a developer creates microservices, he or she will design those services with the scope or bounded context in mind.
These patterns may appear feasible to you, but they are not for large monolithic applications. This is due to the difficulty of identifying sub-domains and business capabilities in large applications. As a result, the only way to decompose large monolithic applications is to use the different Patterns.
Branch: It allows you to process requests and responses from two or more independent microservices at the same time. In contrast to the chained design pattern, the request is passed to two or more mutually exclusive microservices chains rather than in a sequence. This design pattern builds on the Aggregator design pattern by allowing responses to be generated from multiple chains or a single chain.
Circuit Breaker: If a service is not working, the Circuit Breaker design pattern is used to stop the request and response process. For example, suppose a client sends a request to retrieve data from multiple services. However, one of the services is unavailable due to technical difficulties. Now, there are primarily two issues you will encounter: first, because the client will be unaware that a particular service is unavailable, requests will continue to be sent to that service. The second issue is that network resources will be depleted, resulting in poor performance and a negative user experience.
The client can use this pattern to call a remote service through a proxy. Essentially, this proxy will act as a circuit barrier. When the number of failures reaches a certain threshold, the circuit breaker trips for a set period of time. During this timeout period, all attempts to invoke the remote service will fail. When that time period is up, the circuit breaker will allow a limited number of tests to pass, and if those requests are successful, the circuit breaker will return to normal operation. If there is a failure, the time-out period will start over.