Add resilience4j-circuit-breaker: Resilience4j 2.4.0 vs Spring Framework 7 core, on Boot 4.1
Companion module for the rewritten post 'Resilience4j Circuit Breaker in Spring Boot 4.1: What It's Still For', reworked around Framework 7 now shipping @Retryable/@ConcurrencyLimit in core. Covers what's still Resilience4j's job (circuit breaker, rate limiter, bulkhead's bounded wait, fallback methods, Actuator/Micrometer metrics), the off-by-one between maxAttempts and maxRetries, and two Boot-4.1 build breaks: spring-boot-starter-aop no longer exists (renamed to spring-boot-starter-aspectj, proven with Maven Central metadata and the renamed starter's own POM -- see resilience/docs/08-starter-aop-renamed-to-starter-aspectj.md in this same repo), and the resulting fix uses that renamed starter directly rather than assembling spring-aop + aspectjweaver by hand. Kept as its own module rather than a new top-level repository, alongside the existing resilience/ module for the sibling Framework-7 post.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# resilience4j-circuit-breaker
|
||||
|
||||
Companion module for the ankurm.com post [**"Resilience4j Circuit Breaker in Spring Boot 4.1:
|
||||
What It's Still For"**](https://ankurm.com/resilience4j-circuit-breaker-spring-boot/). Every
|
||||
claim in the post about the new `@Retryable`/`@ConcurrencyLimit` API in Spring Framework 7, and
|
||||
about how it does and doesn't overlap with Resilience4j, is backed by a file under
|
||||
`docs/output/` produced by a real test run against real Maven Central artifacts.
|
||||
|
||||
Lives in this container repo (not as its own top-level repository) alongside
|
||||
[`../resilience`](../resilience), the companion module for the sibling post on Framework 7's
|
||||
built-in `@Retryable`/`@ConcurrencyLimit`. The two modules overlap in theme by design — this one
|
||||
is entered from "I want a circuit breaker," that one from "what did Framework 7 just ship" — and
|
||||
intentionally keep separate demo apps rather than sharing one, since each post's transcripts need
|
||||
to stay independently reproducible from its own module.
|
||||
|
||||
## Versions (verified against `repo1.maven.org` maven-metadata.xml, not aggregators)
|
||||
|
||||
| Component | Version | Notes |
|
||||
|---|---|---|
|
||||
| JDK | 25 (Temurin 25.0.4.1+1) | latest LTS |
|
||||
| Spring Boot | 4.1.1 | latest GA at time of writing; 4.2.0-M1 exists but is a milestone |
|
||||
| Spring Framework | 7.0.9 | latest GA; 7.1.0-M1 exists but is a milestone |
|
||||
| Resilience4j | 2.4.0 (`resilience4j-spring-boot4`) | depends internally on `resilience4j-spring6`, not a "spring7" module — see [docs/01](docs/01-two-resilience-stacks.md) |
|
||||
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
./scripts/run-all.sh # regenerates every file in docs/output/ from a real test run
|
||||
./scripts/run.sh # starts the app on :8080 to poke at by hand
|
||||
curl localhost:8080/actuator/health
|
||||
curl localhost:8080/actuator/circuitbreakers
|
||||
```
|
||||
|
||||
Requires JDK 25 and Maven. First run must be online (Maven needs to fetch plugins into the
|
||||
local cache); `-o` works for subsequent builds.
|
||||
|
||||
## What's demonstrated where
|
||||
|
||||
| Area | Source | Test | Transcript |
|
||||
|---|---|---|---|
|
||||
| Resilience4j circuit breaker: trip, stay open, half-open, recover | [`R4jPaymentService`](src/main/java/com/ankurm/resilience/r4j/R4jPaymentService.java) | [`CircuitBreakerTripAndRecoverTest`](src/test/java/com/ankurm/resilience/r4j/CircuitBreakerTripAndRecoverTest.java) | [`01`](docs/output/01-circuitbreaker-trip.txt) |
|
||||
| `@Retryable`: recovers from transient failure | [`SpringRetryablePaymentService`](src/main/java/com/ankurm/resilience/springresilience/SpringRetryablePaymentService.java) | [`SpringRetryableTest`](src/test/java/com/ankurm/resilience/springresilience/SpringRetryableTest.java) | [`03a`](docs/output/03a-retryable-recovers.txt) |
|
||||
| `@Retryable`: no memory between calls | same | same | [`03b`](docs/output/03b-retryable-no-memory.txt) |
|
||||
| `@Retryable`: self-invocation trap | same | same | [`03c`](docs/output/03c-retryable-self-invocation.txt) |
|
||||
| Resilience4j `@Retry` side-by-side (`maxAttempts` counts differently than `maxRetries`) | [`R4jRetryService`](src/main/java/com/ankurm/resilience/r4j/R4jRetryService.java) | [`R4jRetryTest`](src/test/java/com/ankurm/resilience/r4j/R4jRetryTest.java) | [`03d`](docs/output/03d-r4j-retry.txt), [`03e`](docs/output/03e-r4j-retry-exhaustion.txt) |
|
||||
| `@ConcurrencyLimit` BLOCK policy | [`ConcurrencyLimitedService`](src/main/java/com/ankurm/resilience/springresilience/ConcurrencyLimitedService.java) | [`ConcurrencyLimitTest`](src/test/java/com/ankurm/resilience/springresilience/ConcurrencyLimitTest.java) | [`04a`](docs/output/04a-concurrencylimit-block.txt) |
|
||||
| `@ConcurrencyLimit` REJECT policy | same | same | [`04b`](docs/output/04b-concurrencylimit-reject.txt) |
|
||||
| Resilience4j `@Bulkhead` (bounded wait) for comparison | [`R4jBulkheadService`](src/main/java/com/ankurm/resilience/r4j/R4jBulkheadService.java) | same | [`04c`](docs/output/04c-r4j-bulkhead-comparison.txt) |
|
||||
| Real `/actuator/health` + `/actuator/circuitbreakers` with a breaker OPEN | [`R4jPaymentService`](src/main/java/com/ankurm/resilience/r4j/R4jPaymentService.java) | [`ActuatorHealthTest`](src/test/java/com/ankurm/resilience/r4j/ActuatorHealthTest.java) | [`05`](docs/output/05-actuator-health-tripped.txt) |
|
||||
|
||||
## Endpoints (from `scripts/run.sh`)
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|---|---|
|
||||
| `GET /actuator/health` | includes circuit breaker health when `management.health.circuitbreakers.enabled=true` |
|
||||
| `GET /actuator/circuitbreakers` | live circuit breaker state |
|
||||
| `GET /actuator/circuitbreakerevents` | event stream of state transitions |
|
||||
| `GET /actuator/metrics` | includes `resilience4j.circuitbreaker.*` Micrometer series |
|
||||
|
||||
There is no custom diagnostic endpoint in this repo — the standard Actuator set above already
|
||||
exposes everything the post needed, so nothing has to be deleted before shipping.
|
||||
|
||||
**Note on `/actuator/health` in 2.4.0:** with `management.endpoint.health.show-details: always`,
|
||||
`/actuator/health` reports only an aggregate `"circuitBreakers":{"status":"UNKNOWN"}` — no
|
||||
per-instance breakdown. The per-breaker detail (`state`, `failureRate`, `bufferedCalls`, etc.)
|
||||
lives at `/actuator/circuitbreakers` instead. Real captured output of both, side by side, with
|
||||
the breaker actually OPEN: [docs/output/05-actuator-health-tripped.txt](docs/output/05-actuator-health-tripped.txt).
|
||||
|
||||
## Documentation chapters
|
||||
|
||||
1. [Two resilience stacks on one classpath](docs/01-two-resilience-stacks.md) — what moved into
|
||||
Spring Framework 7 core, what didn't, and two build breaks you'll hit getting there
|
||||
2. [The circuit breaker, verified](docs/02-circuit-breaker.md)
|
||||
3. [@Retryable: retries, but no memory](docs/03-spring-retryable.md)
|
||||
4. [@ConcurrencyLimit vs Resilience4j's Bulkhead](docs/04-concurrency-limit.md)
|
||||
5. [Production checklist: which one, for what](docs/05-production-checklist.md)
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
Reference in New Issue
Block a user