Sidecar Design Pattern
The Sidecar Pattern: A Lightweight Approach to Microservices
Introduction
I’ve been looking at the ambassador pattern recently and it seemingly plays well with the sidecar pattern but before I just started implementing I thought I’d do some digging to better understand both and jot my findings here.
With the advent of microservice architecture, developers face the challenge of managing the complexity that comes with the interoperability of it. The sidecar pattern is here to make things a little bit easier.
What is the Sidecar pattern?
The Sidecar pattern is an architectural pattern that involves attaching a secondary container to the main container of an application. This secondary container is responsible for providing additional functionality to the main container without altering its core code. This approach is often used to add features such as logging, monitoring, security, or (in my case) external providers to the main container.
The Sidecar container can run alongside the main container and communicate with it through a shared network interface but I like to think we can be less prescriptive and simply place it right next to our application.
Architecture Examples
Let’s look at a simple example:
Pretty simple, right? The sidecar lives alongside the primary application and communicates outward, that way, if anything changes in the monitoring, logging, or database interfaces, we just change the sidecar.
Given the above, it’s easy to imagine multiple applications using the same sidecar. I think an easier way of architecting that would be imagine the sidecar as the primary focus, as below:
It’s important to note this is just a reference, we should not assume that there will only be one instance of the side sidecar, it could the sidecar exists as a Nuget package you pull into your .Net applications.
Coding Example
// Primary Container
public class DataProcessor
{
private readonly ILogger _logger;
public DataProcessor(ILogger logger)
{
_logger = logger;
}
public void ProcessData(string data)
{
// Processing logic goes here...
// Logging using the sidecar container
_logger.Log($"Data processed: {data}");
}
}
// Sidecar Container - Logger
public class Logger
{
public void Log(string message)
{
// Logging logic goes here...
Console.WriteLine($"[Logger] {message}");
}
}
// Usage
public class Program
{
public static void Main(string[] args)
{
// Create the sidecar container instance
var logger = new Logger();
// Create the primary container instance and inject the logger
var dataProcessor = new DataProcessor(logger);
// Process data
dataProcessor.ProcessData("Sample data");
}
}
This is a simple C# example where we use a sidecar for logging purposes.
We have a DataProcessor
class representing the primary container responsible for processing data. It has a constructor that accepts an instance of the ILogger
interface.
We also have a Logger
class representing the sidecar container responsible for logging. It contains a Log
method that takes a message and prints it to the console.
This example demonstrates the sidecar pattern by separating the logging functionality into a separate container, allowing the primary container to focus solely on data processing while delegating the logging responsibility to the sidecar.
How would my Sidecar work?
This is specific to my use case.
Let’s imagine we have an event emitter that sends out an event every time a customer logs in to our service. There are many downstream services that would want to subscribe to this event and if left to their own devices they would likely all take different approaches to doing so.
By creating a Nuget package that takes care of exactly that, it knows what to consume and how to create a model for the consumer to…consume. As long as versioning is done correctly we are able to change the upstream provider (our event emitter) and maintain the Nuget package, as long as we remember the contract!
On the flip side, let’s imagine we have an Account management system for our customers, we expose an interface for others to use but we now have a contract we have to maintain. If we provide a sidecar for other applications that will facilitate communication with our API through standardised models, we’re able to do some validation before requests are even sent to our API. We can even do some compile-time validation thus saving time and money.
One of the benefits of this pattern is that it allows us to add functionality to a service without having to modify the code of the service itself.
Conclusion
The Sidecar pattern is a lightweight approach to microservices architecture that provides an easy way to extend the functionality of microservices without altering their codebase. It allows developers to add functionality to microservices without compromising on the technology stack for the entire application. As microservices architecture continues to gain popularity, the Sidecar pattern is likely to become even more widespread.