Microservices Cross-Cutting Concerns Design Patterns
This is the 10th post in a series on microservices architecture
As a service, you must implement various cross-cutting concerns such as metrics, reporting exceptions to an exception tracker, logging, distributed tracing, health checks, externalized configuration, and security. Additionally, a service may need to handle service discovery and implement circuit breakers. It makes no sense to write code to set them up from scratch every time a new service is added.
You can develop your services much more quickly by building them on a microservice chassis. Microservice chassis will be explained in further detail in the next section.
Service must be configured for a different environment based on various properties, such as the network location of databases, external endpoints, message queues, and credentials.
According to the environment in which the service is running, these properties have different values. Hardwiring a particular environment’s configuration property values into a deployable service would mean that it would have to be rebuilt for each new environment. Instead, a service should be built once and deployed to multiple environments through the deployment pipeline.
It is also not logical to hardwire different configuration settings into the source code and use them. Similarly, we should not store sensitive data like credentials in configuration properties, but rather in a secret storage mechanism such as HashiCorp Vault or AWS Parameter store. Through the externalized configuration pattern, we can provide the appropriate configuration properties to the service at runtime.
Externalized Configuration
In an externalized configuration mechanism, configuration property values are passed to a service instance at runtime. There are two primary approaches:
- Push model — Configuration properties are passed to the service instance by means of operating system environment variables or a configuration file.
- Pull model — Configuration properties are read from a configuration server by the service instance.
Push-based Externalized Configuration
Push models rely on the collaboration of the deployment environment and the service. When the deployment environment creates a service instance, it supplies the configuration properties. It can pass configuration properties as environment variables, or the deployment environment may supply the properties through configuration files. When the service instance starts, the configuration properties are read.
It is important that the deployment architecture and the service have agreed on how configuration properties are supplied.
Drawbacks of Push-based Externalized Configuration
- Running services might be difficult to reconfigure, and deployment infrastructure might not allow you to change the externalized configuration of a running service without restarting it.
- There’s a chance that configuration property values will be scattered throughout the definition of numerous services.
You may want to consider using a pull-based model due to these drawbacks.
Pull-based Externalized Configuration
As part of the pull model, a service instance reads its configuration properties from a configuration server. When a service instance starts, it queries the configuration service for its configuration. The configuration properties for accessing the configuration server, such as the network location, are given to the service instance via a push-based configuration mechanism, such as environment variables.
Configuration servers can be implemented in a variety of ways, including:
- Version control systems such as Git
- SQL and NoSQL databases
- Specialized configuration servers such as Spring Cloud config server, Hashicorp Vault, which stores sensitive data, like credentials, and AWS parameter store.
Using a configuration server has several benefits:
- Centralized configuration — Config properties are all in one place, so they are easier to manage.
- Transparent decryption of sensitive data — It is a security best practice to encrypt sensitive data, such as database credentials. Configuration server implementations decrypt properties before returning them to the service, otherwise, clients need to know the keys to decrypt them.
- Dynamic reconfiguration — Using polling, a service could be able to detect updated property values and reconfigure itself.
Drawbacks of Pull-based Externalized Configuration
- The configuration server is yet another piece of infrastructure that needs to be set up and maintained unless it’s provided by the infrastructure.
There are many open-source frameworks, such as Spring Cloud Config, that make it easier to run a configuration server.
Microservice Chassis Pattern
The microservices chassis is a set of frameworks that address numerous cross-cutting concerns such as
- Externalized configuration
- Health checks
- Application metrics
- Service Discovery
- Circuit breakers
- Distributed tracing
- Exception tracking
It reduces the amount of code you have to write significantly. You configure the microservice chassis to fit your needs. You can focus on developing your service’s business logic with a microservice chassis.
Spring Boot and Spring Cloud are two examples of microservice chassis frameworks. With Spring Boot you can externalize configuration, and with Spring Cloud you can set up circuit breakers.
Drawbacks of microservice chassis pattern
- You need a microservice chassis framework for every language/platform combination you use to develop services.
Service Mesh Pattern
Using a microservice chassis is a good way to implement various cross-cutting concerns, but it has the disadvantage that you need one for every programming language you use.
One way to solve this problem is to implement some of this functionality outside of the service in what is known as a service mesh. A service mesh is a networking infrastructure that facilitates communication between a service and other services and external applications.
A service mesh implements various concerns, such as circuit breakers, distributed tracing, service discovery, load balancing, and rule-based traffic routing.
Using a service mesh makes the microservice chassis much simpler. Concerns that are tightly coupled with the application code, such as externalized configuration and health checks, need only be implemented in microservice chassis.
Some of the implementations of service mesh include Istio, Linkerd, and Consul.
The service mesh frees the developer from having to deal with a variety of cross-cutting concerns.