Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions saga/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
19 changes: 12 additions & 7 deletions saga/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

<formatter-maven-plugin.version>2.29.0</formatter-maven-plugin.version>
<impsort-maven-plugin.version>1.13.0</impsort-maven-plugin.version>
<jandex-maven-plugin.version>3.0.1</jandex-maven-plugin.version>
<license-maven-plugin.version>5.0.0</license-maven-plugin.version>
<maven-compiler-plugin.version>3.15.0</maven-compiler-plugin.version>
<maven-jar-plugin.version>3.5.0</maven-jar-plugin.version>
Expand All @@ -56,6 +57,7 @@
<module>saga-flight-service</module>
<module>saga-payment-service</module>
<module>saga-train-service</module>
<module>saga-integration-tests</module>
</modules>

<dependencyManagement>
Expand Down Expand Up @@ -87,6 +89,10 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-bean</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-direct</artifactId>
Expand All @@ -104,13 +110,6 @@
<artifactId>quarkus-artemis-jms</artifactId>
<version>${quarkiverse-artemis.version}</version>
</dependency>

<!-- Test -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -182,6 +181,12 @@
<version>${maven-jar-plugin.version}</version>
</plugin>

<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>${jandex-maven-plugin.version}</version>
</plugin>

<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
Expand Down
17 changes: 17 additions & 0 deletions saga/saga-app/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,21 @@
<name>Camel Quarkus :: Examples :: Saga :: App</name>
<description>Main application starting SAGA</description>

<build>
<plugins>
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
Expand Down
17 changes: 17 additions & 0 deletions saga/saga-flight-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,21 @@
<name>Camel Quarkus :: Examples :: Saga :: Flight Service</name>
<description>Flight Service</description>

<build>
<plugins>
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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");
}

Expand Down
126 changes: 126 additions & 0 deletions saga/saga-integration-tests/README.md
Original file line number Diff line number Diff line change
@@ -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 <container-id>
```

## 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)
Loading