diff --git a/saga/README.adoc b/saga/README.adoc index 950c235b..4152370a 100644 --- a/saga/README.adoc +++ b/saga/README.adoc @@ -8,12 +8,16 @@ and other general information. === How it works -There are 4 services as participants of the Saga: +There are 5 modules in this example: -- payment-service: it emulates a real payment transaction and it will be used by both flight-service and train-service +**Service Modules:** +- app: is the starting point and it emulates a user that starts the transaction to buy both flight and train tickets - flight-service: it emulates the booking of a flight ticket and it uses the payment-service to execute a payment transaction - train-service: it emulates the reservation of a train seat and it uses the payment-service to execute a payment transaction -- app: is the starting point and it emulates a user that starts the transaction to buy both flight and train tickets +- payment-service: it emulates a real payment transaction and it will be used by both flight-service and train-service + +**Test Module:** +- integration-tests: contains automated integration tests using Testcontainers for Docker-based testing The starting point is a REST endpoint that creates a request for a new reservation and there is 15% probability that the payment service fails. @@ -101,6 +105,21 @@ Transaction http://localhost:8080/lra-coordinator/0_ffff7f000001_8aad_62d16f11_7 ---- +=== Running Tests + +The integration tests use Testcontainers to automatically start Artemis and LRA Coordinator in Docker. + +[source,shell] +---- +# Ensure Docker is running +docker ps + +# Run integration tests +mvn clean test -pl saga-integration-tests +---- + +See link:saga-integration-tests/README.md[saga-integration-tests/README.md] for more details. + === Package and run the application Once you are done with developing you may want to package and run the application. diff --git a/saga/pom.xml b/saga/pom.xml index 711d5e18..f2c9dbf0 100644 --- a/saga/pom.xml +++ b/saga/pom.xml @@ -44,6 +44,7 @@ 2.29.0 1.13.0 + 3.0.1 5.0.0 3.15.0 3.5.0 @@ -56,6 +57,7 @@ saga-flight-service saga-payment-service saga-train-service + saga-integration-tests @@ -87,6 +89,10 @@ org.apache.camel.quarkus camel-quarkus-core + + org.apache.camel.quarkus + camel-quarkus-bean + org.apache.camel.quarkus camel-quarkus-direct @@ -104,13 +110,6 @@ quarkus-artemis-jms ${quarkiverse-artemis.version} - - - - io.quarkus - quarkus-junit - test - @@ -182,6 +181,12 @@ ${maven-jar-plugin.version} + + io.smallrye + jandex-maven-plugin + ${jandex-maven-plugin.version} + + com.mycila license-maven-plugin diff --git a/saga/saga-app/pom.xml b/saga/saga-app/pom.xml index 0e71248c..d1c0ed07 100644 --- a/saga/saga-app/pom.xml +++ b/saga/saga-app/pom.xml @@ -31,4 +31,21 @@ Camel Quarkus :: Examples :: Saga :: App Main application starting SAGA + + + + io.smallrye + jandex-maven-plugin + + + make-index + + jandex + + + + + + + diff --git a/saga/saga-app/src/main/java/org/apache/camel/example/saga/SagaRoute.java b/saga/saga-app/src/main/java/org/apache/camel/example/saga/SagaRoute.java index d1985326..6f31b1df 100644 --- a/saga/saga-app/src/main/java/org/apache/camel/example/saga/SagaRoute.java +++ b/saga/saga-app/src/main/java/org/apache/camel/example/saga/SagaRoute.java @@ -16,9 +16,11 @@ */ package org.apache.camel.example.saga; +import jakarta.enterprise.context.ApplicationScoped; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.model.rest.RestParamType; +@ApplicationScoped public class SagaRoute extends RouteBuilder { @Override @@ -33,12 +35,15 @@ public void configure() throws Exception { .compensation("direct:cancelOrder") .log("Executing saga #${header.id} with LRA ${header.Long-Running-Action}") .setHeader("payFor", constant("train")) + // Request timeout prevents indefinite waits during service failures or slow responses .to("jms:queue:{{example.services.train}}?exchangePattern=InOut" + - "&replyTo={{example.services.train}}.reply") + "&replyTo={{example.services.train}}.reply" + + "&requestTimeout=30000") .log("train seat reserved for saga #${header.id} with payment transaction: ${body}") .setHeader("payFor", constant("flight")) .to("jms:queue:{{example.services.flight}}?exchangePattern=InOut" + - "&replyTo={{example.services.flight}}.reply") + "&replyTo={{example.services.flight}}.reply" + + "&requestTimeout=30000") .log("flight booked for saga #${header.id} with payment transaction: ${body}") .setBody(header("Long-Running-Action")) .end(); diff --git a/saga/saga-flight-service/pom.xml b/saga/saga-flight-service/pom.xml index 79f59c2e..85b18515 100644 --- a/saga/saga-flight-service/pom.xml +++ b/saga/saga-flight-service/pom.xml @@ -31,4 +31,21 @@ Camel Quarkus :: Examples :: Saga :: Flight Service Flight Service + + + + io.smallrye + jandex-maven-plugin + + + make-index + + jandex + + + + + + + diff --git a/saga/saga-flight-service/src/main/java/org/apache/camel/example/saga/FlightRoute.java b/saga/saga-flight-service/src/main/java/org/apache/camel/example/saga/FlightRoute.java index 8e0bd7e0..04694f18 100644 --- a/saga/saga-flight-service/src/main/java/org/apache/camel/example/saga/FlightRoute.java +++ b/saga/saga-flight-service/src/main/java/org/apache/camel/example/saga/FlightRoute.java @@ -16,9 +16,11 @@ */ package org.apache.camel.example.saga; +import jakarta.enterprise.context.ApplicationScoped; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.model.SagaPropagation; +@ApplicationScoped public class FlightRoute extends RouteBuilder { @Override @@ -27,14 +29,16 @@ public void configure() throws Exception { .saga() .propagation(SagaPropagation.MANDATORY) .option("id", header("id")) - .compensation("direct:cancelPurchase") + .compensation("direct:cancelFlightPurchase") .log("Buying flight #${header.id}") + // Request timeout prevents indefinite waits during payment service failures .to("jms:queue:{{example.services.payment}}?exchangePattern=InOut" + - "&replyTo={{example.services.payment}}.flight.reply") + "&replyTo={{example.services.payment}}.flight.reply" + + "&requestTimeout=30000") .log("Payment for flight #${header.id} done with transaction ${body}") .end(); - from("direct:cancelPurchase") + from("direct:cancelFlightPurchase") .log("Flight purchase #${header.id} has been cancelled due to payment failure"); } diff --git a/saga/saga-integration-tests/README.md b/saga/saga-integration-tests/README.md new file mode 100644 index 00000000..86a3f139 --- /dev/null +++ b/saga/saga-integration-tests/README.md @@ -0,0 +1,126 @@ +# Saga Integration Tests + +Integration tests for the Camel Quarkus Saga example demonstrating distributed transaction coordination using the LRA (Long Running Action) pattern with JMS messaging. + +## Overview + +This module tests the Saga example by running all services (train, flight, payment) in a single JVM with Testcontainers managing external dependencies (LRA Coordinator and Artemis broker). + +## Test Coverage + +The test suite verifies: + +- **LRA Integration:** Saga coordination with LRA coordinator +- **JMS Messaging:** Request-reply pattern over Artemis queues +- **Service Participation:** Train, flight, and payment service coordination +- **Compensation Flow:** Rollback when failures occur (15% random failure rate) +- **End-to-End Flow:** Complete saga orchestration + +### Test Case + +`testSagaWithLRAAndRandomOutcomes()` - Comprehensive end-to-end test that verifies the complete saga flow including LRA coordination, all service participation (train, flight, payment), and validates both success and compensation scenarios. Since the payment service has a 15% random failure rate, the test accepts either outcome as valid. + +## Running Tests + +### Prerequisites + +- Java 17+ +- Maven 3.9+ +- Docker (for Testcontainers) + +### Execute Tests + +```bash +# Run all tests +mvn clean test -pl saga-integration-tests + +# Run specific test +mvn test -pl saga-integration-tests -Dtest=SagaBasicTest#testCompleteSagaFlow + +# Native mode +mvn clean verify -Pnative -pl saga-integration-tests +``` + +## Infrastructure + +**Testcontainers manages:** + +- **LRA Coordinator** (`quay.io/jbosstm/lra-coordinator:latest`) - Distributed saga coordination +- **Artemis Broker** (`quay.io/artemiscloud/activemq-artemis-broker:latest`) - JMS messaging + +Both containers run on a shared Docker network with proper wait strategies. + +## Configuration + +### Test Settings (`src/test/resources/application.yml`) + +Key configuration settings: + +```yaml +quarkus: + http: + port: 8084 + log: + file: + enable: true + path: target/quarkus.log + category: + "org.apache.camel": DEBUG + "org.apache.camel.saga": DEBUG + "org.apache.camel.component.lra": DEBUG + +camel: + lra: + enabled: true + # coordinator-url and local-participant-url are set by SagaTestResource + component: + jms: + test-connection-on-startup: true + concurrent-consumers: 5 +``` + +Dynamic configuration (Artemis URL, LRA coordinator URL) is injected by `SagaTestResource` at test runtime. + +## Saga Flow + +``` +POST /api/saga?id=1 + → SagaRoute creates LRA transaction + → Sends to jms:queue:saga-train-service + → TrainRoute processes and sends to jms:queue:saga-payment-service + → PaymentRoute completes payment + → Sends to jms:queue:saga-flight-service + → FlightRoute processes and sends to jms:queue:saga-payment-service + → PaymentRoute completes payment + → LRA Coordinator commits saga + → Returns LRA ID +``` + +## Troubleshooting + +### Tests Fail with "Connection Refused" + +Docker not running. Start Docker and verify: +```bash +docker ps +``` + +### Tests Timeout + +Increase timeout in tests: +```java +await().atMost(30, TimeUnit.SECONDS) // Instead of 10-15 +``` + +### View Container Logs + +```bash +docker ps # Find container ID +docker logs +``` + +## Related Links + +- [Camel Saga EIP](https://camel.apache.org/components/latest/eips/saga-eip.html) +- [Camel LRA Component](https://camel.apache.org/components/latest/lra-component.html) +- [Issue #6195](https://github.com/apache/camel-quarkus/issues/6195) diff --git a/saga/saga-integration-tests/pom.xml b/saga/saga-integration-tests/pom.xml new file mode 100644 index 00000000..522240e0 --- /dev/null +++ b/saga/saga-integration-tests/pom.xml @@ -0,0 +1,148 @@ + + + + + 4.0.0 + + + camel-quarkus-examples-saga + org.apache.camel.quarkus.examples + 3.32.0 + + + camel-quarkus-example-saga-integration-tests + Camel Quarkus :: Examples :: Saga :: Integration Tests + Integration tests for Saga example + + + + + org.apache.camel.quarkus.examples + camel-quarkus-example-saga-app + ${project.version} + + + org.apache.camel.quarkus.examples + camel-quarkus-example-saga-flight + ${project.version} + + + org.apache.camel.quarkus.examples + camel-quarkus-example-saga-train + ${project.version} + + + org.apache.camel.quarkus.examples + camel-quarkus-example-saga-payment + ${project.version} + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + org.testcontainers + testcontainers + test + + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + + + build + + build + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + org.jboss.logmanager.LogManager + + + + + + + + + native + + + native + + + + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + + + skip-testcontainers-tests + + + skip-testcontainers-tests + + + + true + + + + + diff --git a/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicIT.java b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicIT.java new file mode 100644 index 00000000..c27b6790 --- /dev/null +++ b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicIT.java @@ -0,0 +1,28 @@ +/* + * 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. + */ +package org.apache.camel.example.saga; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +/** + * Integration test for native mode compilation. + * Extends SagaBasicTest to run the same tests against native executable. + */ +@QuarkusIntegrationTest +public class SagaBasicIT extends SagaBasicTest { + // Runs all tests from SagaBasicTest in native mode +} diff --git a/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicTest.java b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicTest.java new file mode 100644 index 00000000..53420cd9 --- /dev/null +++ b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicTest.java @@ -0,0 +1,101 @@ +/* + * 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. + */ +package org.apache.camel.example.saga; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import org.junit.jupiter.api.Test; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Basic integration tests for Saga example. + * Tests verify saga orchestration, service participation, routing, and compensation. + * Note: Payment service has 15% random failure rate to test compensation scenarios. + */ +@QuarkusTest +@QuarkusTestResource(SagaTestResource.class) +public class SagaBasicTest { + + private static final String LOG_FILE = "target/quarkus.log"; + + /** + * Test saga orchestration with LRA - accepts both success and compensation outcomes. + * Payment service has 15% random failure rate, so either scenario is valid. + */ + @Test + public void testSagaWithLRAAndRandomOutcomes() throws Exception { + // Trigger saga asynchronously (may timeout on payment failure, which is expected) + CompletableFuture.runAsync(() -> { + try { + RestAssured.given() + .queryParam("id", 1) + .post("/api/saga"); + } catch (Exception e) { + // Expected - request may timeout on payment failure + } + }); + + // Wait for saga to start and process fully + // In native mode, we need to wait longer for all messages to be logged + await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> { + String log = new String(Files.readAllBytes(Paths.get(LOG_FILE)), StandardCharsets.UTF_8); + assertTrue(log.contains("Executing saga #1"), "Saga should start with LRA"); + + // Wait until we see evidence of completion (success or failure) + boolean completed = log.contains("done for order #1") + || log.contains("fails!") + || log.contains("cancelled"); + assertTrue(completed, "Saga should complete with either success or compensation"); + }); + + // Give logs a moment to flush (important in native mode) + Thread.sleep(1000); + + String log = new String(Files.readAllBytes(Paths.get(LOG_FILE)), StandardCharsets.UTF_8); + + // Verify LRA coordinator is used + assertTrue(log.contains("lra-coordinator"), "Should use LRA coordinator"); + + // Verify services participated + assertTrue(log.contains("Buying train") || log.contains("Buying flight"), + "Services should participate"); + + // Check outcome - either success or compensation is valid + boolean hasSuccess = log.contains("done for order #1"); + boolean hasFailure = log.contains("fails!"); + boolean hasCompensation = log.contains("cancelled"); + + if (hasFailure || hasCompensation) { + System.out.println("Saga #1: Compensation scenario tested (payment failed, saga rolled back)"); + } else if (hasSuccess) { + System.out.println("Saga #1: Success scenario tested (all payments completed)"); + } + + // Either outcome is valid - test passes + assertTrue(hasSuccess || hasFailure, + "Saga should complete with either success or compensation"); + } +} diff --git a/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaTestResource.java b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaTestResource.java new file mode 100644 index 00000000..235093bc --- /dev/null +++ b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaTestResource.java @@ -0,0 +1,110 @@ +/* + * 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. + */ +package org.apache.camel.example.saga; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; + +/** + * Testcontainers resource for Saga integration tests. + * Manages the lifecycle of LRA Coordinator and Artemis broker. + */ +public class SagaTestResource implements QuarkusTestResourceLifecycleManager { + + private static final String LRA_IMAGE = "quay.io/jbosstm/lra-coordinator:latest"; + private static final String ARTEMIS_IMAGE = "quay.io/artemiscloud/activemq-artemis-broker:latest"; + private static final int LRA_PORT = 8080; + private static final int ARTEMIS_PORT = 61616; + + private GenericContainer lraContainer; + private GenericContainer artemisContainer; + private Network network; + + @Override + public Map start() { + network = Network.newNetwork(); + + // Start Artemis broker + artemisContainer = new GenericContainer<>(ARTEMIS_IMAGE) + .withNetwork(network) + .withNetworkAliases("artemis") + .withEnv("AMQ_USER", "admin") + .withEnv("AMQ_PASSWORD", "admin") + .withExposedPorts(ARTEMIS_PORT) + .waitingFor(Wait.forListeningPort() + .withStartupTimeout(Duration.ofSeconds(60))); + + artemisContainer.start(); + + // Start LRA Coordinator + lraContainer = new GenericContainer<>(LRA_IMAGE) + .withNetwork(network) + .withNetworkAliases("lra-coordinator") + .withEnv("QUARKUS_HTTP_PORT", String.valueOf(LRA_PORT)) + .withExposedPorts(LRA_PORT) + .withExtraHost("host.testcontainers.internal", "host-gateway") + .waitingFor(Wait.forHttp("/lra-coordinator") + .forPort(LRA_PORT) + .forStatusCode(200) + .withStartupTimeout(Duration.ofSeconds(90))); + + lraContainer.start(); + + Map config = new HashMap<>(); + + // Artemis configuration + String artemisUrl = String.format("tcp://%s:%d", + artemisContainer.getHost(), + artemisContainer.getMappedPort(ARTEMIS_PORT)); + config.put("quarkus.artemis.url", artemisUrl); + config.put("quarkus.artemis.username", "admin"); + config.put("quarkus.artemis.password", "admin"); + + // LRA configuration + String lraCoordinatorUrl = String.format("http://%s:%d", + lraContainer.getHost(), + lraContainer.getMappedPort(LRA_PORT)); + config.put("camel.lra.coordinator-url", lraCoordinatorUrl); + + // Set local participant URL - use host.testcontainers.internal for coordinator callbacks + config.put("camel.lra.local-participant-url", "http://host.testcontainers.internal:8084/api"); + + // Allow external connections + config.put("quarkus.http.host", "0.0.0.0"); + + return config; + } + + @Override + public void stop() { + if (artemisContainer != null) { + artemisContainer.stop(); + } + if (lraContainer != null) { + lraContainer.stop(); + } + if (network != null) { + network.close(); + } + } +} diff --git a/saga/saga-integration-tests/src/test/resources/application.yml b/saga/saga-integration-tests/src/test/resources/application.yml new file mode 100644 index 00000000..13da7f78 --- /dev/null +++ b/saga/saga-integration-tests/src/test/resources/application.yml @@ -0,0 +1,68 @@ +# +# 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. +# + +# Test configuration +# External dependencies (Artemis, LRA) are managed by SagaTestResource + +quarkus: + http: + port: 8084 + log: + console: + format: "%d{HH:mm:ss} %-5p [%c{2.}] %s%e%n" + level: INFO + min-level: DEBUG + file: + enable: true + path: target/quarkus.log + category: + "org.apache.camel": + level: DEBUG + "org.apache.camel.saga": + level: DEBUG + "org.apache.camel.component.lra": + level: DEBUG + index-dependency: + saga-app: + group-id: org.apache.camel.quarkus.examples + artifact-id: camel-quarkus-example-saga-app + saga-flight: + group-id: org.apache.camel.quarkus.examples + artifact-id: camel-quarkus-example-saga-flight + saga-train: + group-id: org.apache.camel.quarkus.examples + artifact-id: camel-quarkus-example-saga-train + saga-payment: + group-id: org.apache.camel.quarkus.examples + artifact-id: camel-quarkus-example-saga-payment + +camel: + rest: + context-path: /api + component: + jms: + test-connection-on-startup: true + concurrent-consumers: 5 + lra: + enabled: true + # coordinator-url and local-participant-url are set by SagaTestResource + +example: + services: + train: saga-train-service + flight: saga-flight-service + payment: saga-payment-service diff --git a/saga/saga-payment-service/pom.xml b/saga/saga-payment-service/pom.xml index 4dccbd08..84477541 100644 --- a/saga/saga-payment-service/pom.xml +++ b/saga/saga-payment-service/pom.xml @@ -31,4 +31,21 @@ Camel Quarkus :: Examples :: Saga :: Payment Service Payment Service + + + + io.smallrye + jandex-maven-plugin + + + make-index + + jandex + + + + + + + diff --git a/saga/saga-payment-service/src/main/java/org/apache/camel/example/saga/PaymentRoute.java b/saga/saga-payment-service/src/main/java/org/apache/camel/example/saga/PaymentRoute.java index 342a3d87..3b2fd20b 100644 --- a/saga/saga-payment-service/src/main/java/org/apache/camel/example/saga/PaymentRoute.java +++ b/saga/saga-payment-service/src/main/java/org/apache/camel/example/saga/PaymentRoute.java @@ -16,9 +16,11 @@ */ package org.apache.camel.example.saga; +import jakarta.enterprise.context.ApplicationScoped; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.model.SagaPropagation; +@ApplicationScoped public class PaymentRoute extends RouteBuilder { @Override @@ -33,7 +35,7 @@ public void configure() throws Exception { .log("Paying ${header.payFor} for order #${header.id}") .setBody(header("JMSCorrelationID")) .choice() - .when(x -> Math.random() >= 0.85) + .when(simple("${random(0,100)} >= 85")) .log("Payment ${header.payFor} for saga #${header.id} fails!") .throwException(new RuntimeException("Random failure during payment")) .endChoice() diff --git a/saga/saga-train-service/pom.xml b/saga/saga-train-service/pom.xml index d9428f6c..d00364fa 100644 --- a/saga/saga-train-service/pom.xml +++ b/saga/saga-train-service/pom.xml @@ -31,4 +31,21 @@ Camel Quarkus :: Examples :: Saga :: Train Service Train Service + + + + io.smallrye + jandex-maven-plugin + + + make-index + + jandex + + + + + + + diff --git a/saga/saga-train-service/src/main/java/org/apache/camel/example/saga/TrainRoute.java b/saga/saga-train-service/src/main/java/org/apache/camel/example/saga/TrainRoute.java index bf5b05a6..ef339ef4 100644 --- a/saga/saga-train-service/src/main/java/org/apache/camel/example/saga/TrainRoute.java +++ b/saga/saga-train-service/src/main/java/org/apache/camel/example/saga/TrainRoute.java @@ -16,9 +16,11 @@ */ package org.apache.camel.example.saga; +import jakarta.enterprise.context.ApplicationScoped; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.model.SagaPropagation; +@ApplicationScoped public class TrainRoute extends RouteBuilder { @Override @@ -27,14 +29,16 @@ public void configure() throws Exception { .saga() .propagation(SagaPropagation.MANDATORY) .option("id", header("id")) - .compensation("direct:cancelPurchase") + .compensation("direct:cancelTrainPurchase") .log("Buying train #${header.id}") + // Request timeout prevents indefinite waits during payment service failures .to("jms:queue:{{example.services.payment}}?exchangePattern=InOut" + - "&replyTo={{example.services.payment}}.train.reply") + "&replyTo={{example.services.payment}}.train.reply" + + "&requestTimeout=30000") .log("Payment for train #${header.id} done with transaction ${body}") .end(); - from("direct:cancelPurchase") + from("direct:cancelTrainPurchase") .log("Train purchase #${header.id} has been cancelled due to payment failure"); }