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 V2] (https://docs.docker.com/compose/install/)
- Build the application
docker compose build
- Running the application
docker compose up backend-sagas
docker compose up backend-tcc
- Running Spock Tests
docker compose up test-sagas
docker compose up test-tcc
- Start db
sudo service postgresql start
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
- Copy
backend/src/main/resources/application-dev.properties.example
toapplication-dev.properties
and fill the placeholder fields. - If you have run the unit-test using docker,
backend/target
directory may be with root owner and group, do:sudo rm -rf backend/target
.
cd backend
mvn clean -Psagas spring-boot:run
mvn clean -Ptcc spring-boot:run
cd backend
mvn clean -Ptest-sagas test
mvn clean -Ptest-tcc test
-
Some Sagas test cases:
-
Some TCC test cases:
The code is structured into:
- 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 figure shows the main classes to be extended for aggregates, their events and services. The classes in red belong to the domain-driven design concepts, and the classes green corresponds to the transaction model specific classes. Classes in blue are the domain-specific extensions, in this case for the quizzes case study, and the classes in orange it is their transactional model specific extension.
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(...).
For the transactional model dependent part:
- Define Saga Aggregates: Extend aggregates with the information required for semantic locks, like SagaTournament and its Semantic Lock.
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(...)
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 backend/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 backend/jmeter/tournament/thesis-cases/
jmeter
- The command launches JMeter GUI. By clicking
File > Open
and selecting a test file it is possible to observe the test structure. - Tests can also be run using the GUI, by clicking on the
Start
button.
The code follows the structure in the figure, where the packages in blue and orange contain, respectively, the microservices domain specific code and the transactional causal consistency domain specific code.
The figure shows the main classes to be extended in the steps described next.
Apply the following steps:
- 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 Causal Aggregates: Extend aggregates with the information required to process merges, like CausalTournament.
- 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 Functionalities: Functionalities coordinate the execution of aggregate services using TCC, like functionality updateStudentName(...), where each service interacts with the unit of work to register changes and publish events, like service updateExecutionStudentName(...).
- Define Test Cases: Define deterministic tests cases for the concurrent execution of functionalities using services to decrement the system version number, which defines functionalities execution order, and to force the deterministic processing of events, like in the Concurrent Execution of Update Name and Add Participant.
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