Microservice Chassis
Context
Section titled “Context”Every service you build needs the same unglamorous machinery before it can do anything useful. It must load its configuration, set up structured logging with request correlation, expose metrics in the format your monitoring expects, answer health and readiness probes for the orchestrator, and register itself for discovery. None of this is business logic, yet it is a precondition for the business logic to run safely.
When you wrote one service, you assembled this plumbing once. By the tenth service you notice the same hundreds of lines being copied, pasted, and quietly diverging: one service exposes /healthz, another /health; one logs JSON, another plain text; metrics are named inconsistently. The cross-cutting baseline is reimplemented, slightly differently, in every service.
Problem
Section titled “Problem”You want every new service to start life with configuration, logging, metrics, health checks, and discovery already wired in and consistent across the fleet — so that a service exposes the same health endpoint, emits logs in the same shape, and reports metrics with the same names as every other service. You want a developer creating a new service to write business logic on day one, not spend a week reassembling boilerplate. And you want to upgrade that baseline in one place rather than in twenty repositories.
So the forces are: a consistent, ready-made operational baseline in every service, with minimal per-service effort and a single place to evolve it.
Solution
Section titled “Solution”Build a microservice chassis: a framework, library, or base that bundles the cross-cutting concerns so a new service inherits them. The chassis owns the plumbing — it reads configuration, configures structured logging, registers a metrics endpoint, mounts health and readiness probes, and handles discovery registration — and exposes a small surface where you plug in the business logic.
The developer’s job shrinks to: start from the chassis, add the routes and domain logic, and ship. Everything operational is already present and identical to every other service built on the chassis. Often the chassis is paired with a service template — a starter repository, generator, or skeleton — so creating a new service means generating from the template and filling in the business code.
Because the baseline lives in one shared component, improving it (a new metrics field, a tightened log format, a better health check) is one change you roll out by bumping the chassis version across services, rather than editing each repository by hand.
flowchart TB
subgraph Service[A service built on the chassis]
direction TB
Biz[Business logic: routes and domain rules]
subgraph Chassis[Microservice chassis]
Cfg[Config loading]
Log[Structured logging]
Met[Metrics endpoint]
Health[Health and readiness probes]
Disc[Service discovery registration]
end
Biz --> Chassis
end
Chassis --> Plat[Platform: orchestrator, monitoring, registry] Example
Section titled “Example”Here a service is created on the chassis. The single bootstrap call wires up config, logging, metrics, health, and discovery; the only thing the developer adds is the route that carries business meaning. Each example is idiomatic to its language.
import { createService } from '@acme/chassis';
// The chassis loads config, sets up logging, metrics,// /health, /ready, and registers the service for discovery.const app = createService({ name: 'order-service' });
// Developer adds only the business logic.app.get('/orders/:id', async (req, res) => { const order = await orderRepo.find(req.params.id); res.json(order);});
app.start();from acme_chassis import create_service
# The chassis wires config, logging, metrics,# /health, /ready, and discovery registration.app = create_service(name="order-service")
# Developer adds only the business logic.@app.get("/orders/{order_id}")async def get_order(order_id: str): return await order_repo.find(order_id)
app.start()package main
import "github.com/acme/chassis"
func main() { // The chassis sets up config, logging, metrics, // /health, /ready, and discovery registration. app := chassis.NewService("order-service")
// Developer adds only the business logic. app.Get("/orders/{id}", func(c *chassis.Ctx) error { order, err := orderRepo.Find(c.Param("id")) if err != nil { return err } return c.JSON(order) })
app.Start()}use acme_chassis::Service;
#[tokio::main]async fn main() { // The chassis wires config, logging, metrics, // /health, /ready, and discovery registration. let mut app = Service::new("order-service");
// Developer adds only the business logic. app.get("/orders/:id", |ctx| async move { let order = order_repo.find(ctx.param("id")).await?; ctx.json(order) });
app.start().await;}Resulting context
Section titled “Resulting context”What you gain:
- Consistency for free. Every service exposes the same health endpoint, log shape, and metric names, because one shared component provides them — observability and operability stop being per-team guesswork.
- Faster service creation. A new service starts with the operational baseline already present, so developers write domain code instead of reassembling plumbing.
- One place to evolve the baseline. Improvements to logging, metrics, or health land in the chassis and roll out by version bump, not by editing every repository.
What it costs you:
- Per-language effort. A polyglot fleet needs a chassis per language, and keeping them feature-equivalent is real, ongoing work.
- Coupling and versioning friction. Every service depends on the chassis; a breaking change forces a coordinated upgrade, and a stale chassis can hold a service back.
- Risk of bloat. A chassis that tries to do everything becomes heavy and opinionated. Keep it focused on genuinely cross-cutting concerns, not business helpers.
A practical note: a chassis handles in-process concerns (config, logging, metrics, health), while a Service Mesh handles between-process networking. Many teams use both.
Related patterns
Section titled “Related patterns”- Externalized Configuration — the chassis is usually what reads and applies the externalized config.
- Service Mesh — handles the networking cross-cutting concerns the chassis leaves to the sidecar.
- Service per Container — a chassis-built service is still packaged and run as one service per container.