The artifact supports the test of business logic of a microservices application designed on the concept of Domain-Driven Design Aggregate and using several transactional models.
The currently supported transactional models are:
- Eventual Consistency
- Sagas applying the Orchestration variant
- Transactional Causal Consistency
The system allows testing the interleaving of functionalities execution in a deterministic context, such that it is possible to evaluate the resulting behavior.
The description of the examples for Transactional Causal Consistency are in Transactional Causal Consistent Microservices Simulator.
docker compose buildOr run the service with the flag --build
Sagas:
docker compose up quizzes-sagasTCC:
docker compose up quizzes-tccSagas:
docker compose up quizzes-sagas-streamTCC:
docker compose up quizzes-tcc-streamNote: Run
build-simulatorfirst before running tests.
docker compose up build-simulatorSimulator Sagas:
docker compose up test-simulator-sagasQuizzes Sagas:
docker compose up test-quizzes-sagasQuizzes TCC:
docker compose up test-quizzes-tcc- IntelliJ IDEA (Ultimate or Community Edition)
Follow the same database setup steps as in the Maven section or run postgres container with docker-compose.
The project includes ready-to-use IntelliJ run configurations in the .run/ directory. After opening the project in IntelliJ, these configurations will be automatically available in the Run/Debug dropdown.
- Open the project in IntelliJ IDEA
- Run the
build-simulatorconfiguration to install the simulator library - Select a run configuration from the dropdown (e.g., Quizzes)
- Click the Run button
- Maven 3.9.9
- Java 21+
- PSQL 14
- RabbitMQ 3.12+ (required for stream profile)
- JMeter 5.6
- Start PostgreSQL:
sudo service postgresql startor with docker-compose:
docker compose up postgres -d- Create the database:
sudo su -l postgres
dropdb msdb
createdb msdb- Create user to access db:
psql msdb
CREATE USER your-username WITH SUPERUSER LOGIN PASSWORD 'yourpassword';
\q
exit- Configure application properties:
- Fill in the placeholder fields with your database credentials in
applications/quizzes/src/main/resources/application.yaml
- Fill in the placeholder fields with your database credentials in
cd simulatormvn clean installmvn clean -Ptest-sagas testcd applications/quizzesmvn clean -Psagas spring-boot:runmvn clean -Ptcc spring-boot:runmvn clean -Ptest-sagas testmvn clean -Ptest-tcc testRunning the application as distributed microservices requires setting up individual databases for each service and running RabbitMQ for inter-service communication.
- Start RabbitMQ:
# Using Docker (recommended)
docker-compose up rabbitmq -d- Create databases for each microservice:
sudo su -l postgres
createdb versiondb
createdb answerdb
createdb coursedb
createdb executiondb
createdb questiondb
createdb quizdb
createdb topicdb
createdb tournamentdb
createdb userdb
exitNote: Running postgres with docker container automatically creates the databases
- Install the simulator library (if not already done):
cd simulator
mvn clean install
cd ..| Service | Port | Profiles | Database |
|---|---|---|---|
| Gateway | 8080 | - | - |
| Version Service | 8081 | version-service,stream |
versiondb |
| Answer Service | 8082 | (sagas or tcc),stream,answer-service |
answerdb |
| Course Execution Service | 8083 | (sagas or tcc),stream,course-execution-service |
executiondb |
| Question Service | 8084 | (sagas or tcc),stream,question-service |
questiondb |
| Quiz Service | 8085 | (sagas or tcc),stream,quiz-service |
quizdb |
| Topic Service | 8086 | (sagas or tcc),stream,topic-service |
topicdb |
| Tournament Service | 8087 | (sagas or tcc),stream,tournament-service |
tournamentdb |
| User Service | 8088 | (sagas or tcc),stream,user-service |
userdb |
| Course Service | 8089 | (sagas or tcc),stream,course-service |
coursedb |
1. Start the Version Service (from project root):
cd simulator
mvn spring-boot:run -Dspring-boot.run.profiles=version-service,stream2. Start each Quizzes microservice (from applications/quizzes):
cd applications/quizzesSagas:
| Service | Command |
|---|---|
| Answer Service | mvn spring-boot:run -Panswer-saga |
| Course Service | mvn spring-boot:run -Pcourse-saga |
| Course Execution Service | mvn spring-boot:run -Pcourse-execution-saga |
| Question Service | mvn spring-boot:run -Pquestion-saga |
| Quiz Service | mvn spring-boot:run -Pquiz-saga |
| Topic Service | mvn spring-boot:run -Ptopic-saga |
| Tournament Service | mvn spring-boot:run -Ptournament-saga |
| User Service | mvn spring-boot:run -Puser-saga |
TCC:
| Service | Command |
|---|---|
| Answer Service | mvn spring-boot:run -Panswer-tcc |
| Course Service | mvn spring-boot:run -Pcourse-tcc |
| Course Execution Service | mvn spring-boot:run -Pcourse-execution-tcc |
| Question Service | mvn spring-boot:run -Pquestion-tcc |
| Quiz Service | mvn spring-boot:run -Pquiz-tcc |
| Topic Service | mvn spring-boot:run -Ptopic-tcc |
| Tournament Service | mvn spring-boot:run -Ptournament-tcc |
| User Service | mvn spring-boot:run -Puser-tcc |
3. Start the Gateway (from applications/gateway):
cd applications/gateway
mvn spring-boot:runTip: You can use the pre-configured IntelliJ run configurations in the
.run/directory to start each microservice more easily.
Sagas test cases:
TCC test cases:
The application uses Spring Boot profiles and YAML configuration files to manage different deployment modes.
Database settings are defined in application.yaml:
| Profile | Database | Description |
|---|---|---|
| Monolith | msdb |
Single database for all aggregates |
| Microservices | Per-service DBs | Each service has its own database (e.g., tournamentdb, userdb) |
Service-specific database URLs are configured in profile files like application-tournament-service.yaml.
When running with the stream profile, inter-service communication uses RabbitMQ. Bindings are configured in application.yaml:
| Binding Type | Example | Purpose |
|---|---|---|
| Command Channels | tournament-command-channel |
Send commands to services |
| Command Consumers | tournamentServiceCommandChannel-in-0 |
Receive and process commands |
| Event Channel | event-channel |
Broadcast events to subscribers |
| Event Subscribers | tournamentEventSubscriber-in-0 |
Receive events for processing |
| Response Channel | commandResponseChannel-in-0 |
Receive command responses |
Service-specific bindings override only the channels relevant to that service, as shown in application-tournament-service.yaml.
Each microservice runs on a dedicated port:
| Service | Port | Profile File |
|---|---|---|
| Gateway | 8080 | application.yaml |
| Version Service | 8081 | - |
| Answer Service | 8082 | application-answer-service.yaml |
| Course Execution | 8083 | application-course-execution-service.yaml |
| Question Service | 8084 | application-question-service.yaml |
| Quiz Service | 8085 | application-quiz-service.yaml |
| Topic Service | 8086 | application-topic-service.yaml |
| Tournament Service | 8087 | application-tournament-service.yaml |
| User Service | 8088 | application-user-service.yaml |
The Gateway application.yaml configures:
-
Service URLs (lines 4-12): Define base URLs for each microservice with environment variable overrides for Docker deployment.
-
Route Definitions (lines 31-89): Map API paths to backend services using Spring Cloud Gateway predicates.
-
Service List (lines 14-22): List of services for admin operations like version management.
- The core concepts of Domain-Driven Design
- The core concepts for the distributed functionalities Coordination
- The core concepts for management of Sagas
- The core concepts for management of TCC
- A case study for Quizzes Tutor
- The transactional model independent Microservices
- The Sagas implementation for
- The TCC implementation for
- The tests of the Quizzes Tutor for
The code follows the structure in the simulator library and application decomposition figures, where the packages in blue and orange contain, respectively, the microservices domain specific code and the transactional causal consistency domain specific code.
The API Gateway is used when running the quizzes application as microservices to route API requests to the appropriate microservice.
How to implement and test your own business logic for Sagas and TCC (Illustrated with Quizzes Microservice System)
The figure shows the main classes to be extended for aggregates, their events and services.
Apply the following steps to define a domain-specific aggregate, its events and services, here illustrated with the Quizzes Tutor system and its Tournament aggregate.
For the transactional model independent part:
- Define Aggregate: Each microservice is modeled as an aggregate. The first step is to define the aggregates. The simulator uses Spring-Boot and JPA, so the domain entities definition uses the JPA notation. In Tournament aggregate we can see the aggregate root entity and the reference to its internal entities.
- Specify Invariants: The aggregate invariants are defined by overriding method verifyInvariants().
- Define Events: Define the events published by upstream aggregates and subscribed by downstream aggregates, like UpdateStudentNameEvent.
- Subscribe Events: The events published by upstream aggregates can be subscribed by overriding method getEventSubscriptions().
- Define Event Subscriptions: Events can be subscribed depending on its data. Therefore, define subscription classes like TournamentSubscribesUpdateStudentName.
- Define Event Handlers: For each subscribed event define an event handler that delegates the handling in a handling functionality, like UpdateStudentNameEventHandler and its handling functionality processUpdateStudentNameEvent(...).
- Define Aggregate Services: Define the microservice API, whose implementation interact with the unit of work to register changes and publish events, like service updateExecutionStudentName(...).
- Define Event Handling: Define the aggregates event handling, that periodically polls the event table to process events, like TournamentEventHandling.
- Define Event Subscriber Service: Define the event subscriber service, that subscribes to events published by other microservices via Spring Cloud Stream, like TournamentEventSubscriberService.
For the transactional model dependent part:
- Define Saga Aggregates: Extend aggregates with the information required for semantic locks, like SagaTournament and its Semantic Lock.
- Define Causal Aggregates: Extend aggregates with the information required for causal consistency, like CausalTournament
To define the system functionalities, it is necessary to extend the simulator part for coordination.
For the functionalities:
- Define Functionalities: Functionalities coordinate the execution of aggregate services using sagas, like functionality AddParticipantFunctionalitySagas(...) and AddParticipantFunctionalityTCC(...)
- Define Commands: Define the commands to be executed by the functionalities, like AddParticipantCommand. Every method of the aggregate service should have a corresponding command.
For the inter-service communication:
- Create the CommandHandlers of the aggregate: It receives commands from local or remote services' functionalities and calls the corresponding aggregate service method of that command, like TournamentCommandHandler for local calls and TournamentStreamCommandHandler for remote calls via messaging.
- Configure Spring Cloud Stream Bindings: Define the command and event channels in
application.yaml, like tournament-service bindings.
To write tests:
- Design Test Cases: Define tests cases for the concurrent execution of functionalities deterministically enforcing execution orders, like in the Concurrent Execution of Update Name and Add Participant. Directory coordination contains the test of more complex interleavings using the sagas transactional model.
- After starting application with the tcc profile, either using Docker or Maven, and installing JMeter
cd applications/quizzes/jmeter/tournament/thesis-cases/
jmeter -n -t TEST.jmx
- Some test cases:
- 5a-updateStudentName-addParticipant-processUpdateNameEvent.jmx
- 5b-addParticipant-updateStudentName-processUpdateNameEvent.jmx
- 5c-updateStudentName1-addParticipant-updateStudentName2-processUpdateNameEvent.jmx
- 5d-addParticipant1-updateStudentName-processUpdateNameEvent1-addParticipant2-processUpdateNameEvent2.jmx
- 8-5-update-tournament-concurrent-intention-pass.jmx
- 8-6-add-participant-concurrent-update-execution-student-name-processing-ends-first.jmx
- 8-7-add-participant-concurrent-anonymize-event-processing-processing-ends-last.jmx
- 8-8-update-execution-student-add-participant-process-event-add-participant.jmx
- 8-9-add-participant-concurrent-anonymize-event-processing-processing-ends-first.jmx
- 8-10-concurrent-delete-tournament-add-participant.jmx
cd applications/quizzes/jmeter/tournament/thesis-cases/
jmeter
- The command launches JMeter GUI. By clicking
File > Openand selecting a test file it is possible to observe the test structure. - Tests can also be run using the GUI, by clicking on the
Startbutton.
Spock Tests in DAIS2023 paper - 23nd International Conference on Distributed Applications and Interoperable Systems
To reproduce the paper results follow the steps:
- Analyze a figure in the paper, fig3a-d and fig4;
- Read the test case code for the figure, including the final assertions that define the expected behavior (see below);
- Run the test case (see below);
- Read the logger INFO messages, they use UPPERCASE. They identify when a functionality and event processing starts and ends and what its version number is.
- For instance, in test-fig4 both functionalities start with the same version number (they are concurrent), but addParticipant finishes with a higher number, because it finishes after updateName. It can be observed in the log that an exception was thrown, due to the invariant break.
- Test code
- Run:
docker-compose up test-fig3a
- Test code
- Run:
docker-compose up test-fig3b
- Test code
- Run:
docker-compose up test-fig3c
- Test code
- Run:
docker-compose up test-fig3d
- Test code
- Run:
docker-compose up test-fig4



