Skip to content

Resilience module: CircuitBreaker + Retry + TimeLimiter at the gateway - #1

Merged
Toleflaco merged 7 commits into
mainfrom
feature/circuit-breaker
Jul 18, 2026
Merged

Toleflaco merged 7 commits into
mainfrom
feature/circuit-breaker

Conversation

@Toleflaco

@Toleflaco Toleflaco commented Jul 16, 2026 •

Copy link
Copy Markdown
Owner

Complete the resilience module of Fase 10: three composable
patterns protecting task-service-route at the API gateway.

What this PR adds

  • CircuitBreaker (taskServiceBreaker) — count-based sliding
    window of 10 calls, opens at 50% failure rate with min 5 calls,
    10s in OPEN before HALF_OPEN probing.
  • Retry — 2 attempts on GET / SERVER_ERROR / IOException /
    TimeoutException, exponential backoff 500ms → 1500ms.
  • TimeLimiter (integrated in Spring Cloud CircuitBreaker
    abstraction) — explicitly configured to 5s, overriding the
    invisible 1s default.

Empirical verification

With docker compose pause task-service:

  • Calls 1-3: ~5.0s each (TimeLimiter ceiling).
  • Call 4: ~5ms (breaker OPEN, fail-fast).
  • Discontinuity of three orders of magnitude at the transition
    CLOSED → OPEN — the primary protection against gateway resource
    exhaustion under downstream failure.

Actuator confirms state and metrics:

{
  "failureRate": "80.0%",
  "bufferedCalls": 5,
  "failedCalls": 4,
  "notPermittedCalls": 1,
  "state": "OPEN"
}

Documentation

  • ADR-004 — border
    authentication with JWT at the gateway (context).
  • ADR-005
    — externalising downstream hostnames via per-profile
    placeholders (triggered by adding CircuitBreaker filter).
  • ADR-006 — resilience
    patterns composition, alternatives considered, empirical
    verification (this module).

Notable findings documented in ADR-006

  • Integrated TimeLimiter default of 1s in Spring Cloud
    CircuitBreaker Resilience4j, invisible in config until made
    explicit.
  • Per-route metadata.response-timeout ineffective under the
    new spring.cloud.gateway.server.webflux prefix in SCG
    2025.0.0. Kept in config with explanatory comment as
    documentation of the finding.
  • Real composition is a four-layer matryoshka, not three:
    TimeLimiter (outermost, integrated) → CircuitBreaker → Retry →
    HTTP call.

Add spring-cloud-starter-circuitbreaker-reactor-resilience4j
dependency to api-gateway. Declare taskServiceBreaker instance
with count-based sliding window (size 10, min 5 calls, 50% failure
rate threshold, 10s wait duration in open state, 3 permitted calls
in half-open). Wrap /tasks/** route with the CircuitBreaker filter
and forward to /fallback/tasks on failure. Add FallbackController
returning an RFC 7807 problem+json 503 response consistent with
the platform error handling convention.

End-to-end verification with Docker Compose deferred to next
session.

---

feat(gateway): scaffolding del circuit breaker para la ruta de task-service

Añade la dependencia spring-cloud-starter-circuitbreaker-reactor-
resilience4j al api-gateway. Declara la instancia taskServiceBreaker
con ventana deslizante por conteo (tamaño 10, mínimo 5 llamadas,
umbral de fallo del 50%, espera de 10s en estado open, 3 llamadas
permitidas en half-open). Envuelve la ruta /tasks/** con el filter
CircuitBreaker y redirige a /fallback/tasks en caso de fallo. Añade
FallbackController que devuelve una respuesta problem+json 503 según
RFC 7807, coherente con el manejo de errores del resto de la
plataforma.

Verificación end-to-end con Docker Compose diferida a la siguiente
sesión.
Fix TaskSpecifications.byUserId to reference the flat userId column
after the monolith → microservices decomposition (was navigating
root.get("user").get("id") which no longer exists).

Refactor gateway routing config to externalise downstream URIs per
profile via placeholders (services.auth-uri, services.task-uri).
The previous approach duplicated the entire routes list between
application.yml and application-docker.yml, causing filters added
to base (like the new CircuitBreaker) to silently disappear under
the docker profile.

Enable circuit breaker health indicator at the management level
(management.health.circuitbreakers.enabled: true) and expose the
/actuator/circuitbreakers endpoint for direct observability of
breaker state, in addition to the aggregated health endpoint.
…le placeholders

Documents the refactoring applied during Session 8 Part 2 of Fase 10
to eliminate the duplication of the routes list between application.yml
and application-docker.yml. The pattern preserved was structural: keep
routes declared once in the base file with placeholder-referenced
hostnames, and let per-profile files override only the actual variance.

Follows Nygard's format consistent with ADR-004.
Wraps Retry inside CircuitBreaker on the unified task-service-route.
Retry configured with retries=2, methods=GET, series=SERVER_ERROR,
exponential backoff (500ms firstBackoff, 1500ms maxBackoff, factor 2).

Empirical verification with task-service stopped:
- HTTP 503 fallback served in ~1020ms consistently
- Circuit breaker sees 1 aggregated failure per client request
- CircuitBreaker order=1 (outer), Retry order=2 (inner) confirmed
  via /actuator/gateway/routes

Refs: feature/circuit-breaker branch, ADR-006 pending
(TimeLimiter + ADR-006 to be added in next session)
Add resilience4j.timelimiter instance 'taskServiceBreaker' with
timeoutDuration 5s and cancelRunningFuture true. This makes explicit
the timeout mechanism previously acting as an invisible 1s default
from Spring Cloud CircuitBreaker's integrated TimeLimiter.

Empirically verified in Sesion 10:
- With integrated default (1s): curl to paused downstream ~1.02s
- With explicit config (5s): curl to paused downstream ~5.02s
- The TimeLimiter is the dominant timeout mechanism, wrapping the
  entire operation including Retry attempts.

Document per-route metadata timeouts as ineffective under the new
spring.cloud.gateway.server.webflux prefix in SCG 2025.0.0. Kept
in config with explanatory comment pointing to ADR-006 for future
investigation.
- Add dedicated section 'Resilience at the gateway' summarizing
  CircuitBreaker + Retry + TimeLimiter composition, link to ADR-006.
- Extend tech stack bullet on Spring Cloud Gateway and add
  Resilience4j entry.
- Include /actuator/gateway/routedefinitions and
  /actuator/circuitbreakers in observability endpoints.
- Retire Circuit Breaker from Future work (implemented); add
  Bulkhead pattern as deferred item conditional on fan-out routing.
- Update foundation description to include resilience patterns as
  part of the operational core.
@Toleflaco Toleflaco changed the title Feature/circuit breaker Resilience module: CircuitBreaker + Retry + TimeLimiter at the gateway Jul 18, 2026
@Toleflaco
Toleflaco merged commit 4d9e0cc into main Jul 18, 2026
1 check passed
@Toleflaco
Toleflaco deleted the feature/circuit-breaker branch July 18, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant