Microservices

Microservices Design Patterns

Microservices is a distinctive method of developing software systems that try to focus on building single-function modules with well-defined interfaces and operations.

Microservices have many benefits for Agile and DevOps teams.

The goal of microservices is to increase the velocity of application releases, by decomposing the application into small autonomous services that can be deployed independently. A microservices architecture also brings some challenges.

Before we dive into the design patterns, we need to understand what principles microservice architecture has been built:

  • Scalability
  • Availability
  • Resiliency
  • Independent, autonomous
  • Decentralized governance
  • Failure isolation
  • Auto-Provisioning
  • Continuous delivery through DevOps

The design patterns shown here can help mitigate these challenges.

Below are the Micro-services Design Patterns

Decomposition Patterns:

1)Decompose by Business Capability:

Microservices is all about making services loosely coupled, applying the single responsibility principle. It decomposes by business capability. Define services corresponding to business capabilities. A business capability is a concept from business architecture modeling. It is something that a business does to generate value.

A business capability often corresponds to a business object, e.g.

  • Order Management is responsible for orders.
  • Customer Management is responsible for customers.

2)Decompose by Subdomain:

Decomposing an application using business capabilities might be a good start, but you will come across so-called “God Classes” which will not be easy to decompose. These classes will be common among multiple services. Define services corresponding to Domain-Driven Design (DDD) subdomains. DDD refers to the application’s problem space — the business — as the domain.

A domain consists of multiple subdomains. Each subdomain corresponds to a different part of the business.

Subdomains can be classified as follows:

  • Core — key differentiator for the business and the most valuable part of the application.
  • Supporting — related to what the business does but not a differentiator. These can be implemented in-house or outsourced.
  • Generic — not specific to the business and is ideally implemented using off-the-shelf software.

The subdomains of an Order management include:

  • Product catalog service
  • Inventory management services
  • Order management services
  • Delivery management services

3)Decompose by Transactions:

In a distributed system, an application typically needs to call multiple microservices to complete one business transaction. To avoid latency issues or two-phase commit problems, you can group your microservices based on transactions.

The distributed transaction consists of two steps:

  • Prepare phase — during this phase, all participants of the transaction prepare for commit and notify the coordinator that they are ready to complete the transaction
  • Commit or Rollback phase — during this phase, either a commit or a rollback command is issued by the transaction coordinator to all participants

This pattern is appropriate if you consider response times important and your different modules do not create a monolith after you package them.

4)Strangler Patterns:

The above design patterns that you go through were decomposing applications for Greenfield, but 80% of the work you do is with big brownfield applications and monolithic applications (legacy codebase). The Strangler pattern comes to the rescue or solution. This creates two separate applications that live side by side in the same URI space. Over time, the newly refactored application “strangles” or replaces the original application until finally, you can shut off the monolithic application.

The Strangler Application steps are transformed, coexist, and eliminate:

Transform — Create a parallel new site with modern approaches.
Coexist — Leave the existing site where it is for a time. Redirect from the existing site to the new one so the functionality is implemented incrementally.
Eliminate — Remove the old functionality from the existing site.

5)Bulkhead Pattern
6)Sidecar Pattern:

Deploy components of an application into a separate processor container to provide isolation and encapsulation.

This pattern can also enable applications to be composed of heterogeneous components and technologies. This pattern is named Sidecar because it resembles a sidecar attached to a motorcycle.

In the pattern, the sidecar is attached to a parent application and provides supporting features for the application. The sidecar also shares the same lifecycle as the parent application and is created and retired alongside the parent.

Integration Patterns:

1)API Gateway pattern
2)Aggregator Pattern:

When breaking the business functionality into several smaller logical pieces of code, it becomes necessary to think about how to collaborate the data returned by each service. This responsibility cannot be left to the consumer.
The Aggregator pattern helps to address this. It talks about how we can aggregate the data from different services and then send the final response to the consumer.

This can be done in two ways:

  • A composite microservice will make calls to all the required microservices, consolidate the data, and transform the data before sending it back.
  • An API Gateway can also partition the request to multiple microservices and aggregate the data before sending it to the consumer.

3)Proxy Pattern
4)Gateway Routing Pattern
5)Chained Microservice Pattern:

A chained microservice design pattern produces a single consolidated response to the request. In this case, the request from the client is received by Service A, which is then communicating with Service B, which in turn may be communicating with Service C. All the services are likely using a synchronous HTTP request/response messaging.
6)Branch Pattern:

The branch microservice design pattern extends the Aggregator design pattern and allows simultaneous response processing from two, likely mutually exclusive, chains of microservices. This pattern can also be used to call different chains, or a single chain, based on the business needs.
7) Client-Side UI Composition Pattern:

When services are developed by decomposing business capabilities/subdomains, the services responsible for user experience have to pull data from several microservices. In the monolithic world, there used to be only one call from the UI to a backend service to retrieve all data and refresh/submit the UI page. However, now it won’t be the same.

With microservices, the UI has to be designed as a skeleton with multiple sections/regions of the screen/page. Each section will make a call to an individual backend microservice to pull the data. Frameworks like AngularJS and ReactJS help to do that easily. These screens are known as Single Page Applications (SPA). Each team develops a client-side UI component such as an AngularJS directive, that implements the region of the page/screen for their service.

A UI team is responsible for implementing the page skeletons that build pages/screens by composing multiple, service-specific UI components.

Database Pattern:

In defining the database architecture for microservices we need to consider the below points.

  1. Services must be loosely coupled. They can be developed, deployed, and scaled independently.
  2. Business transactions may enforce invariants that span multiple services.
  3. Some business transactions need to query data that is owned by multiple services.
  4. Databases must sometimes be replicated and shared to scale.
  5. Different services have different data storage requirements.


1)Database per Service:

To solve the above concerns, one database per microservice must be designed; it must be private to that service only. It should be accessed by the microservice API only. It cannot be accessed by other services directly. For example, for relational databases, we can use private-tables-per-service, schema-per-service, or database-server-per-service.

Database per service
Database per service

2)Shared Database Per Service:

We have talked about one database per service being ideal for microservices. It is an anti-pattern for microservices. But if the application is a monolith and trying to break into microservices, denormalization is not that easy. In the later phase we can move to DB per services pattern,

Till that we make follow this. A shared database per service is not ideal, but that is the working solution for the above scenario. Most people consider this an anti-pattern for microservices, but for brownfield applications, this is a good start to breaking the application into smaller logical pieces. This should not be applied to greenfield applications.
3)Command Query Responsibility Segregation (CQRS):

CQRS stands for Command and Query Responsibility Segregation, a pattern that separates read and update operations for a data store. Implementing CQRS in your application can maximize its performance, scalability, and security. The flexibility created by migrating to CQRS allows a system to better evolve over time and prevents update commands from causing merge conflicts at the domain level.
4)Event Sourcing
5)Saga Pattern

Observability Patterns:

1)Log Aggregation:

Consider a use case where an application consists of multiple services. Requests often span multiple service instances. Each service instance generates a log file in a standardized format. We need a centralized logging service that aggregates logs from each service instance. Users can search and analyze the logs. They can configure alerts that are triggered when certain messages appear in the logs.

For example, PCF does have a Log aggregator, which collects logs from each component (router, controller, Diego, etc…) of the PCF platform along with applications. AWS Cloud Watch also does the same.
2)Performance Metrics:

When the service portfolio increases due to a microservice architecture, it becomes critical to keep a watch on the transactions so that patterns can be monitored and alerts sent when an issue happens.

A metrics service is required to gather statistics about individual operations. It should aggregate the metrics of an application service, which provides reporting and alerting. There are two models for aggregating metrics:

Push — the service pushes metrics to the metrics service e.g. NewRelic, AppDynamics, Datadog
Pull — the metrics services pull metrics from the service e.g. Prometheus

3)Distributed Tracing:

Distributed tracing is a method of tracking application requests as they flow from frontend devices to backend services and databases. Developers can use distributed tracing to troubleshoot requests that exhibit high latency or errors.
4)Health Check:

When microservice architecture has been implemented, there is a chance that a service might be up but not able to handle transactions. Each service needs to have an endpoint that can be used to check the health of the application, such as /health. This API should o check the status of the host, the connection to other services/infrastructure, and any specific logic.

Cross-Cutting Patterns:

1)External Configuration:

A service typically calls other services and databases as well. For each environment like dev, QA, UAT, prod, the endpoint URL or some configuration properties might be different. A change in any of those properties might require a re-build and re-deploy of the service.

To avoid code modification configuration can be used. Externalize all the configuration, including endpoint URLs and credentials. The application should load them either at startup or on the fly. These can be accessed by the application on startup or can be refreshed without a server restart.

2)Service Discovery Pattern:

Service discovery is how applications and (micro)services locate each other on a network. Implementations include both a central server(s) that maintain a global view of addresses and clients that connect to the central server to update and retrieve addresses.

A service registry needs to be created which will keep the metadata of each producer service and specification for each. A service instance should register to the registry when starting and should de-register when shutting down.

There are two types of service discovery:

  • client-side : eg: Netflix Eureka
  • Server-side : eg: AWS ALB.

3)Circuit Breaker Pattern:

A service generally calls other services to retrieve data, and there is a chance that the downstream service may be down. There are two problems with this: first, the request will keep going to the down service, exhausting network resources, and slowing performance. Second, the user experience will be bad and unpredictable.

The consumer should invoke a remote service via a proxy that behaves similarly to an electrical circuit breaker. When the number of consecutive failures crosses a threshold, the circuit breaker trips, and for the duration of a timeout period, all attempts to invoke the remote service will fail immediately. After the timeout expires the circuit breaker allows a limited number of test requests to pass through.

If those requests succeed, the circuit breaker resumes normal operation. Otherwise, if there is a failure, the timeout period begins again. This pattern is suited to, prevent an application from trying to invoke a remote service or access a shared resource if this operation is highly likely to fail.
4)Blue-Green Deployment Pattern:

A blue/green deployment is a deployment strategy in which you create two separate, but identical environments. One environment (blue) is running the current application version and one environment (green) is running the new application version. Using a blue/green deployment strategy increases application availability and reduces deployment risk by simplifying the rollback process if a deployment fails.

Blue Green Deployment
Blue Green Deployment

Once testing has been completed on the green environment, live application traffic is directed to the green environment and the blue environment is deprecated.

Conclusion

Microservice Architecture can help development scaling with many long-term benefits. But Microservice Architecture is no Silver Bullet that can be used in every use case. If it is used in the wrong type of application, Microservice Architecture can give more pains as gains. The development team that wants to adopt Microservice Architecture should follow a set of best practices and use a set of reusable, battle-hardened design patterns.

This list is not all-inclusive, and depending on your use case, you may need other design patterns. But this list will give you an excellent introduction to Microservice Architecture Design Patterns.

Loading

3 thoughts on “Microservices Design Patterns

Comments are closed.

Translate »