Open-source project
eventuate-tram/eventuate-tram-sagas-examples-customers-and-orders avatar
eventuate-tram/eventuate-tram-sagas-examples-customers-and-orders

eventuate-tram-sagas-examples-customers-and-orders: a saga in two Spring Boot services

Spring Boot/JPA microservices that use an orchestration-based saga to maintain data consistency

582 stars254 forksJavaNOASSERTION

At a glance

What is it?
This example builds an order and a customer service on Spring Boot, JPA and the Eventuate Tram Saga framework, with an orchestrator that reserves credit and then approves or rejects the order. It is a teaching codebase, but a complete one, with CDC, Kafka, an API gateway and a scripted end to end test.
Who is it for?
This example fits a team learning saga coordination, or an engineer who wants to see an orchestrator implemented against a real broker rather than described in a blog post. It is the wrong starting point if you want a production skeleton, since it is a demonstration with two services, a fixed credit limit scenario and test scripts rather than a framework you extend.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two services and one consistency problem

The README describes the application as demonstrating how to maintain data consistency in a Java, JDBC and JPA-based microservice architecture using sagas, and it consists of two services. The `Order Service` creates orders, and the `Customer Service` manages customers. Both are implemented using Spring Boot, JPA and the Eventuate Tram Saga framework.

The problem the example is built around is specific: the `Order Service` uses a saga to enforce the customer's credit limit when creating orders. That is a real distributed transaction. Creating an order and checking the customer's remaining credit span two services with two databases, and a plain database transaction cannot span both. A saga is the mechanism for keeping them consistent, a sequence of transactions each local to a service.

The README is careful about the design choice, saying there are two main ways to coordinate sagas, orchestration and choreography, and pointing at a separate example for the choreography style. This one uses orchestration-based sagas, where a saga object invokes the participants.

The project is part of Eventuate, described as a microservices collaboration platform, and the licence is one GitHub cannot classify, with a LICENSE file in the repository root.

The orchestrator is a persistent object that keeps talking

The mechanism is described in four sentences, and they are the whole design.

A saga orchestrator is a persistent object that does one of two things. On creation, it sends a command message to a participant. And when it receives a reply, it updates its state and sends a command message to the next participant.

The word persistent is the load-bearing one. The orchestrator is not a function that runs and returns, it is a row in a database that survives a crash, which is what makes the whole thing resumable. If the process dies between sending a command and receiving the reply, the orchestrator can be reloaded and continue, because its state was never in memory.

The Create Order saga in this example follows a specific sequence. The `Order Service` creates an `Order` in a pending state. The `Order Service` creates a `CreateOrderSaga` to coordinate the creation of the order. The `CreateOrderSaga` sends a `reserveCredit` command to the `CustomerService`. The `Customer Service` receives the command, attempts to reserve credit for that `Order`, and replies with a message indicating the outcome. The `CreateOrderSaga` receives the reply, and sends either an `ApproveOrder` or a `RejectOrder` command to the `OrderService`. The `Order Service` receives the command and changes the state of the order to either `approved` or `rejected`.

So the customer never learns about the order, and the order service never talks to the customer service directly. Every interaction goes through the saga, which is what makes the sequence auditable.

CDC moves the messages, Kafka carries them

The Architecture section explains how messages actually travel, and the answer is change data capture rather than a messaging library inside each service.

The `Customer Service` implements the REST endpoints for managing customers and persists the `Customer` JPA entity in a MySQL or Postgres database. Using the Eventuate Tram Saga framework, it processes command messages, updates the `Customer` entity, and sends back a reply message. The `Order Service` implements a REST endpoint for managing orders and persists the `Order` JPA entity and the `CreateOrderSaga` in the same kind of database, and using the same framework it sends command messages and processes replies.

The transport between them is the Eventuate Tram CDC service, which tracks inserts into the `MESSAGE` table using the MySQL binlog and publishes messages to Apache Kafka.

That design has a specific consequence worth understanding. A service does not publish a message when it wants to talk to someone, it writes a row, and the CDC service notices. The outbox pattern is implemented by the framework, and the repository carries build-and-test scripts for a sharded outbox variant, `build-and-test-all-mysql-sharded-outbox.sh`, next to the ordinary one, plus scripts for creating Kafka topics and consumer groups.

For a reader who has seen choreography, this is the interesting difference: in choreography the services talk to each other directly, while here the only services that talk to each other are the ones the orchestrator explicitly commands.

One command starts the whole stack

Building and running takes a single Gradle invocation, and the choice is the database:

code
 ./gradlew :end-to-end-tests:runApplicationMySQL

or

code
 ./gradlew :end-to-end-tests:runApplicationPostgres

The command starts the containers on unique ports and prints out the home page URL. That last detail is what makes the example pleasant to try, because the ports are assigned per run and the URL is printed, so you do not have to guess what is listening where.

The repository layout shows the scale of the machinery around that one command. There is a customer-service and an order-service directory, an api-gateway-service directory, an end-to-end-tests directory, a migration-tests directory, buildSrc and gradle directories, and a pile of scripts: wait-for-mysql.sh, wait-for-postgres.sh, wait-for-services.sh, kafka-topics.sh, kafka-consumer-groups.sh, mysql-customer-service-cli.sh, postgres-order-service-cli.sh and test-curl-commands.sh.

Those wait-for scripts are the visible sign that the stack has an ordering problem, which is exactly the kind of problem a real microservice system has. The example does not pretend that starting a database, a CDC service, Kafka, a gateway and two services is instantaneous, and it ships the waiting logic rather than hoping.

Three curl calls tell you the whole story

The application is driven through the API Gateway with curl, and the sequence is worth reading end to end because it is the smallest complete demonstration of the pattern.

First a customer, with a credit limit:

bash
$ curl -X POST --header "Content-Type: application/json" -d '{
  "creditLimit": {
    "amount": 5
  },
  "name": "Jane Doe"
}' http://localhost:8080/customers

HTTP/1.1 200
Content-Type: application/json;charset=UTF-8

{
  "customerId": 1
}

Then an order for 4, which is inside the limit of 5:

bash
$ curl -X POST --header "Content-Type: application/json" -d '{
  "customerId": 1,
  "orderTotal": {
    "amount": 4
  }
}' http://localhost:8080/orders

HTTP/1.1 200
Content-Type: application/json;charset=UTF-8

{
  "orderId": 1

And the order state, which is the saga's verdict:

bash
$ curl -X GET http://localhost:8080/orders/1

HTTP/1.1 200
Content-Type: application/json;charset=UTF-8

{
  "orderId": 1,
  "orderState": "APPROVED"
}

The `orderState` field is the whole point of the example in one value. It is APPROVED because the saga got a yes from the customer service, and if the amount had been 6 the same call would have produced a rejected order instead, with the credit never reserved.

A code tour instead of a long README

The README points at a Code Tour configuration, and that choice says something about how the example is meant to be read. The author has configured a Code Tour that walks through the code in either Visual Studio Code or GitHub Codespaces.

In Visual Studio Code, the steps are to install the Code Tour extension from the Visual Studio Code Marketplace, and then use the `CodeTour: Start Tour` command from the command palette. In a GitHub Codespace, you create the codespace from the repository, install the extension if necessary, and run the same command.

The repository carries the tour files in a `.tours` directory, and the Codespace link points at the `development` branch rather than the default branch, which is consistent with the tour describing a moving target.

For a reader who would rather read the source in order, the README offers the alternative route through the Swagger UIs and the curl examples, and the service descriptions give the map: the REST endpoints, the JPA entities, the messages each service sends and the replies it processes.

Questions are directed to a Google Group mailing list, a Slack workspace and a contact page, so the example has a community around it rather than being a one-off post.

An example project, still being pushed to

The maintenance picture for an example repository is a little different from a library, and this one is worth reading carefully.

The repository is not archived and the last push was on 2026-10-22, so the example is still being worked on, and the default branch is master. There are no GitHub releases, which is normal for a repository whose artefacts are containers and Gradle tasks rather than published artifacts.

The licence is the interesting part. The repository listing records it as a custom licence that GitHub cannot classify, and the LICENSE.md file at the root is the authoritative text. For a codebase you intend to copy patterns from, that file is the thing to read, since the terms under which the code may be reused are set there rather than in the README.

The other structural detail is the CI setup. There is a `.circleci` directory alongside the Gradle build files, a mise.toml for toolchain versions, and separate script sets for MySQL and Postgres runs, which tells you the project is tested on both database variants rather than on one.

The final honest note on scope. This is a two-service example with one business rule, and its value is in the shape of the code rather than in the breadth of the framework. Reading it will teach you the choreography and orchestration split and the CDC transport, and it will not tell you how to run a hundred services, because it was never trying to.

Editorial conclusion

This example fits a team learning saga coordination, or an engineer who wants to see an orchestrator implemented against a real broker rather than described in a blog post. It is the wrong starting point if you want a production skeleton, since it is a demonstration with two services, a fixed credit limit scenario and test scripts rather than a framework you extend. Verify first which database variant you want, MySQL or Postgres, since the run targets differ, and read the code tour before copying the pattern into your own services, because the credit reservation and the order state machine are the parts worth studying.

Frequently asked questions

What does the eventuate-tram sagas customers and orders example demonstrate?

It demonstrates how to maintain data consistency in a Java, JDBC and JPA-based microservice architecture using sagas. The application has two services, an Order Service that creates orders and a Customer Service that manages customers, and the Order Service uses a saga to enforce the customer's credit limit when creating orders.

How does the orchestration-based saga in this example work?

A saga orchestrator is a persistent object that either sends a command message to a participant when it is created, or updates its state and sends a command to the next participant when it receives a reply. In the Create Order saga the orchestrator sends reserveCredit to the Customer Service, then sends ApproveOrder or RejectOrder to the Order Service.

How do I run the eventuate-tram sagas example?

Run ./gradlew :end-to-end-tests:runApplicationMySQL for the MySQL variant or ./gradlew :end-to-end-tests:runApplicationPostgres for the Postgres variant. The command starts the containers on unique ports and prints the home page URL to visit.

How are messages exchanged between the order and customer services?

The Eventuate Tram CDC service tracks inserts into the MESSAGE table using the MySQL binlog and publishes messages to Apache Kafka, so the services exchange command and reply messages through the framework rather than calling each other directly.

How can I get a guided tour of the example code?

The README points at a Code Tour configuration that walks through the code in Visual Studio Code or in GitHub Codespaces. Install the Code Tour extension from the Visual Studio Code Marketplace and run the CodeTour: Start Tour command from the command palette.

Official sources

  1. eventuate-tram/eventuate-tram-sagas-examples-customers-and-orders on GitHub
  2. Issues
  3. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/eventuate-tram-eventuate-tram-sagas-examples-customers-and-orders.svg)](https://hysenlabs.com/projects/eventuate-tram-eventuate-tram-sagas-examples-customers-and-orders)

Was this useful?