Microservices Part 2: Design Patterns
Microservice is the architecture style most developers adopt when developing modern applications. Although microservices provide many advantages in developing modern applications, there are some drawbacks we need to address too. Therefore we need to have a good understanding of common patterns used to solve these drawbacks. Before we dive into the design patterns, we need to revise what are the main advantages of microservices:
- Independent, autonomous
- Decentralized governance
- Failure isolation
- Continuous delivery through DevOps
Therefore applying microservices brings several challenges and issues. Let’s discuss those problems and their solutions.
The microservice applications are divide into serval small services according to domains or business capability. Therefore we need some method to aggregate the data from different services and then send the final response to the consumer. To achieve this Aggregator Pattern provide us three ways;
- Parallel Pattern (Scatter Gather Pattern)
- Chain Pattern
- Branch Pattern
Parallel Pattern allows us to send requests to two or more microservices parallelly without waiting for each other and get data. Then this retrieved data is aggregated and send to the customer. For this, to be in effect each calling microservice should not be dependent on each other.
Chain Pattern is useful in situations where one microservice depended on the data coming from another microservice. For example, imagine we have two microservices as student service and attendance service. First, we need to get the student data from the student service, and then to get attendance details, we can send the student id for the attendance service. Finally, we can aggregate the retrieved data and send it to the microservice consumer.
Branch Pattern is useful when we are deciding which microservice to call, depending on some value in data obtained from another microservice. For example, imagine we get staff details from staff microservice in a school and depending on the role attribute of the obtained data; if it is academic, we can call the “lecture allocation” microservice, and if it is admin, we can call the “work allocation” microservice.
Why Aggregator Pattern is Useful ??
Imagine we have developed several microservices and use aggregator patterns to consume 2 of the microservices. After that, deploy them in the production environment. After several days the team will receive a new business requirement change to aggregate some more microservices. In that situation, we can create a new aggregator service for the new set of mentioned microservices. Next, we can maintain the new and old versions of the aggregator service parallelly. It will give consumers some time to migrate to the new aggregator service depending on the consumer’s scrum timeline.
Circuit Breaker Pattern
The Circuit Breaker Design pattern is a sustainable pattern help to keep good health on our services. As the name suggests, the Circuit Breaker pattern is used to stop the process of request and response if a service is not working. When a request comes to a web server, it will allocate one thread to call the required microservices. Imagine there is a delay in one of the called microservice due to some reason. Therefore, the thread that invokes the services will be in the waiting and blocked state. Since the client will not have any knowledge about a particular service being down, the request will be continuously sent to that service. As a result, all the request threads created will be queued and will be in waiting or blocked state. Even though after sometimes delayed microservice recovered, the network resources will be exhausted with low performance due to the processing of a large number of queued requests. To avoid this, we can define some maximum threshold time limit (like 200 ms) that every microservice should respond to. Then we can configure a web server to check the threshold limits of requests to the microservices. If the requests have exceeded the maximum threshold, we can immediately fail requests for that particular microservice without waiting.
But during this failed period in the background, we will send a ping request to the failed microservice from time to time to monitor the response time. If the response time comes back to the normal threshold, we will turn on the circuit breaker again, enabling the flow of requests to that microservices again. We do not have to implement this circuit breaker logic by ourselves. Netflix Hystrix is a good implementation of the circuit breaker pattern, which also helps you to define a fallback mechanism that can be used when the circuit breaker trips.
Proxy Design Pattern
Proxy Pattern is useful for the abstraction of different versions of our microservices to the consumer. Imagine we have a microservice that takes employee code and retrieves leave information. But with future restructuring and with the new version of our microservice, we decided to pass the employee id instead of the employee code. In this situation, we can have a web server with the proxy pattern and maintain both services parallelly. Then the consumer can call our proxy service instead of contacting the microservice directly. If the consumer provides an employee id, it will filter that request and send it to the new version of our microservice. If the consumer still sending the employee code, it will filter and route that request to the old version of our microservice. As a result, it will give consumers some time to migrate to our updated microservice version depending on the consumer’s scrum timeline.
With container technology, IP addresses are dynamically allocated to the service instances. Every time the address changes, a consumer service can break and need manual changes. Also, the tight coupling may happen by hard coding the URLs in the consumer where microservices reside. Therefore as a solution, we can use a Service Registry for the maintenance of the metadata of each microservice. A service instance is registered to the service registry when starting and de-register when shutting down. The consumer should query the registry and find out the microservice residing URL details. The registry also needs to do a health check of the microservice to ensure that only working instances are made available to the consumer. The Service Discovery is divided into two parts as client-side and server-side. An example of client-side discovery is Netflix Eureka, and an example of server-side discovery is the AWS Application Load Balancer.
Blue-Green Deployment Pattern
Blue-Green Deployment Pattern is a technique that enables continuous delivery to production with reduced downtime and risk. In Blue-Green Deployment, we run two identical production environments called Blue and Green. Let’s assume Green is the existing live old instance, and Blue is the new version of microservice. At any given time, only one instance is kept live by serving all the production traffic. The benefits of this pattern are;
- Help to reduce the downtime of microservices.
- Help to roll back microservices with production issues quickly.
- help to build confidence, since testing of new version can be done in production in isolation before rollout.
There are many other Microservice Patterns like Saga Pattern, Command Query Responsibility Segregation (CQRS) Pattern, API Gateway Pattern, Strangler Pattern, Asynchronous Messaging Design Pattern, Sidecar Pattern, Event Sourcing Pattern, etc. We will discuss these topics in detail in a future article.
For further more clarification check these resources;