Microservices Design Patterns
This is the 3rd post in a series on microservices architecture
High availability, scalability, resilience to failure, and performance are characteristics of microservices. You can use the microservice architecture pattern in order to architect a microservice application, thereby reducing the risk of failures in microservices.
The patterns are divided into three layers:
The application patterns address issues faced by developers such as the decomposition of data, data maintenance, testing, user interface, and some of the observability patterns.
Let’s go over the basics of these application patterns.
Choosing how to decompose a monolithic system into services
- Decompose by business capability — Services are organized around business capabilities.
- Decompose by subdomain — Services are organized around subdomains of domain-driven design.
- Data consistency — A separate database is used by each service in order to ensure loose coupling. For data consistency across services, the Saga pattern must be used.
- Querying —The other problem with using a database per service is that some queries need to join data from multiple services. It is impossible to perform distributed queries against a service’s database as its data can only be accessed via its API. Data scattered across multiple services must be retrieved using one of the querying patterns.
- API composition — API calls are made to one or more services and results are aggregated.
- Command Query Responsibility Segregation (CQRS) — The data is maintained in one or more replicas that can be easily queried.
Individual microservices are easier to test because they are much smaller than monolithic applications. While testing that the different services work together, it’s important to avoid the use of complex, slow, and unstable end-to-end tests that examine multiple services simultaneously.
- Consumer-driven contract test — Ensure that a service meets the expectations of its clients.
- Consumer side contract test — Make sure the client of the service can communicate with it.
- Service component test — Isolate the service and test it.
It is the responsibility of the different teams to display data that correspond to different services and how it is displayed.
- Server-side page fragment composition — Each team develops a web application that generates the HTML fragment for the region of the page that their service implements. UI teams develop the page templates by aggregating service-specific HTML fragments on the server-side.
- Client-side UI composition — Each team creates a client-side UI component implementing the region of the screen for their service, such as an AngularJS directive. By composing multiple, service-specific UI components, UI teams implement page skeletons to build screens.
In order to operate an application effectively, it is important to understand its runtime behavior and troubleshoot problems such as failed requests.
- Audit Logging—Audit logging records the actions of each user. A log of audit activity is typically used to assist in customer support, ensure compliance, and detect suspicious activity.
- Application metrics—Monitoring and alerting are key components of the production environment. There is a range of metrics, such as the utilization of CPU, memory, and disk, to the latency of service requests and the number of requests executed. Metrics are collected by a metric service, which provides alerting and visualization.
Application Infrastructure Patterns
They are for infrastructure issues that also affect development, such as communications, observability, reliability, and security patterns.
Cross-Cutting Concerns Patterns
We must first understand the Concern in order to understand the Cross-cutting Concerns. Concerns are parts of the system based on their functionality. There are two kinds of concerns:
Core Concerns — It represents a single and specific functionality for primary requirements, such as business logic.
Cross-cutting Concerns — Concerns that pertain to secondary requirements. Cross-cutting concerns are concerns that apply to the whole application, such as security and logging.
- Externalized Configuration — During runtime, it provides configuration property values, like database credentials and network location, to a service.
- Microservice Chassis — A microservices chassis is a framework or set of frameworks that handle a range of concerns, such as externalized configuration, health checks, application metrics, service discovery, circuit breakers, and distributed tracing. You can develop your service’s business logic more efficiently with a microservice chassis.
- Service Template — A developer can quickly start developing a new service by copying a source code template. As the name implies, a template is a simple runnable service that implements the build logic and cross-cutting concerns as well as sample application logic.
Microservice-based applications are distributed systems. Microservice architecture relies heavily on interprocess communication (IPC).
- Remote Procedure Invocation (RPI) — Requests to service are made using a request/reply protocol.
- Domain-Specific Protocol — For inter-service communication, such as email using SMTP/IMAP, or media streaming using RTMP/HLS/HDS use the domain-specific protocol.
- Messaging — Use asynchronous messaging for inter-service communication, such as AMQP
Observability patterns provide insight into how applications behave. It is much more difficult to diagnose problems with a microservice architecture. Requests can bounce between multiple services before a response is finally returned to a client.
- Log aggregation —Write logs of service activity into a centralized logging server that can perform searching and alerting.
- Exception tracking — Exceptions should be reported to an exception tracking service, which deduplicates exceptions, alerts developers, and tracks their resolution.
- Health check API —Provide an endpoint that returns the health of the service.
- Distributed tracing — Provide each external request with an ID and track requests as they flow between services.
When services are unavailable, how can you ensure reliable communication between them?
- Circuit breaker — A circuit breaker can be used to protect cross-service calls. When a certain number of downstream resource requests fail to meet a certain threshold, a circuit breaker opens. The system will quickly fail if the circuit breaker is open. After some time, the client will send some requests to check whether the downstream service has been restored. The request will be sent again once the health is restored if there is a normal response.
Users are typically authenticated by the API gateway in a microservice architecture. The user’s identity and role must then be passed to the services it invokes. A common solution is to use the access token pattern. API gateways pass an access token, such as JWT (JSON Web Token), to services, which can validate the token and get information about users.
They solve problems pertaining to infrastructures outside of development, such as deployment, discovery, and communication patterns with external APIs.
There are several patterns for deploying microservices. Traditionally, services are packaged in a language-specific manner. There are two modern deployment approaches.
- VM or containers —VMs or containers can be used to deploy services.
- Serverless deployment — The serverless platform executes the service’s code once you upload it. An automated, self-service platform is the best way to deploy and manage services.
Normally, services need to communicate with one another. A monolithic application invokes its services using language-level methods or procedure calls. Traditionally, distributed systems run at fixed, well-known locations (hosts and ports) so that services can be accessed through HTTP/REST or some other mechanism. Most modern microservice-based applications, however, run in virtualized or containerized environments in which the number of instances of a service and their locations change dynamically.
- Self Registration — The service registers itself with the service registry
- Client-side Discovery—Service clients retrieve service instances from the service registry and then load balance among them.
- 3rd Party Registration — A third party automatically registers service instances with the service registry.
- Server-side Discovery — Service discovery is done by a router, which receives a request from a client.
External API Patterns
API granularity provided by microservices is often different from what a client requires. The APIs provided by microservices are typically fine-grained, so clients must interact with multiple services. Each client requires a different amount of data, and network performance affects each client differently.
- API Gateway — API Gateway implements a service that’s the entry point into the microservices-based application from external API clients. It performs request routing, API composition, and other functions such as authentication, rate limiting, caching, etc.
- Backend for frontends — Create a separate API gateway for each type of client. Each mobile, browser, and public API team will own its own gateway, while an API gateway team owns the common layer.
In future posts, we will go into detail about each of these patterns.