End-to-end example: prove your resilience annotations work
This walkthrough uses the sample application of the integration-tests module to verify, for real, that MicroProfile Fault Tolerance annotations react to failures: a @Timeout on a slow business method, then a @Retry + @Fallback on a failing database.
A fault injected at the inbound REST boundary (HTTP_IN) hits the request before the resource runs, so the Fault Tolerance annotations below it never see it. Both scenarios therefore arm a deeper layer — SERVICE or DATABASE — whose faults are injected inside or below the guarded methods (see Chaos layers).
Start the sample application
./mvnw -pl integration-tests quarkus:dev
Open the Dev UI at http://localhost:8080/q/dev and the Goblin Chaos Dashboard. The sample application has an in-memory H2 datasource, so every layer is available.
Scenario 1: a slow service triggers @Timeout
SampleService.timed() is guarded by @Timeout(400ms); GET /api/service/timeout calls it.
-
In Chaos layers, check Service and make sure Database is unchecked (the default Inbound REST and Outbound HTTP can stay checked). At level
100the deepest armed layer wins the whole request: with Database armed, this request — which never touches the database — would resolve to the database layer and not be assaulted at all. -
Enable Latency and set the range to
600-700ms, above the 400 ms timeout. Keep the level at100. -
Call the endpoint:
curl -w "\nstatus=%{http_code} time=%{time_total}s\n" http://localhost:8080/api/service/timeout -
The call fails with a
500after about 0.4 s instead of 0.6 s:@Timeoutinterrupted the injected delay. The Assault History shows alatencyentry onio.quarkiverse.goblin.it.SampleService.timedwhose duration is the delay actually endured (about 400 ms), and the status bar counts it underservice.
Scenario 2: a failing database triggers @Retry then @Fallback
SampleService.databasePingWithRetry() is guarded by @Retry(maxRetries = 2) and @Fallback; it calls SampleRepository, which acquires a JDBC connection. GET /api/db/retry calls it.
-
In Chaos layers, check Database.
-
Disable Latency and enable Exception (the default
java.lang.RuntimeExceptionis fine). Keep the level at100. -
Call the endpoint:
curl -w "\nstatus=%{http_code}\n" http://localhost:8080/api/db/retry -
The response is
200withdatabase fallback reply. The Assault History shows threeexceptionentries onDatabase <default> connection: the first attempt and the two retries each acquired a connection and failed, then the fallback answered; the status bar counts them underdatabase. Compare withGET /api/db/ping, which has no Fault Tolerance annotation and fails with a500.
Make it intermittent
Set the level to 30 and call the endpoints repeatedly: some attempts now succeed, so @Retry recovers before the fallback, and only part of the calls hit the timeout — the intermittent, non-deterministic failures that are the hardest to get right in production.
The same scenarios can run in a @QuarkusTest: set quarkus.goblin.test.enabled=true, arm the layer and the assault through the injected AssaultEngine (engine.getMutableConfig()), and assert on the response and on engine.getHistory(). See GoblinServiceLayerIntegrationTest and GoblinDatabaseLayerIntegrationTest in the integration-tests module.
|