Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
What is the sidecar pattern? Applications and services often have generic concerns such as health checks, configuration, metrics, as well as how they communicate with each other either directly or through messaging. Services usually implement these using libraries or SDKs to handle these concerns. How can you share these concerns across all these services so you’re not implementing them in every service? The sidecar pattern and ambassador pattern might be a good fit to solve this problem.
Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything in this post.
Regardless of the platform, you’re going to leverage libraries/packages/SDKs to handle common concerns like health checks, configuration, metrics, and more. Each service will have these common concerns and need to use the libraries for their respective platform.
For example, if you had two services, one written in .NET and the other written in Go. Each service would leverage libraries in its ecosystem to provide this common functionality.
Even with libraries, you still need to define how you’re handling and dealing with these common concerns. You may be using the same underlying infrastructure for both of them. As an example, each service might be publishing metrics to Cloudwatch.
Wouldn’t be nice if there was a standardized way that each service would handle these shared concerns?
The sidecar pattern allows you to extract the common concerns from your service and host them separately in a separate process, known as a sidecar.
In the containerized world, this is often thought of as a separate container from your service, however, it really is just a separate process that runs locally alongside your service.
With a sidecar, your service can now interact with a separate process that will handle the common concerns. The sidecar can perform health checks on your service, your service can send metrics to the sidecar, etc.
Back to the example of sending metrics to Cloudwatch. With the sidecar, we can also apply the Ambassador Pattern. This all makes the sidecar a proxy to send data to Cloudwatch. This means our service is interacting with the sidecar, using a common API, and it’s in charge of sending the data to Cloudwatch. It’s not just metrics and Cloudwatch, this applies to any external service.
The benefit is that the sidecar is handling any failures, retries, backoffs, etc. We don’t have to implement all kinds of retry logic in our service, rather that’s a common concern handled by the sidecar when communicating with external services.
If we want to make an HTTP request to an external service, we proxy it through the sidecar.
If there is a transient failure, the sidecar is responsible for doing the retry while still maintaining the connection to our service while it does so.
While each service has its own instance of a sidecar, you can be using the same sidecar for many different services. This allows each service to have the exact same interface to all of the shared concerns the sidecar provides.
This means that you can also be using it for things like a message broker.
If you read any of my other blog posts or watch my videos on YouTube, you know I’m an advocate for loosely coupling between services using messages, and not using blocking synchronous request-response.
A sidecar can provide a common abstraction over a message broker. This means that each service doesn’t have to interact with the broker directly, nor does it need to use the specific libraries or SDKs for that broker. The sidecar is providing a common API for sending and consuming messages and is abstracting the underlying message broker.
This means you could have one service using .NET and another using Python, both exchanging messages to a broker supporting AMQP. Each service would be completely unaware of what that underlying transport and message broker is.
So why would you want to use a sidecar and ambassador patterns? If you have services that are using different languages/platforms, and you want to standardize common concerns. Instead of each service implementing using their native packages/libraries/SDKs to their respective platform, the sidecar pattern allows you to define a common API that each service uses. This allows you to focus more on what your service actually provides rather than common infrastructure or concerns.
One trade-off to mention is latency. If you’re using a sidecar with the ambassador pattern to proxy requests to external services, a message broker, etc, you’re going to be adding latency. While this latency might not that much, it’s something to note.
If you don’t have a system that’s comprised of many services, or they are all using the same language/platform, then a sidecar could just be unneeded complexity. You could leverage an internal shared package/library that each service would use for defining a common API for shared concerns, it doesn’t need to be a sidecar. Again, your context matters. Use the patterns when you have the problems these patterns solve.
Developer-level members of my YouTube channel or Patreon get access to a private Discord server to chat with other developers about Software Architecture and Design as well as access to source code for any working demo application that I post on my blog or YouTube. Check out the YouTube Membership or Patreon for more info.