= An Eventuate project
image::https://eventuate.io/i/logo.gif[]
This project is part of http://eventuate.io[Eventuate], which is a microservices collaboration platform.
This application demonstrates two key patterns:
The application consists of three services:
Order Service
- manages ordersCustomer Service
- manages customersOrder History Service
- maintains the order historyAll services are implemented using https://micronaut.io[Micronaut], JPA and the https://github.com/eventuate-tram/eventuate-tram-core[Eventuate Tram framework], which provides https://microservices.io/patterns/data/transactional-outbox.html[transactional publish/subscribe].
The Order Service
uses a choreography-based saga to enforce the customer's credit limit when creating orders.
The Order History Service
implements a CQRS view and subscribes to domain events published by the Order Service
and Customer Service
== About Sagas
http://microservices.io/patterns/data/saga.html[Sagas] are a mechanism for maintaining data consistency in a http://microservices.io/patterns/microservices.html[microservice architecture]. A saga is a sequence of transactions, each of which is local to a service.
To learn more about why you need sagas if you are using microservices:
There are two main ways to coordinate sagas: orchestration and choreography. To learn more about orchestration, please look at the https://github.com/eventuate-tram-examples/eventuate-tram-sagas-micronaut-examples-customers-and-orders[Orchestration-based saga example] 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.
=== The Create Order saga
The saga for creating an Order
consists of the follow steps:
Order
in a PENDING
state and publishes an OrderCreated
eventCustomer Service
receives the event attempts to reserve credit for that Order
. It publishes either a Credit Reserved
event or a CreditLimitExceeded
event.Order Service
receives the event and changes the state of the order to either APPROVED
or REJECTED
.== About Command Query Responsibility Segregation (CQRS)
The http://microservices.io/patterns/data/cqrs.html[CQRS pattern] implements queries that retrieves data from multiple services. It maintains a queryable replica of the data by subscribing to domain events published by the services that own the data.
In this example, the Order History Service
maintains a CQRS view in MongoDB by subscribing to domain events published by the Order Service
and Customer Service
.
The CQRS view stores each customer as a MongoDB document that contains information the customer and their orders.
To learn more about why you need CQRS if you are using microservices:
== Transactional messaging with Eventuate Tram
The services uses the https://github.com/eventuate-tram/eventuate-tram-core[Eventuate Tram framework] to communicate asynchronously using events. The flow for publishing a domain event using Eventuate Tram is as follows:
MESSAGE
table as part of the ACID transaction that updates the JPA entity.MESSAGE
table using the MySQL binlog (or Postgres WAL) and publishes messages to Apache Kafka.== Architecture
The following diagram shows the architecture of the Customers and Orders application.
image::./images/Eventuate_Tram_Customer_and_Order_Architecture.png[]
The application consists of three services: Customer Service
, Order Service
, and Order History Service
=== Customer Service
The Customer Service
implements a REST API for managing customers.
The service persists the Customer
JPA entity in a MySQL/Postgres database.
Using Eventuate Tram
, it publishes Customer
domain events that are consumed by the Order Service
.
For more information, see the link:./customer-service-canvas.adoc[microservice canvas for the Customer Service
].
image::./customer-service-canvas.png[width=300]
=== Order Service
The Order Service
implements REST API for managing orders.
The service persists the Order
JPA entity in MySQL/Postgres database.
Using Eventuate Tram
, it publishes Order
domain events that are consumed by the Customer Service
.
For more information, see the link:./order-service-canvas.adoc[microservice canvas for the Order Service
].
image::./order-service-canvas.png[width=300]
=== Order History Service
The Order History Service
implements REST API for querying a customer's order history
This service subscribes to events published by the Order Service
and Customer Service
and updates a MongoDB-based CQRS view.
For more information, see the link:./order-history-service-canvas.adoc[microservice canvas for the Order History Service
].
image::./order-history-service-canvas.png[width=300]
== Building and running
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 https://docs.docker.com/compose/[Docker Compose]:
./gradlew mysqlbinlogComposeBuild
./gradlew mysqlbinlogComposeUp
Note:
./gradlew postgrespollingComposeUp
or ./gradlew postgreswalComposeUp
localhost
- e.g. you are using Docker Toolbox, you will have to use ${DOCKER_HOST_IP}
instead of localhost.
See this http://eventuate.io/docs/usingdocker.html[guide to setting DOCKER_HOST_IP
] for more information.== Using the application
Once the application has started, you can use the application via the Swagger UI:
Customer Service
- http://localhost:8082/swagger-ui/index.html
Order Service
- http://localhost:8081/swagger-ui/index.html
Order History Service
- http://localhost:8083/swagger-ui/index.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://localhost: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://localhost:8081/orders
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
{
"orderId": 1
}
Next, check the status of the Order
in the Order Service
:
$ curl -X GET http://localhost:8081/orders/1
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
{
"orderId": 1,
"orderState": "APPROVED"
}
Finally, look at the customer's order history in the Order History Service
:
$ curl -X GET --header "Accept: */*" "http://localhost:8083/customers/1"
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
{
"id": 1,
"orders": {
"1": {
"state": "APPROVED",
"orderTotal": {
"amount": 4
}
}
},
"name": "Chris",
"creditLimit": {
"amount": 100
}
}
== Got questions?
Don't hesitate to create an issue or see