# Sidecar

## Problem

Every microservice needs cross-cutting infrastructure concerns: logging, metrics, TLS termination, configuration refresh, health checks, service mesh integration. Implementing these in every service leads to code duplication, inconsistency, and coupling business logic to infrastructure libraries. Polyglot services (different languages) make this even harder.

## Solution

Deploy a **sidecar** process alongside each service instance. The sidecar handles cross-cutting concerns while the main service focuses on business logic. They share the same lifecycle (start/stop together) and communicate over localhost (HTTP, gRPC, or shared files).

The sidecar:
- Intercepts inbound/outbound traffic for TLS, retries, circuit breaking.
- Collects and exports logs, metrics, and traces.
- Manages configuration refresh, secrets rotation, health checks.
- Is language-agnostic — works with any service regardless of tech stack.

## When to Use

- Multiple services in different languages need the same infrastructure concerns.
- You want to separate infrastructure from business logic cleanly.
- Service mesh deployment (Envoy, Linkerd sidecars).
- Configuration, secrets, or certificates need to be managed uniformly.

## When to Avoid

- Single-service deployments where a library is simpler.
- The overhead of an extra process per instance is unacceptable (very resource-constrained environments).
- The concern is truly service-specific and doesn't generalise.

## Pseudocode

```
class Sidecar:
    function __init__(service_port, config):
        this.service_url = "http://localhost:" + service_port
        this.metrics = MetricsCollector()
        this.logger = StructuredLogger()
        this.health_checker = HealthChecker(service_url)

    function intercept_request(request):
        this.logger.log("incoming", request)
        this.metrics.increment("requests_total")

        start = now()
        response = forward(request, this.service_url)
        duration = now() - start

        this.metrics.observe("request_duration", duration)
        this.logger.log("response", response.status, duration)
        return response

    function check_health():
        return this.health_checker.check()
```

## Python

```python
"""Sidecar pattern in Python — helper process for cross-cutting concerns."""
import time
from dataclasses import dataclass, field


# ── Request / Response ────────────────────────────────────────

@dataclass
class Request:
    method: str
    path: str
    headers: dict = field(default_factory=dict)
    body: str = ""


@dataclass
class Response:
    status: int
    body: str
    headers: dict = field(default_factory=dict)


# ── Main application service (business logic only) ────────────

class OrderService:
    """Pure business logic — no logging, metrics, or auth."""

    def handle(self, req: Request) -> Response:
        if req.path == "/orders" and req.method == "GET":
            return Response(200, '{"orders": [{"id": 1, "item": "Widget"}]}')
        if req.path == "/orders" and req.method == "POST":
            return Response(201, '{"id": 2, "status": "created"}')
        return Response(404, '{"error": "not found"}')

    def health(self) -> bool:
        return True


# ── Sidecar components ────────────────────────────────────────

class MetricsCollector:
    def __init__(self):
        self.counters: dict[str, int] = {}
        self.histograms: dict[str, list[float]] = {}

    def increment(self, name: str) -> None:
        self.counters[name] = self.counters.get(name, 0) + 1

    def observe(self, name: str, value: float) -> None:
        self.histograms.setdefault(name, []).append(value)

    def summary(self) -> dict:
        result = {"counters": dict(self.counters)}
        for name, values in self.histograms.items():
            result[name] = {
                "count": len(values),
                "avg_ms": sum(values) / len(values) if values else 0,
            }
        return result


class StructuredLogger:
    def __init__(self):
        self.entries: list[dict] = []

    def log(self, event: str, **kwargs) -> None:
        entry = {"event": event, "timestamp": time.time(), **kwargs}
        self.entries.append(entry)


class HealthChecker:
    def __init__(self, service):
        self.service = service
        self.last_status: bool = False

    def check(self) -> dict:
        try:
            self.last_status = self.service.health()
        except Exception:
            self.last_status = False
        return {"healthy": self.last_status}


# ── Sidecar proxy ────────────────────────────────────────────

class Sidecar:
    def __init__(self, service):
        self.service = service
        self.metrics = MetricsCollector()
        self.logger = StructuredLogger()
        self.health_checker = HealthChecker(service)

    def handle(self, req: Request) -> Response:
        """Intercept, add cross-cutting concerns, forward to service."""
        # Pre-processing
        self.logger.log("request_received", method=req.method, path=req.path)
        self.metrics.increment("requests_total")

        # Forward to main service
        start = time.time()
        response = self.service.handle(req)
        duration_ms = (time.time() - start) * 1000

        # Post-processing
        self.metrics.observe("request_duration_ms", duration_ms)
        self.metrics.increment(f"status_{response.status}")
        self.logger.log(
            "request_completed",
            method=req.method, path=req.path,
            status=response.status, duration_ms=round(duration_ms, 2),
        )

        return response

    def check_health(self) -> dict:
        return self.health_checker.check()

    def get_metrics(self) -> dict:
        return self.metrics.summary()


# ── Tests ─────────────────────────────────────────────────────

def test_sidecar_forwards_requests():
    sidecar = Sidecar(OrderService())
    resp = sidecar.handle(Request("GET", "/orders"))
    assert resp.status == 200 and "Widget" in resp.body
    print(f"PASS: sidecar forwarded GET /orders -> {resp.status} {resp.body}")


def test_sidecar_collects_metrics():
    sidecar = Sidecar(OrderService())
    sidecar.handle(Request("GET", "/orders"))
    sidecar.handle(Request("POST", "/orders"))
    sidecar.handle(Request("GET", "/nonexistent"))

    metrics = sidecar.get_metrics()
    assert metrics["counters"]["requests_total"] == 3
    assert metrics["counters"]["status_200"] == 1
    assert metrics["counters"]["status_201"] == 1
    assert metrics["counters"]["status_404"] == 1
    print(f"PASS: metrics collected: {metrics}")


def test_sidecar_logs_requests():
    sidecar = Sidecar(OrderService())
    sidecar.handle(Request("GET", "/orders"))

    logs = sidecar.logger.entries
    assert len(logs) == 2  # request_received + request_completed
    assert logs[0]["event"] == "request_received"
    assert logs[1]["event"] == "request_completed"
    print(f"PASS: logged {len(logs)} entries: {[e['event'] for e in logs]}")


def test_sidecar_health_check():
    sidecar = Sidecar(OrderService())
    health = sidecar.check_health()
    assert health["healthy"] is True
    print(f"PASS: health check: {health}")


def test_business_logic_stays_clean():
    """OrderService has zero infrastructure code."""
    service = OrderService()
    resp = service.handle(Request("GET", "/orders"))
    assert resp.status == 200
    # No logging, metrics, or auth in OrderService
    assert not hasattr(service, "logger")
    assert not hasattr(service, "metrics")
    print("PASS: business logic is free of infrastructure concerns")


if __name__ == "__main__":
    test_sidecar_forwards_requests()
    test_sidecar_collects_metrics()
    test_sidecar_logs_requests()
    test_sidecar_health_check()
    test_business_logic_stays_clean()
    print("\nAll Sidecar tests passed.")
```

## JavaScript

```javascript
// sidecar.js — Sidecar pattern in JavaScript

// ── Main application service (business logic only) ───────────

class OrderService {
  handle(req) {
    if (req.path === "/orders" && req.method === "GET") {
      return { status: 200, body: '{"orders": [{"id": 1, "item": "Widget"}]}' };
    }
    if (req.path === "/orders" && req.method === "POST") {
      return { status: 201, body: '{"id": 2, "status": "created"}' };
    }
    return { status: 404, body: '{"error": "not found"}' };
  }

  health() {
    return true;
  }
}

// ── Sidecar components ───────────────────────────────────────

class MetricsCollector {
  constructor() {
    this.counters = {};
    this.histograms = {};
  }

  increment(name) {
    this.counters[name] = (this.counters[name] || 0) + 1;
  }

  observe(name, value) {
    if (!this.histograms[name]) this.histograms[name] = [];
    this.histograms[name].push(value);
  }

  summary() {
    const result = { counters: { ...this.counters } };
    for (const [name, values] of Object.entries(this.histograms)) {
      const avg = values.reduce((a, b) => a + b, 0) / values.length;
      result[name] = { count: values.length, avg_ms: avg };
    }
    return result;
  }
}

class StructuredLogger {
  constructor() {
    this.entries = [];
  }

  log(event, extra = {}) {
    this.entries.push({ event, timestamp: Date.now(), ...extra });
  }
}

class HealthChecker {
  constructor(service) {
    this.service = service;
    this.lastStatus = false;
  }

  check() {
    try {
      this.lastStatus = this.service.health();
    } catch {
      this.lastStatus = false;
    }
    return { healthy: this.lastStatus };
  }
}

// ── Sidecar proxy ────────────────────────────────────────────

class Sidecar {
  constructor(service) {
    this.service = service;
    this.metrics = new MetricsCollector();
    this.logger = new StructuredLogger();
    this.healthChecker = new HealthChecker(service);
  }

  handle(req) {
    // Pre-processing
    this.logger.log("request_received", {
      method: req.method,
      path: req.path,
    });
    this.metrics.increment("requests_total");

    // Forward to main service
    const start = performance.now();
    const response = this.service.handle(req);
    const durationMs = performance.now() - start;

    // Post-processing
    this.metrics.observe("request_duration_ms", durationMs);
    this.metrics.increment(`status_${response.status}`);
    this.logger.log("request_completed", {
      method: req.method,
      path: req.path,
      status: response.status,
      duration_ms: Math.round(durationMs * 100) / 100,
    });

    return response;
  }

  checkHealth() {
    return this.healthChecker.check();
  }

  getMetrics() {
    return this.metrics.summary();
  }
}

// ── Tests ────────────────────────────────────────────────────

function testSidecarForwardsRequests() {
  const sidecar = new Sidecar(new OrderService());
  const resp = sidecar.handle({ method: "GET", path: "/orders" });
  console.assert(resp.status === 200 && resp.body.includes("Widget"));
  console.log(`PASS: sidecar forwarded GET /orders -> ${resp.status} ${resp.body}`);
}

function testSidecarCollectsMetrics() {
  const sidecar = new Sidecar(new OrderService());
  sidecar.handle({ method: "GET", path: "/orders" });
  sidecar.handle({ method: "POST", path: "/orders" });
  sidecar.handle({ method: "GET", path: "/nonexistent" });

  const metrics = sidecar.getMetrics();
  console.assert(metrics.counters.requests_total === 3);
  console.assert(metrics.counters.status_200 === 1);
  console.assert(metrics.counters.status_201 === 1);
  console.assert(metrics.counters.status_404 === 1);
  console.log(`PASS: metrics collected: ${JSON.stringify(metrics.counters)}`);
}

function testSidecarLogsRequests() {
  const sidecar = new Sidecar(new OrderService());
  sidecar.handle({ method: "GET", path: "/orders" });

  const logs = sidecar.logger.entries;
  console.assert(logs.length === 2);
  console.assert(logs[0].event === "request_received");
  console.assert(logs[1].event === "request_completed");
  console.log(
    `PASS: logged ${logs.length} entries: ${JSON.stringify(logs.map((e) => e.event))}`
  );
}

function testSidecarHealthCheck() {
  const sidecar = new Sidecar(new OrderService());
  const health = sidecar.checkHealth();
  console.assert(health.healthy === true);
  console.log(`PASS: health check: ${JSON.stringify(health)}`);
}

function testBusinessLogicStaysClean() {
  const service = new OrderService();
  const resp = service.handle({ method: "GET", path: "/orders" });
  console.assert(resp.status === 200);
  console.assert(service.logger === undefined);
  console.assert(service.metrics === undefined);
  console.log("PASS: business logic is free of infrastructure concerns");
}

testSidecarForwardsRequests();
testSidecarCollectsMetrics();
testSidecarLogsRequests();
testSidecarHealthCheck();
testBusinessLogicStaysClean();
console.log("\nAll Sidecar tests passed.");
```

## Related Patterns

- **Decorator** — the sidecar decorates the main service with cross-cutting concerns without modifying its code.
- **Proxy** — the sidecar acts as a network proxy, intercepting inbound and outbound traffic for the main service.
- **Observer** — the sidecar observes the main service's health and traffic to emit metrics and logs.
