This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch chaos-example in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
commit 579f08d006902e0cd962dbcd31f44e92dbdc2a71 Author: Claus Ibsen <[email protected]> AuthorDate: Sun Nov 8 12:26:50 2026 +0100 fail-well/chaos - chaos rules break the shop on purpose, and a scenario gates the build The shop charges an order every second at a payment service over HTTP and hands it to shipping over seda. Chaos rules added while it runs make the payment service answer 503, go down for 10 seconds, fail shipping and fail the dead letter channel too, and the README says what each one shows in the log, in camel get chaos and in the Camel TUI. chaos/payment-outage.chaos replays a bad half minute with a seed and three expectations; with --max-seconds its verdict is the exit code. Needs Camel 4.24 (camel-chaos is new there), so the CI entry comes with the move of the examples to Camel 4.24.0. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .gitignore | 2 +- README.md | 3 +- camel-jbang-example-catalog.json | 48 +++++++ fail-well/README.md | 3 +- fail-well/chaos/README.md | 220 +++++++++++++++++++++++++++++ fail-well/chaos/application.properties | 26 ++++ fail-well/chaos/chaos.camel.yaml | 63 +++++++++ fail-well/chaos/chaos/payment-outage.chaos | 11 ++ fail-well/chaos/metadata.json | 38 +++++ fail-well/chaos/parked.camel.yaml | 18 +++ fail-well/chaos/test/chaos.citrus.it.yaml | 42 ++++++ generate-catalog.sh | 2 +- 12 files changed, 472 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index db62dd3..c16bf84 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ camel-runner.jar .citrus-jbang/ # written by the csv-to-json example at runtime outbox/ -# written by the error-handling example at runtime +# written by the error-handling and chaos examples at runtime parked/ # written by the file-processing example at runtime inbox/ diff --git a/README.md b/README.md index 9822da3..60cbc06 100644 --- a/README.md +++ b/README.md @@ -80,12 +80,13 @@ The routing patterns: content-based router, splitter, aggregator, filter and mul ### [Fail well](fail-well/) -Retries, a dead letter channel, and a circuit breaker in front of a flaky service. +Retries, a dead letter channel, and a circuit breaker in front of a flaky service, then chaos rules that break the routes on purpose to prove it. | 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 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 | +| [Chaos](fail-well/chaos/) | The shop charges an order every second at a payment service over HTTP and hands it to shipping; chaos rules added while it runs make the payment service answer 503, go down for 10 seconds and fail shipping, the retries recover most orders and the dead letter channel parks the rest, and a scenario file replays a bad half minute with a seed and ends with the verdict PASS as its exit code. | Camel 4.24 or newer | ### [Connect without a service](connect/) diff --git a/camel-jbang-example-catalog.json b/camel-jbang-example-catalog.json index 261b291..e9eaecc 100644 --- a/camel-jbang-example-catalog.json +++ b/camel-jbang-example-catalog.json @@ -913,6 +913,54 @@ ], "order": 1 }, + { + "name": "fail-well/chaos", + "title": "Chaos", + "description": "The shop charges an order every second at a payment service over HTTP and hands it to shipping; chaos rules added while it runs make the payment service answer 503, go down for 10 seconds and fail shipping, the retries recover most orders and the dead letter channel parks the rest, and a scenario file replays a bad half minute with a seed and ends with the verdict PASS as its exit code.", + "level": "fail-well", + "teaches": { + "components": [ + "timer", + "http", + "platform-http", + "seda", + "direct", + "file", + "log" + ], + "eips": [ + "errorHandler", + "deadLetterChannel", + "redeliveryPolicy", + "setVariable", + "setBody", + "removeHeaders" + ], + "languages": [ + "simple" + ] + }, + "tags": [ + "shop", + "chaos", + "resilience", + "error-handling", + "dead-letter", + "tui" + ], + "bundled": true, + "requiresDocker": false, + "hasCitrusTests": true, + "files": [ + "README.md", + "application.properties", + "chaos.camel.yaml", + "chaos/payment-outage.chaos", + "parked.camel.yaml" + ], + "needs": "Camel 4.24 or newer", + "order": 3 + }, { "name": "fail-well/circuit-breaker", "title": "Circuit breaker", diff --git a/fail-well/README.md b/fail-well/README.md index 8e50e0f..43999b0 100644 --- a/fail-well/README.md +++ b/fail-well/README.md @@ -1,12 +1,13 @@ # Fail well <!-- group:start --> -Retries, a dead letter channel, and a circuit breaker in front of a flaky service. +Retries, a dead letter channel, and a circuit breaker in front of a flaky service, then chaos rules that break the routes on purpose to prove it. | 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 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 | +| [Chaos](chaos/) | The shop charges an order every second at a payment service over HTTP and hands it to shipping; chaos rules added while it runs make the payment service answer 503, go down for 10 seconds and fail shipping, the retries recover most orders and the dead letter channel parks the rest, and a scenario file replays a bad half minute with a seed and ends with the verdict PASS as its exit code. | Camel 4.24 or newer | 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/chaos/README.md b/fail-well/chaos/README.md new file mode 100644 index 0000000..d821cb9 --- /dev/null +++ b/fail-well/chaos/README.md @@ -0,0 +1,220 @@ +# Chaos + +The shop takes an order every second, charges it at the payment service over HTTP and hands it to shipping. Nothing +in the routes fails on its own: you break things on purpose while it runs, with chaos rules, and watch whether the +retries, the dead letter channel and the hand-off to shipping do what they should. A scenario file then replays a bad +half minute at the payment service, and its verdict is the exit code, so it can gate a build. + +Chaos is new in Camel 4.24 ([camel-chaos](https://camel.apache.org/components/next/others/chaos.html)), and it is for +development only: it is on with profile `dev`, the default of `camel run`, and never with profile `prod`. + +## What you will see + +Without rules, every order is charged and shipped: + +```text +INFO ... chaos.camel.yaml:35 : Order ORD-1 charged: {"payment": "PAY-30456"} +INFO ... chaos.camel.yaml:45 : Order ORD-1 shipped +``` + +With a rule that makes the payment service answer 503 to one call in four, the error handler retries, and the order +is charged on the second or third attempt; the few that fail three times in a row are parked: + +```text +WARN ... DeadLetterChannel : Failed delivery for (...) at orders[to1] chaos.camel.yaml:33. On delivery attempt: 0 caught: org.apache.camel.http.base.HttpOperationFailedException: HTTP operation failed invoking http://localhost:8080/payment with statusCode: 503 +INFO ... chaos.camel.yaml:35 : Order ORD-45 charged: {"payment": "PAY-27475"} +... +WARN ... DeadLetterChannel : Failed delivery for (...) at orders[to1] chaos.camel.yaml:33. On delivery attempt: 2 caught: org.apache.camel.http.base.HttpOperationFailedException: HTTP operation failed invoking http://localhost:8080/payment with statusCode: 503 +INFO ... parked.camel.yaml:12 : Order ORD-47 parked for manual review: HTTP operation failed invoking http://localhost:8080/payment with statusCode: 503 +``` + +and the scenario ends with its verdict: + +```text +Chaos verdict: PASS (3 expectations met) +``` + +## Install Camel CLI + +Install [JBang](https://www.jbang.dev/download/) and the Camel CLI as described in the +[root README](../../README.md#install-the-camel-cli); `camel --version` confirms the install. This example needs +Camel 4.24 or newer. + +## Run it + +```shell +camel run * --chaos +``` + +`--chaos` adds camel-chaos to the integration. Leave it running, and give the commands below in another terminal. +Stop it with `ctrl` + `c`: when rules were added, the chaos report is logged as the application stops. + +## How it works + +- `chaos.camel.yaml` is the shop. Route `orders` creates an order every second (`order.period`), charges it with a + `POST` to `http://localhost:8080/payment` and sends it to `seda:shipping`, a hand-off to route `shipping` that does + not wait for it. The `errorHandler` of the file, a dead letter channel to `direct:parked` with two retries half a + second apart, applies to both routes. Route `payment` is the payment service, a `platform-http` route of the same + application, so the example needs nothing else to run; it has `noErrorHandler`, so a failure there is the caller's + to deal with. +- `parked.camel.yaml` writes an order that could not be charged or shipped to `parked/<order id>.json`. It is in a + file of its own, with an error handler of its own, as in [Error handling](../error-handling/). +- `chaos/payment-outage.chaos` is a scenario: chaos rules in a file, one per line, with a seed, so the same + experiment runs again. +- A chaos rule is one line: what it targets (an `endpoint=`, a `route=`, a `component=` or a `step=`), the fault + (`error`, `delay`, `drop`, `duplicate`, `corrupt`, `stall`), how often (`rate=`) and for how long (`until=`), and + optionally what must still hold (`expect=`). A rule ends by itself, at the latest after 10 minutes. + +## Break it step by step + +1. Ask the integration what is worth trying. Chaos reads the routes and suggests rules, the most worth trying first: + + ```shell + camel cmd chaos-suggest + ``` + + ```text + Chaos rules to try, the most worth trying first: + + 1. route orders: Returns an error (HTTP status) (errors) + an HTTP call to http://localhost:8080/payment without a circuit breaker: what does the route do when the service answers 503? + camel cmd chaos-add --rule='endpoint=http://localhost:8080/payment error status=503 rate=0.25 until=10m' + + 2. route orders: The route after a hand-off fails (lost) + route orders hands the message to route shipping over seda and does not wait: when shipping fails, is the message lost? + camel cmd chaos-add --rule='route=shipping error rate=0.2 until=2m expect=orders->shipping:lost<=0 within=3m' + ... + ``` + +2. Make the payment service answer 503 to one call in four, the first suggestion: + + ```shell + camel cmd chaos-add --rule='endpoint=http://localhost:8080/payment error status=503 rate=0.25 until=10m' + ``` + + ```text + Added chaos rule r1: fail 25% of the sends to http://localhost:8080/payment with HTTP status 503, for 10 minutes + ``` + + The log shows the retries, and once in a while an order parked in `parked/`. `camel get chaos` shows how the rule + is doing: its observed rate, and the effect of its faults (`60 recovered, 4 handled`: recovered by a retry, or + handled by the dead letter channel). Before you add a rule, `chaos-add --dry-run` says what it would hit, without + adding it. + +3. Take the payment service down for 10 seconds. A suspended `platform-http` route answers 503, so the HTTP client + retries once by itself, then the error handler twice, and the orders of those seconds are parked: + + ```shell + camel cmd chaos-add --rule='route=payment stall duration=10s' + ``` + +4. Fail route `shipping` one message in five, with an expectation that no order charged is lost on the way: + + ```shell + camel cmd chaos-add --rule='route=shipping error rate=0.2 until=1m expect=orders->shipping:lost<=0 within=90s' + ``` + + The dead letter channel of the file covers `shipping` too: a retry ships the order, or else it is parked, so + nothing is lost; when the expectation is decided, `camel get chaos --report` says `met, 0 lost`. + +5. Now the last safety net: fail the dead letter channel too, while the payment service is down: + + ```shell + camel cmd chaos-add --rule='endpoint=http://localhost:8080/payment error status=503 until=10s' --rule='endpoint=direct:parked error at=dlq until=10s' + ``` + + The orders of those seconds are neither charged nor parked. The log has only a WARN for each + (`New exception occurred during processing by the DeadLetterChannel[direct:parked]`), and the report counts them + as lost, with the verdict FAIL: + + ```text + chaos (pid 58473): FAIL + lost: 9 + FAIL: rule r5 failed or dropped 9 sends of the dead letter channel, which handled them without delivering them: 9 lost + ``` + +6. Read the report: per rule its matches, faults and effect, per route the exchanges completed, failed, handled by + the error handler and redelivered, the recent faults, and per route which kinds of fault have been tried and which + not yet: + + ```shell + camel get chaos --report + ``` + + `camel cmd chaos-clear --reset` removes the rules and starts the report again, for the next experiment. + +## Watch it in the Camel TUI + +```shell +camel tui +``` + +- The chaos screen (`Chaos` under `0 More`) lists the rules with their live rate, faults per second and effect, and + the report of the selected rule. `a` adds a rule from a form of known rules, `w` lists what to try (the + suggestions), `e` changes, `x` removes and `c` clears a rule, `R` starts a new experiment, and `s` saves the rules + as a scenario file. +- `4 Timeline` shows per second the faults of each rule, and what each route did: completed, recovered, handled, + failed, and the seconds route `payment` was down. +- `t` on a rule asks the AI panel to make a test of it, with the seed, so it fails the same messages again. + +## A scenario as a gate + +`chaos/payment-outage.chaos` is a bad half minute at the payment service, with an expectation on each rule: + +```text +seed=42 + +# one call in four fails for 20 seconds: the redeliveries recover them, and the shop still takes 20 orders +endpoint=http://localhost:8080/payment error status=503 rate=0.25 until=20s expect=orders:20 within=25s +# shipping fails one message in five for 15 seconds: its redeliveries still ship every order charged +route=shipping error rate=0.2 until=15s expect=orders->shipping:lost<=0 within=20s +# then the payment service is down for 10 seconds: every order is charged or parked, none fails +route=payment stall duration=10s from=+20s expect=orders:failed<=0 within=15s +``` + +Run the shop with it for 45 seconds: + +```shell +camel run * --chaos=chaos/payment-outage.chaos --max-seconds=45 +``` + +The report is logged when it stops, and the verdict is the exit code: 0 for PASS, 1 for FAIL, so a build can run it. +`seed=42` makes the rules fail the same calls each time. `camel cmd chaos-load chaos/payment-outage.chaos` loads the +scenario into a shop that runs already. + +## Try changing + +- Add `endpoint=direct:parked error at=dlq until=10s from=+20s` to the scenario: the parked orders are lost, and the + verdict is FAIL, exit code 1. +- Set `maximumRedeliveries` to 0 in `chaos.camel.yaml` and run the scenario: the first rule parks the orders instead + of recovering them. +- Wrap the payment call in a `circuitBreaker` (see [Circuit breaker](../circuit-breaker/)) and ask + `camel cmd chaos-suggest` again: it suggests an outage, to see the breaker open and the fallback answer. +- Slow the payment service down, with an expectation on the time of an order: + `endpoint=http://localhost:8080/payment delay duration=100ms..2s rate=0.5 until=1m expect=orders:meanTime<=1s within=90s`. +- Make the payment service charge twice: `endpoint=http://localhost:8080/payment duplicate rate=0.1` sends one call + in ten again. Is your payment service idempotent? +- Fail only some orders: `when="${variable.orderId} ends with '7'"` on any rule fires only for the orders whose + number ends with 7. + +## Integration testing + +The example comes with a test in the [Citrus](https://citrusframework.org/) YAML DSL, +`test/chaos.citrus.it.yaml`, which the Camel CLI runs: + +```shell +camel test run test/chaos.citrus.it.yaml +``` + +The test starts the shop with the scenario and verifies that the payment service goes down and comes back, and that +orders are still shipped. + +## Help and contributions + +If you hit any problem using Camel or have some feedback, then please +[let us know](https://camel.apache.org/community/support/). + +We also love contributors, so +[get involved](https://camel.apache.org/community/contributing/) :-) + +The Camel riders! diff --git a/fail-well/chaos/application.properties b/fail-well/chaos/application.properties new file mode 100644 index 0000000..ae171ea --- /dev/null +++ b/fail-well/chaos/application.properties @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + + +# a new order every second +order.period=1000 +# the port of the payment service, which the shop calls on localhost +camel.server.port=8080 + +# the folder the parked route writes holds data, not sources: with camel run --source-dir in dev mode, +# a change in it does not reload the routes +camel.jbang.dataDir=parked diff --git a/fail-well/chaos/chaos.camel.yaml b/fail-well/chaos/chaos.camel.yaml new file mode 100644 index 0000000..2d2ce5e --- /dev/null +++ b/fail-well/chaos/chaos.camel.yaml @@ -0,0 +1,63 @@ +# The web shop takes an order every second, charges it at the payment service over HTTP and hands it to +# shipping. Nothing here fails on its own: the faults come from the chaos rules you add while it runs. +- errorHandler: + deadLetterChannel: + deadLetterUri: direct:parked + redeliveryPolicy: + maximumRedeliveries: 2 + redeliveryDelay: 500 + retryAttemptedLogLevel: WARN + +- route: + id: orders + from: + uri: timer + parameters: + timerName: orders + period: "{{order.period}}" + includeMetadata: true + steps: + - setVariable: + name: orderId + expression: + simple: + expression: "ORD-${header.CamelTimerCounter}" + - removeHeaders: + pattern: "Camel*" + - setBody: + expression: + simple: + expression: |- + {"orderId": "${variable.orderId}", "customer": "C-${random(100,999)}", "country": "DK", + "lines": [{"sku": "CAMEL-TSHIRT", "qty": ${random(1,3)}, "price": 19.95}], "status": "paid"} + - to: + uri: "http://localhost:{{camel.server.port}}/payment" + - log: + message: "Order ${variable.orderId} charged: ${body}" + - to: + uri: seda:shipping + +- route: + id: shipping + from: + uri: seda:shipping + steps: + - log: + message: "Order ${variable.orderId} shipped" + +# The payment service the shop calls. In a real shop it runs elsewhere; here it is a route of +# the same application, so the example needs nothing else to run. +- route: + id: payment + errorHandler: + noErrorHandler: {} + from: + uri: platform-http + parameters: + path: /payment + httpMethodRestrict: POST + steps: + - setBody: + expression: + simple: + expression: "{\"payment\": \"PAY-${random(10000,99999)}\"}" diff --git a/fail-well/chaos/chaos/payment-outage.chaos b/fail-well/chaos/chaos/payment-outage.chaos new file mode 100644 index 0000000..28ba946 --- /dev/null +++ b/fail-well/chaos/chaos/payment-outage.chaos @@ -0,0 +1,11 @@ +# A bad half minute at the payment service: for 20 seconds it answers 503 to one call in four, +# then it is down for 10 seconds. The shop must keep taking orders, ship every order it charged, +# and park the orders it could not charge instead of losing them. +seed=42 + +# one call in four fails for 20 seconds: the redeliveries recover them, and the shop still takes 20 orders +endpoint=http://localhost:8080/payment error status=503 rate=0.25 until=20s expect=orders:20 within=25s +# shipping fails one message in five for 15 seconds: its redeliveries still ship every order charged +route=shipping error rate=0.2 until=15s expect=orders->shipping:lost<=0 within=20s +# then the payment service is down for 10 seconds: every order is charged or parked, none fails +route=payment stall duration=10s from=+20s expect=orders:failed<=0 within=15s diff --git a/fail-well/chaos/metadata.json b/fail-well/chaos/metadata.json new file mode 100644 index 0000000..304bff1 --- /dev/null +++ b/fail-well/chaos/metadata.json @@ -0,0 +1,38 @@ +{ + "title": "Chaos", + "description": "The shop charges an order every second at a payment service over HTTP and hands it to shipping; chaos rules added while it runs make the payment service answer 503, go down for 10 seconds and fail shipping, the retries recover most orders and the dead letter channel parks the rest, and a scenario file replays a bad half minute with a seed and ends with the verdict PASS as its exit code.", + "level": "fail-well", + "order": 3, + "teaches": { + "components": [ + "timer", + "http", + "platform-http", + "seda", + "direct", + "file", + "log" + ], + "eips": [ + "errorHandler", + "deadLetterChannel", + "redeliveryPolicy", + "setVariable", + "setBody", + "removeHeaders" + ], + "languages": [ + "simple" + ] + }, + "tags": [ + "shop", + "chaos", + "resilience", + "error-handling", + "dead-letter", + "tui" + ], + "needs": "Camel 4.24 or newer", + "bundled": true +} diff --git a/fail-well/chaos/parked.camel.yaml b/fail-well/chaos/parked.camel.yaml new file mode 100644 index 0000000..f1574b2 --- /dev/null +++ b/fail-well/chaos/parked.camel.yaml @@ -0,0 +1,18 @@ +# The orders the shop could not charge or ship are written as files for someone to look at. +# The route has an error handler of its own, so a failure in parking an order is logged +# instead of being sent back here. +- route: + id: parked + errorHandler: + deadLetterChannel: + deadLetterUri: "log:parking-failed?level=ERROR" + from: + uri: direct:parked + steps: + - log: + message: "Order ${variable.orderId} parked for manual review: ${exception.message}" + - to: + uri: file + parameters: + directoryName: parked + fileName: "${variable.orderId}.json" diff --git a/fail-well/chaos/test/chaos.citrus.it.yaml b/fail-well/chaos/test/chaos.citrus.it.yaml new file mode 100644 index 0000000..7519f7a --- /dev/null +++ b/fail-well/chaos/test/chaos.citrus.it.yaml @@ -0,0 +1,42 @@ +name: chaos-test +description: The chaos example runs its scenario, takes the payment service down and brings it back, and still ships orders +actions: + - camel: + cli: + run: + integration: + name: "chaos" + file: "../chaos.camel.yaml" + systemProperties: + file: "../application.properties" + resources: + - "parked.camel.yaml" + - "chaos/payment-outage.chaos" + args: + - "--chaos=chaos/payment-outage.chaos" + - camel: + cli: + verify: + integration: "chaos" + logMessage: "Order ORD-1 shipped" + - camel: + cli: + verify: + integration: "chaos" + logMessage: "Chaos rule r3 suspended route payment for 10s" + maxAttempts: 30 + delayBetweenAttempts: 1000 + - camel: + cli: + verify: + integration: "chaos" + logMessage: "Chaos rule r3 resumed route payment" + maxAttempts: 20 + delayBetweenAttempts: 1000 + - camel: + cli: + verify: + integration: "chaos" + logMessage: "Order ORD-35 shipped" + maxAttempts: 20 + delayBetweenAttempts: 1000 diff --git a/generate-catalog.sh b/generate-catalog.sh index 9fa9660..c0c3adb 100755 --- a/generate-catalog.sh +++ b/generate-catalog.sh @@ -146,7 +146,7 @@ GROUPS = [ ("run", "Run", "Running Camel: timers and cron schedules, a bean in a route, properties and profiles."), ("transform", "Transform and map", "JSON, XML and CSV in and out, field-by-field mapping, Groovy and XSLT."), ("route", "Route", "The routing patterns: content-based router, splitter, aggregator, filter and multicast."), - ("fail-well", "Fail well", "Retries, a dead letter channel, and a circuit breaker in front of a flaky service."), + ("fail-well", "Fail well", "Retries, a dead letter channel, and a circuit breaker in front of a flaky service, then chaos rules that break the routes on purpose to prove it."), ("connect", "Connect without a service", "Files, an HTTP client and a REST server; everything runs inside the example."), ("connect-service", "Connect to one service", "SQL, JMS, MQTT, Kafka and FTP against a service the Camel CLI starts for you with `camel infra run`."), ("contracts", "Contracts and security", "An OpenAPI contract served and called, and an API protected by Keycloak."),
