This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
The following commit(s) were added to refs/heads/main by this push:
new 05bf8cd fail-well/circuit-breaker - the fallback tells a failed call
from one the open breaker did not make (#127)
05bf8cd is described below
commit 05bf8cd44efac2e5c2575aa535e77cf8d8bf1515
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Oct 20 12:55:30 2026 +0200
fail-well/circuit-breaker - the fallback tells a failed call from one the
open breaker did not make (#127)
* chore: the examples move to Camel 4.23.0: CI tests with it, and
ai/mcp-server and contracts/openapi-server use what it brings
CI pins Camel 4.23.0 for the CLI and for the integrations the Citrus tests
start, as camel@apache/camel defaults to
the latest LTS release (4.22.0). ai/mcp-server sets the server title,
description and instructions, its README says
that the server serves ai-resource routes next to the tools, and the log
lines it shows are those of 4.23.0.
contracts/openapi-server keeps its /api base path, but its README no longer
says the servers entry must have a path:
on 4.23 an entry without one serves the operations at the root.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* chore: the catalog no longer lists the tickets-out files
ai/ticket-classifier writes at runtime
The catalog was generated in a directory where the example had run, so it
listed tickets-out/*/T-*.json, which are
not in the repository; fetching them from GitHub answers 404.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* fail-well/circuit-breaker - the fallback tells a failed call from one the
open breaker did not make
On Camel 4.23 CamelCircuitBreakerResponseRejected is set in onFallback
(CAMEL-24818), so the fallback answers
"supplier failed" when the call was made and failed, and "breaker open,
supplier not called" when the open breaker
rejected it. The README explains both and points at camel get
circuit-breaker and its new fallback, rejected and
timeout counters (CAMEL-24819); the test checks both lines. Closes #81.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---------
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
README.md | 2 +-
camel-jbang-example-catalog.json | 2 +-
fail-well/README.md | 2 +-
fail-well/circuit-breaker/README.md | 37 +++++++++++++++-------
.../circuit-breaker/circuit-breaker.camel.yaml | 21 +++++++++---
fail-well/circuit-breaker/metadata.json | 2 +-
.../test/circuit-breaker.citrus.it.yaml | 7 +++-
7 files changed, 52 insertions(+), 21 deletions(-)
diff --git a/README.md b/README.md
index 192ba9e..b95b73e 100644
--- a/README.md
+++ b/README.md
@@ -84,7 +84,7 @@ Retries, a dead letter channel, and a circuit breaker in
front of a flaky servic
| Example | What you will see | Needs |
|---|---|---|
| [Error handling](fail-well/error-handling/) | The three orders go to a
payment provider: the first is charged at once, the second gets no answer twice
and is charged on the third attempt after two retries logged as warnings, and
the third is declined, logged as such and parked as a file for manual review. |
nothing |
-| [Circuit breaker](fail-well/circuit-breaker/) | A stock check calls the
supplier every second; the supplier goes down for nine calls, the breaker opens
after two failures in its window of four, answers from the fallback while open,
tries the supplier again after five seconds and closes once a call succeeds;
each line logs the breaker state. | nothing |
+| [Circuit breaker](fail-well/circuit-breaker/) | A stock check calls the
supplier every second; the supplier goes down for nine calls, the breaker opens
after two failures in its window of four, answers from the fallback without
calling the supplier while open, tries it again after five seconds and closes
once a call succeeds; each line logs the breaker state and whether the supplier
failed or was not called. | nothing |
### [Connect without a service](connect/)
diff --git a/camel-jbang-example-catalog.json b/camel-jbang-example-catalog.json
index 0b703a9..b0ee5ab 100644
--- a/camel-jbang-example-catalog.json
+++ b/camel-jbang-example-catalog.json
@@ -916,7 +916,7 @@
{
"name": "fail-well/circuit-breaker",
"title": "Circuit breaker",
- "description": "A stock check calls the supplier every second; the
supplier goes down for nine calls, the breaker opens after two failures in its
window of four, answers from the fallback while open, tries the supplier again
after five seconds and closes once a call succeeds; each line logs the breaker
state.",
+ "description": "A stock check calls the supplier every second; the
supplier goes down for nine calls, the breaker opens after two failures in its
window of four, answers from the fallback without calling the supplier while
open, tries it again after five seconds and closes once a call succeeds; each
line logs the breaker state and whether the supplier failed or was not called.",
"level": "fail-well",
"teaches": {
"components": [
diff --git a/fail-well/README.md b/fail-well/README.md
index 3612799..8e50e0f 100644
--- a/fail-well/README.md
+++ b/fail-well/README.md
@@ -6,7 +6,7 @@ Retries, a dead letter channel, and a circuit breaker in front
of a flaky servic
| Example | What you will see | Needs |
|---|---|---|
| [Error handling](error-handling/) | The three orders go to a payment
provider: the first is charged at once, the second gets no answer twice and is
charged on the third attempt after two retries logged as warnings, and the
third is declined, logged as such and parked as a file for manual review. |
nothing |
-| [Circuit breaker](circuit-breaker/) | A stock check calls the supplier every
second; the supplier goes down for nine calls, the breaker opens after two
failures in its window of four, answers from the fallback while open, tries the
supplier again after five seconds and closes once a call succeeds; each line
logs the breaker state. | nothing |
+| [Circuit breaker](circuit-breaker/) | A stock check calls the supplier every
second; the supplier goes down for nine calls, the breaker opens after two
failures in its window of four, answers from the fallback without calling the
supplier while open, tries it again after five seconds and closes once a call
succeeds; each line logs the breaker state and whether the supplier failed or
was not called. | nothing |
Start with [Error handling](error-handling/); the examples read best in the
order above, each one building on what the one before it set up.
diff --git a/fail-well/circuit-breaker/README.md
b/fail-well/circuit-breaker/README.md
index c38d159..a688f20 100644
--- a/fail-well/circuit-breaker/README.md
+++ b/fail-well/circuit-breaker/README.md
@@ -7,14 +7,18 @@ fallback without calling the supplier, and tries again after
five seconds until
## What you will see
```text
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 1 (breaker CLOSED):
CAMEL-MUG: 47 in stock at the supplier
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 2 (breaker CLOSED):
CAMEL-MUG: 35 in stock at the supplier
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 3 (breaker CLOSED):
CAMEL-MUG: 49 in stock at the supplier
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 4 (breaker CLOSED): no
answer, using last known stock
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 5 (breaker OPEN): no
answer, using last known stock
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 1 (breaker CLOSED):
CAMEL-MUG: 24 in stock at the supplier
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 2 (breaker CLOSED):
CAMEL-MUG: 44 in stock at the supplier
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 3 (breaker CLOSED):
CAMEL-MUG: 31 in stock at the supplier
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 4 (breaker CLOSED):
supplier failed, using last known stock
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 5 (breaker OPEN):
supplier failed, using last known stock
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 6 (breaker OPEN): breaker
open, supplier not called, using last known stock
...
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 14 (breaker OPEN): no
answer, using last known stock
-INFO ... circuit-breaker.camel.yaml:30 : Stock check 15 (breaker CLOSED):
CAMEL-MUG: 58 in stock at the supplier
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 10 (breaker OPEN):
supplier failed, using last known stock
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 11 (breaker OPEN):
breaker open, supplier not called, using last known stock
+...
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 14 (breaker OPEN):
breaker open, supplier not called, using last known stock
+INFO ... circuit-breaker.camel.yaml:43 : Stock check 15 (breaker CLOSED):
CAMEL-MUG: 46 in stock at the supplier
```
## Install Camel CLI
@@ -35,13 +39,18 @@ Stop it with `ctrl` + `c`; the state logged on each line is
the breaker's state
- `circuitBreaker` wraps the call to `direct:supplier` with
[Resilience4j](https://resilience4j.readme.io/):
the breaker counts the last four calls (`slidingWindowSize`) and opens when
half of them failed
(`failureRateThreshold`), which happens on check 5.
-- While open, the steps are not run at all; `onFallback` sets the body
instead. After `waitDurationInOpenState`,
- five seconds, the breaker goes half open and lets one call through
(`permittedNumberOfCallsInHalfOpenState`):
- check 10 still fails, so it opens for another five seconds; check 15
succeeds and closes it.
+- `onFallback` runs both when a call fails and when the open breaker does not
make the call at all, and
+ `CamelCircuitBreakerResponseRejected` tells them apart: `false` on checks 4
and 5, where the supplier was
+ called and failed (the failure on check 5 opens the breaker, so the state
after it is `OPEN`), and `true` on
+ check 6 onwards, where the steps are not run at all. That difference is what
the breaker buys: no call to a
+ service that is known to be down.
+- After `waitDurationInOpenState`, five seconds, the breaker goes half open
and lets one call through
+ (`permittedNumberOfCallsInHalfOpenState`): check 10 is that call and still
fails, so it opens for another five
+ seconds; check 15 succeeds and closes it.
- The supplier is a second route with `noErrorHandler`, so its exception
reaches the breaker; it is down while
the timer counter is 4 to 12.
-- `CamelCircuitBreakerState` is one of the exchange properties the breaker
sets; `CamelCircuitBreakerResponseSuccessfulExecution`
- and `CamelCircuitBreakerResponseFromFallback` are the others.
+- `CamelCircuitBreakerState` and `CamelCircuitBreakerResponseRejected` are
exchange properties the breaker sets;
+ `CamelCircuitBreakerResponseSuccessfulExecution` and
`CamelCircuitBreakerResponseFromFallback` are others.
## Build it step by step
@@ -51,9 +60,13 @@ Stop it with `ctrl` + `c`; the state logged on each line is
the breaker's state
3. Wrap the call in `circuitBreaker` with `onFallback`: no more stack traces,
but the supplier is still called
nine times because the default window is 100 calls.
4. Add the `resilience4jConfiguration` shown here and log
`CamelCircuitBreakerState` to watch it open and close.
+5. Choose the fallback body on `CamelCircuitBreakerResponseRejected`, so the
log shows which checks the breaker
+ saved from calling the supplier.
## Try changing
+- Run `camel get circuit-breaker` in another terminal while it runs: the state
and the success, failure,
+ rejected, fallback and timeout counters of the breaker.
- Set `waitDurationInOpenState` to 2000 and see the breaker try the supplier
sooner.
- Add `timeoutEnabled: true` and `timeoutDuration: 500` and a `delay` of 1000
in the supplier: a slow supplier
counts as a failure too.
diff --git a/fail-well/circuit-breaker/circuit-breaker.camel.yaml
b/fail-well/circuit-breaker/circuit-breaker.camel.yaml
index 90d73bf..87f6ef9 100644
--- a/fail-well/circuit-breaker/circuit-breaker.camel.yaml
+++ b/fail-well/circuit-breaker/circuit-breaker.camel.yaml
@@ -23,10 +23,23 @@
uri: direct:supplier
onFallback:
steps:
- - setBody:
- expression:
- simple:
- expression: "no answer, using last known stock"
+ # rejected: the breaker is open, so the supplier was not
called at all
+ - choice:
+ when:
+ - expression:
+ simple:
+ expression:
"${exchangeProperty.CamelCircuitBreakerResponseRejected} == true"
+ steps:
+ - setBody:
+ expression:
+ simple:
+ expression: "breaker open, supplier not
called, using last known stock"
+ otherwise:
+ steps:
+ - setBody:
+ expression:
+ simple:
+ expression: "supplier failed, using last known
stock"
- log:
message: "Stock check ${header.CamelTimerCounter} (breaker
${exchangeProperty.CamelCircuitBreakerState}): ${body}"
diff --git a/fail-well/circuit-breaker/metadata.json
b/fail-well/circuit-breaker/metadata.json
index 21bb5fc..07753a1 100644
--- a/fail-well/circuit-breaker/metadata.json
+++ b/fail-well/circuit-breaker/metadata.json
@@ -1,6 +1,6 @@
{
"title": "Circuit breaker",
- "description": "A stock check calls the supplier every second; the
supplier goes down for nine calls, the breaker opens after two failures in its
window of four, answers from the fallback while open, tries the supplier again
after five seconds and closes once a call succeeds; each line logs the breaker
state.",
+ "description": "A stock check calls the supplier every second; the
supplier goes down for nine calls, the breaker opens after two failures in its
window of four, answers from the fallback without calling the supplier while
open, tries it again after five seconds and closes once a call succeeds; each
line logs the breaker state and whether the supplier failed or was not called.",
"level": "fail-well",
"order": 2,
"teaches": {
diff --git a/fail-well/circuit-breaker/test/circuit-breaker.citrus.it.yaml
b/fail-well/circuit-breaker/test/circuit-breaker.citrus.it.yaml
index 8821505..068b417 100644
--- a/fail-well/circuit-breaker/test/circuit-breaker.citrus.it.yaml
+++ b/fail-well/circuit-breaker/test/circuit-breaker.citrus.it.yaml
@@ -13,7 +13,12 @@ actions:
jbang:
verify:
integration: "circuit-breaker"
- logMessage: "(breaker OPEN): no answer"
+ logMessage: "(breaker OPEN): supplier failed, using last known stock"
+ - camel:
+ jbang:
+ verify:
+ integration: "circuit-breaker"
+ logMessage: "(breaker OPEN): breaker open, supplier not called,
using last known stock"
- camel:
jbang:
verify: