T2-Project Architecture¶
The T2-Project consists of seven services.
The green and blue services are the core T2 services. The blue ones participate in the saga, the green ones do not. The white things are databases and external services.
UI : The application frontend.
UIBackend : API Gateway for the UI.
Cart : Manages the user shopping carts. It saves the cart contents to the cart repository.
Orchestrator : Manages the saga.
Order : Persists orders to the order repository and marks them as either success or failure.
Payment : Handles the store’s payment by contacting some credit institute.
Inventory : Manages the store’s products. They are stored in the product repository.
The UIBackend communicates over HTTP with a REST like interface. The Saga participants (marked in blue) use message based communication among each other. This is necessary because of the saga pattern.
The T2-Project realizes the following business process:
The activities within the Saga subprocess must be executed as a transaction. The transaction is implemented according to the Saga Pattern.
The T2-Project’s services realise the activities of the business process like this:
Service |
Activity |
|---|---|
Cart |
add item to cart |
Inventory |
add reservation, delete reservation, commit reservation |
Order |
create order, reject order |
Payment |
do payment |
Orchestrator |
confirm order |
Saga¶
The T2-Project’s saga is composed of the following steps:
Step |
Service |
Transaction |
Compensation |
|---|---|---|---|
1 |
Inventory |
cancelReservations() |
|
2 |
Order |
createOrder() |
rejectOrder() |
3 |
Payment |
doPayment() |
|
4 |
Inventory |
commitReservations() |
The doPayment() step is the saga’s pivot transaction.
If this step succeeds, the saga runs to completion.
The step commitReservations() is designed to be repeatable.
It will succeed eventually.
The same applies to the compensations rejectOrder() and cancelReservations().
Every order is persisted, those that succeeded as well as those that failed. An order holds, among other details, the order state. Upon creation the order state is set to success. As all orders should be persisted, the compensation of creating an order is to set its state to failed. Currently an order cannot be cancelled after submission to the orchestrator.
The inventory manages the number of available units per product.
If the inventory decreases the number of available units only after placing the order, the product may not be available anymore because another user bought to many units of that product.
To prevent such failures the ordered units of a product are locked as soon as they are placed in the shopping cart.
As there is now no action to perform on the inventory before the pivot transaction (doPayment()), there is only the compensation that deletes the reservation (in case the saga rolls back) or the step commitReservations() that handles served reservations.
Frameworks & Dependencies¶
The T2-Project uses the following frameworks (and services):
Spring and Spring Boot¶
Dependency |
Version |
|---|---|
Spring Boot |
3.2.x |
Eventuate Tram and Eventuate Tram Saga¶
Eventuate Tram is a framework for Transactional Messaging and Eventuate Tram Sagas is a framework for saga orchestration. They both work on top of Spring / Spring Boot. The T2-Project uses the Eventuate Tram Core framework with Kafka as the message broker and Postgres as the database.
Versions¶
Dependency |
Version |
|---|---|
io.eventuate.tram.core |
0.29.0.RELEASE |
io.eventuate.tram.sagas |
0.18.0.RELEASE |
Eventuate CDC Service¶
The Eventuate CDC Service realizes the Transactional Outboxing Pattern required by the Saga Pattern.
Since we are using Postgres, there are two strategies for reading messages/events available to the CDC reader: Postgres WAL and Polling. The default value for polling is 500 milliseconds, which results in a delay of about 3 seconds for the whole Saga operation. Therefore, we switched to Postgres WAL, which is more time efficient and results in a delay of only about 200 milliseconds (without concurrent requests).
Dependency |
Version |
|---|---|
eventuateio/eventuate-cdc-service |
0.16.0.RELEASE |
Message Broker¶
The T2-Project uses Kafka and Zookeeper as message broker.
Saga Databases¶
The T2-Project uses two PostgreSQL databases for the saga data: one for the inventory service and one for the orchestrator. The other two services that participate in Saga, Order service and Payment service, don’t store their domain data in a relational database. Therefore, they also use the orchestrator’s Postgres database for the Saga messages.
The Postgres databases use the container image from eventuateio because it already contains the tables required for the transactional outboxing (source code).
Domain Databases¶
The T2-Project uses MongoDBs and PostgreSQL databases for storing the domain data. The cart repository and the order repository are MongoDBs. The product repository used by the Inventory service is a PostgreSQL database. As mentioned above, the Postgres database of the Inventory service is used for both domain data and saga data to ensure atomicity of local transactions (transactional outbox pattern).
Dependency Management¶
Apache Maven is used for dependency management.
Initially, the versions and dependencies of the individual services were managed completely independently of each other in their own pom.xml. However, versions were also managed centrally with the help of environment variables (see the now deleted script setenv.sh).
The structure as a multi-module project offers the following advantages:
Standard method for managing equal dependencies and versions and for building an entire software system
Maven automatically ensures the correct build order, which e.g. simplifies the compilation of common
Compared to the initial implementation with environment variables, this simplifies local execution and changing versions
Less redundant configuration code
However, there are also disadvantages:
Management of dependencies and versions in a central location contradicts the principle of autonomy of microservices (however, it is possible to overwrite the version definition for individual services if required)
CI pipeline for building individual services does not start if something is changed in the central pom.xml (can be solved via a notification mechanism, but has not yet been implemented)
Services¶
All Services are implemented as Spring Boot applications. This is the services’ general package structure:
de.unistuttgart.t2.<service-name>de.unistuttgart.t2.<service-name>.sagade.unistuttgart.t2.<service-name>.repositoryde.unistuttgart.t2.<service-name>.exceptionde.unistuttgart.t2.<service-name>.domain
Each service has a subset of those packages, as visualized in the diagram below. The diagram reads as follows: Orchestrator has the <service-name> package and a packages saga, Order and Inventory have those packages and also a package repository, and so on.
de.unistuttgart.t2.<service-name>¶
The app package contains the following classes, usually prefixed with the service name. E.g the application class of the Order Service is called OrderApplication, the controller is called OrderController and so on.
Application : annotated with
@SpringBootApplication.Service : contains the logic of the service.
Controller : defines the HTTP endpoint of the service. This class is only present, if the service has HTTP endpoints.
Services with complicated configurations have an additional config package that contains the various configuration classes.
de.unistuttgart.t2.<service-name>.saga¶
The saga package contains classes that are saga specific. For the participants:
CommandHandler : handles incoming messages.
For the orchestrator:
Saga : definition of the saga.
de.unistuttgart.t2.<service-name>.repository¶
The repository packages contain all classes and interfaces for the domain databases.
Item : the items in the database.
Repository : an Interface that extends Spring’s MongoRepository to access the database.
de.unistuttgart.t2.<service-name>.exceptions¶
Any kind of service specific exceptions can be found here.
de.unistuttgart.t2.<service-name>.domain¶
Any classes that represent something domain specific, but does not belong into the repository package. Most domain specific things are used by multiple services and thus located in the common package, however things that only one service needs are located here.
Links¶
For more details on each service, look at the repositories or the API documentation:
Order service: GitHub
Inventory service: GitHub
Payment service: GitHub
Orchestrator service: GitHub
Cart service: GitHub
Credit Institute service: GitHub
UIBackend : GitHub
UI : GitHub
Common: GitHub
E2E Test: GitHub
Note: A convenience repo including all microservices exists so that not every repo needs to be downloaded separately: https://github.com/t2-project/microservices.