Microservice Architecture is gaining lot of popularity and wider adoption within the community.
Building a microservice architecture that is highly scalable, resilient, secure, and observable is also becoming a big challenge. However, Microservices Architecture says that services can be heterogeneous, should not be dependent on the each other but what will happen to common functions which needs to included as a part of architecture like service discovery, logging, fault injection etc. which needs to standardize across all services and should not be independent on services or any one language?
What is the problem?
Applications and services often require related functionality, such as monitoring, logging, configuration, and networking services. These peripheral tasks can be implemented as separate components or services.
If they are tightly integrated into the application, they can run in the same process as the application, making efficient use of shared resources. However, this also means they are not well isolated, and an outage in one of these components can affect other components or the entire application. Also, they usually need to be implemented using the same language as the parent application. As a result, the component and the application have close interdependence on each other.
If the application is decomposed into services, then each service can be built using different languages and technologies. While this gives more flexibility, it means that each component has its own dependencies and requires language-specific libraries to access the underlying platform and any resources shared with the parent application. In addition, deploying these features as separate services can add latency to the application. Managing the code and dependencies for these language-specific interfaces can also add considerable complexity, especially for hosting, deployment, and management.
What we really need?
We want a process or piece of code which can place inside their own process or container and providing homogeneous interface for Platform services across languages.
A service or process which is primarily is not the part of the Application but connected to Application in such a manner that it goes hand in hand with parent process, wherever parent process goes it goes.
What is Side Car Pattern
Segregating the functionalities of an application into a separate process can be viewed as a Sidecar pattern. The sidecar design pattern allows you to add a number of capabilities to your application without additional configuration code for third-party components.
As a sidecar is attached to a motorcycle, similarly in software architecture a sidecar is attached to a parent application and extends/enhances its functionalities. A sidecar is loosely coupled with the main application.
Let me explain this with an example. Imagine that you have six microservices talking with each other in order to determine the cost of a package.
Each microservice needs to have functionalities like observability, monitoring, logging, configuration, circuit breakers, and more. All these functionalities are implemented inside each of these microservices using some industry standard third-party libraries.
But, is this not redundant? Does it not increase the overall complexity of your application? What happens if your applications are written in different languages — how do you incorporate the third-party libraries which are generally specific to a language like .NET, Java, Python, etc.?
Benefits of Sidecar Pattern
A sidecar is independent from its primary application in terms of runtime environment and programming language, so you don’t need to develop one sidecar per language.
The sidecar can access the same resources as the primary application. For example, a sidecar can monitor system resources used by both the sidecar and the primary application.
Because of its proximity to the primary application, there’s no significant latency when communicating between them.
Even for applications that don’t provide an extensibility mechanism, you can use a sidecar to extend functionality by attaching it as its own process in the same host or sub-container as the primary application.
The sidecar pattern is often used with containers and referred to as a sidecar container or sidekick container.
When to use this Pattern
Your primary application uses a heterogeneous set of languages and frameworks. A component located in a sidecar service can be consumed by applications written in different languages using different frameworks.
A component is owned by a remote team or a different organization.
A component or feature must be co-located on the same host as the application
You need a service that shares the overall lifecycle of your main application, but can be independently updated.
You need fine-grained control over resource limits for a particular resource or component. For example, you may want to restrict the amount of memory a specific component uses. You can deploy the component as a sidecar and manage memory usage independently of the main application.
When not to use this Pattern:
When interprocess communication needs to be optimized. Communication between a parent application and sidecar services includes some overhead, notably latency in the calls. This may not be an acceptable trade-off for chatty interfaces.
For small applications where the resource cost of deploying a sidecar service for each instance is not worth the advantage of isolation.
When the service needs to scale differently than or independently from the main applications. If so, it may be better to deploy the feature as a separate service.