When building application services, developers must incorporate mechanisms that can handle the specific management tasks required by each individual service. These requirements are often referred to as cross-cutting concerns, which is a term for the management concerns that apply to multiple services and layers of the respective application system. These typically include things like gathering metrics, enforcing security, creating application logs and managing service discovery.
In a microservices architecture, where large numbers of distributed services swarm application systems, handling these concerns is an intimidating challenge. While there are multiple ways to address this conundrum, the microservices chassis is an effective way to alleviate the burden placed on developers to manage these cross-cutting concerns.
In this article, we look into the details of the microservices chassis pattern and how to implement it. We also examine the pros and cons of this approach and even some examples to know when it is the right approach.
What is a microservices chassis, and why is it needed?
While independence is a huge part of the concept behind microservices, developers are often still forced to address concerns that span every service in an application or system. You can't afford to manually handle cross-cutting concerns for every individual microservice since that would mean constant code rewrites and enormous investments of time.
You might also have situations where individual services share some concerns but not others. For example, you may have one service that is only concerned with logging, while another service is concerned with logging, security and configuration.
If you design your application so that you have cross-cutting concerns for each service, regardless of whether they need support for that process or not, you will end up with problematic code redundancies.
In enterprise application development, aspect-oriented programming (AOP) has traditionally been a go-to method for handling cross-cutting concerns. This is a development approach that focuses on keeping the logic related to certain application features restricted to their own modules of code, rather than coding that feature logic into every one of the application's components. The microservices chassis pattern resembles this approach but was designed to handle the unique types of cross-cutting concerns associated with distributed services.
This chassis pattern aims to consolidate underlying functional logic into a single module and prepare it for reuse across several services. Much like how AOP divides external-facing application features into individual units of code, the chassis pattern creates individual modules for the back-end operational functions that proliferate microservices architectures. Examples of these include things like external communication configurations, dynamic service discovery routines, automated exception handling, error log management, distributed tracing and adding retry mechanisms that kick in during intermittent failures.
Here are the major advantages of a microservices chassis:
- cuts down on the amount of code developers must write to handle cross-cutting concerns;
- helps decouple business logic from the functional concerns of an application;
- improves the ability to add features to individual services without impacting other services; and
- enforces consistency by ensuring that services follow a standardized set of operational guidelines.
How to implement the microservices chassis pattern
To implement the microservices chassis patten, start by creating shared libraries for each cross-cutting concern associated with your services. Then, expose all of the functions related to those concerns using an API or other dedicated application component. This transfers all cross-cutting concerns to individual collections of reusable libraries that the services can access as needed.
It's possible that centralizing cross-cutting concerns may cause bottlenecks for services that share libraries. To avoid this, modifications to the service chassis should be rare so that dependencies are not likely to cause significant problems. If you start to notice problematic couplings and dependencies, go back and examine if the functional libraries you've exposed are adequately parsed.
Here are a few additional points to keep in mind when designing a microservices chassis:
- Keep the chassis mechanism's code as lightweight as possible, looking carefully for any redundancies.
- Move any unnecessary features to a separate archival library.
- If the microservices architecture embodies multiple languages, consider using a sidecar proxy to mitigate the language-agnostic nature of the chassis.
- Avoid testing the chassis repetitively unless you have significantly altered the underlying framework.
- Take care to ensure that the chassis framework does not introduce additional component couplings or dependencies that significantly impact the overall system.
From microservices chassis to service mesh
While they do provide some of the same operational benefits, it's not out of the question to use a service mesh and microservices chassis pattern in tandem. While the service mesh provides powerful support for things like authentication, load balancing and rules-based traffic routing, developers can simultaneously take advantage of the code-level management tasks that the chassis excels at, such as externalized configuration and health checks.
The service mesh can also help isolate deployments when creating new versions of a service, which enables developers to restrict the release of new versions to only a certain set of users or user types.
Downsides of the microservices chassis pattern
Building and implementing the microservices chassis mechanism imposes a notable amount of upfront work and costs, but the payoff is its ability to eliminate code duplication and optimize application performance. That said, there are some issues worth considering before you jump into adoption.
One downside of adopting a microservice chassis is that it is not typically language- or platform-agnostic. Often, developers need to implement one chassis mechanism for each language and platform used within a single microservices architecture. Although a microservices chassis does provide an elegant way to handle cross-cutting concerns for services written in the same language or deployed from the same platform, the potential requirement to create multiple chassis is a downside.
Another potential downside is that a team's overarching application development or deployment platform may already include many of the beneficial utilities provided by a microservices chassis pattern, especially in large-scale enterprise scenarios. For example, many existing deployment environments and platforms, such as AWS and CloudBees, readily handle things like service discovery. Likewise, today's service mesh tools often perform many of the network-related operations handled by a microservices chassis.