Stage 7 · Master
Resilience Patterns
Circuit Breakers
Meridian does not ship any breaker implementation yet; this chapter specifies where fail-fast isolation belongs once the gateway begins calling internal services and billing-service begins calling external payment systems.
Where Breakers Belong
Circuit breakers are only useful at boundaries that can fail independently of the current process. In Meridian, libs/platform packages are in-process code and therefore are not breaker candidates. The real failure surfaces emerge later: apps/gateway reverse-proxying to identity-service, community-service, and billing-service; identity-service resolving tenant slugs or memberships from its own datastore; and billing-service eventually calling an external payment provider. The current repository state has not reached those boundaries yet, because every service still exposes only /healthz.
This lesson is design specification. router.New() currently registers GET /healthz only, and middleware.WithRecover() is the gateway's only cross-cutting behavior. Breaker placement is being defined before downstream traffic exists so the boundary remains explicit when it is added.
Gateway Boundaries
The gateway is the only public entrypoint and already owns the downstream service URLs in its Config struct. That makes it the correct place to apply per-service breaker state once proxy routes are introduced. A spike of failures talking to identity-service should cause fast failure for identity-owned routes without poisoning community-service or billing-service traffic. Breaker state therefore belongs to each remote dependency, not to the entire gateway process.
apps/gateway/internal/proxy/reverse_proxy.gocreateResponsibility: Construct one reverse proxy per downstream service and attach a dependency-local breaker to each transport path.
Why now: identity-service, community-service, and billing-service can degrade independently; breaker state must reflect that isolation.
Connects to: Uses the downstream service URLs already loaded by apps/gateway/internal/config/config.go.
apps/gateway/internal/router/router.gomodifyResponsibility: Route versioned public endpoints to proxies that can fail fast when a dependency is open-circuited.
Why now: The router owns the mapping between public paths and dependency boundaries.
Connects to: Extends the current /healthz-only route table instead of creating a second entrypoint.
libs/platform/errors/errors.gomodifyResponsibility: Reserve stable error codes for dependency_unavailable style failures once handlers begin returning structured envelopes.
Why now: Fast-fail behavior needs a contractually recognizable wire shape, not ad hoc 502 text.
Connects to: Feeds the APIError envelope already implemented today.
Payment Provider Boundaries
billing-service is the second clear breaker candidate. Invoice reads against Meridian-owned data and payment capture against an external provider are different classes of dependency. A breaker around outbound payment API calls prevents slow or failing provider traffic from saturating billing-service goroutines and starving unrelated invoice lookups. The breaker should wrap only the provider client, never the entire billing-service request path.
| Boundary | Breaker value | Reason |
|---|---|---|
| gateway -> identity-service/community-service/billing-service | High | Remote network dependencies, independent failure domains, public latency impact |
| billing-service -> external payment provider | High | Third-party latency and partial outages should not consume all billing-service capacity |
| libs/platform/logging or libs/platform/tenancy | None | In-process packages do not fail as remote dependencies |
Where Not to Use Them
- Do not wrap /healthz handlers with breakers; they should report process health, not dependency policy.
- Do not place breakers around pure computation, validation, or libs/platform helpers; those are not remote failure domains.
- Do not use one global breaker for all downstream traffic in gateway; that couples unrelated services and defeats microservice isolation.
The Decision
Meridian reserves circuit breakers for remote boundaries with independent failure modes: gateway-to-service proxy calls and future billing-service calls to external payment systems. The implementation phase should attach breaker state per dependency, surface fast-fail responses through APIError, and leave in-process platform packages unwrapped. The result is selective isolation rather than blanket pessimism.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.