This application demonstrates how to maintain data consistency in an Java/JDBC/JPA-base microservice architecture using sagas. It’s a choreography-based saga version of Eventuate Tram Sagas Customers and Orders
The application consists of two services:
-
Order Service
- creates orders -
Customer Service
- manages customers
Both services are implemented using Spring Boot, JPA and the Eventuate Tram framework, which provides transactional publish/subscribe.
The Order Service
uses a choreography-based saga to enforce the customer’s credit limit when creating orders.
Sagas are a mechanism for maintaining data consistency in a microservice architecture. A saga is a sequence of transactions, each of which is local to a service.
There are two main ways to coordinate sagas: orchestration and choreography. Please see example to learn about orchestration-based sagas. This example uses choreography-based sagas, which use domain events for coordination. Each step of a saga updates the local database and publishes a domain event. The domain event is processed by an event handler, which performs the next local transaction.
To learn more about why you need sagas if you are using microservices:
-
the Saga pattern
-
read about sagas in my Microservice patterns book
The saga for creating an Order
consists of the follow steps:
-
The Order Service creates an
Order
in a pending state and publishes anOrderCreated
event -
The
Customer Service
receives the event attempts to reserve credit for thatOrder
. It publishes either aCredit Reserved
event or aCreditLimitExceeded
event. -
The
Order Service
receives the event and changes the state of the order to eitherapproved
orrejected
.
The following diagram shows the architecture of the Customers and Orders application.
The application consists of two services:
-
Customer Service
- implements the REST endpoints for managing customers. The service persists theCustomer
JPA entity in a MySQL/Postgres database. UsingEventuate Tram
, it publishesCustomer
domain events that are consumed by theOrder Service
. -
Order Service
- implements a REST endpoint for managing orders. The service persists theOrder
JPA entity in MySQL/Postgres database. UsingEventuate Tram
, it publishesOrder
domain events that are consumed by theCustomer Service
.
A service publishes events using Eventuate Tram.
Eventuate Tram inserts events into the MESSAGE
table as part of the ACID transaction that updates the JPA entity.
The Eventuate Tram CDC service tracks inserts into the MESSAGE
table using the MySQL binlog and publishes messages to Apache Kafka.
A service subscribes to the events and updates its JPA entities in response.
Note: you do not need to install Gradle since it will be downloaded automatically. You just need to have Java 8 installed.
First, build the application
./gradlew assemble
Next, launch the services using Docker Compose:
export DOCKER_HOST_IP=...
docker-compose -f docker-compose-mysql.yml build
docker-compose -f docker-compose-mysql.yml up -d
Note:
-
You can also run the Postgres version using
docker-compose-eventuate-postgres.yml
-
You need to set
DOCKER_HOST_IP
before running Docker Compose. This must be an IP address or resolvable hostname. It cannot belocalhost
. See this guide to settingDOCKER_HOST_IP
for more information.
Once the application has started, you can use the application via the Swagger UI:
-
Order Service
-http://${DOCKER_HOST_IP}:8081/swagger-ui.html
-
Customer Service
-http://${DOCKER_HOST_IP}:8082/swagger-ui.html
You can also use curl
to interact with the services.
First, let’s create a customer:
$ curl -X POST --header "Content-Type: application/json" -d '{
"creditLimit": {
"amount": 5
},
"name": "Jane Doe"
}' http://${DOCKER_HOST_IP}:8082/customers
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
{
"customerId": 1
}
Next, create an order:
$ curl -X POST --header "Content-Type: application/json" -d '{
"customerId": 1,
"orderTotal": {
"amount": 4
}
}' http://${DOCKER_HOST_IP}:8081/orders
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
{
"orderId": 1
}
Finally, check the status of the Order
:
$ curl -X GET http://${DOCKER_HOST_IP}:8081/orders/1
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
{
"orderId": 1,
"orderState": "APPROVED"
}
Don’t hesitate to create an issue or see