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
From the root of the repository, install the extension once, then start the sample application in dev mode:
mvn install -DskipTests
mvn -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. Its application.properties starts with chaos active and the Latency assault enabled (100-500 ms, level 100).
The dashboard settings are saved to integration-tests/.goblin-state.json and restored at the next start instead of application.properties. If you already used the dashboard, the toggles, layers and parameters below may differ from the defaults: delete the file before starting, or use Reset to defaults in the Danger zone (latency on with 100-5000 ms, java.lang.RuntimeException, layers Inbound REST + Outbound HTTP).
|
Scenario 1: a slow service triggers @Timeout
SampleService.timed() is guarded by @Timeout(400ms); GET /api/service/timeout calls it.
-
In Chaos layers, switch Service on and make sure Database is off (the default Inbound REST and Outbound HTTP can stay on). 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. -
Latency is already enabled: set its range to
600-700ms, above the 400 ms timeout, and clickSave. 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, switch Database on.
-
Disable Latency and enable Exception (the default
java.lang.RuntimeExceptionis fine; any exception class works, since@Retryretries on every exception by default). 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.
|