- Overview
- Architecture
- Code Structure
- Running an Application
- Configuration Reference
- Test Cases
- Benchmarking and Performance Tests
- Publications
Developing business-logic-rich microservices requires navigating complex trade-offs between data consistency and distributed coordination. Although patterns like Sagas and Transactional Causal Consistency (TCC) provide mechanisms to manage distributed state, validating their behavior before production is challenging.
The Microservices Simulator is a Domain-Driven Design (DDD) microservice simulator that isolates core business logic from communication and transactional infrastructure. By modeling distributed systems around aggregates, the simulator allows developers to evaluate identical application code under varying consistency guarantees and network constraints. It features support for multiple transactional models (Sagas, TCC) and seamless transitions across diverse deployment topologies, ranging from centralized execution to fully distributed environments.
In practice, this allows developers to compare Saga semantic-lock strategies against TCC snapshot-based conflict resolution without rewriting domain services, and benchmark the same workflows across local, stream, and gRPC deployment profiles.
This tool acts as a deterministic sandbox for the shift-left validation and optimization of microservice architectures, minimizing developer effort while enabling robust architectural validation.
The system architecture is divided into three primary layers:
- Application Layer: Contains the concrete domain logic, specifically, the Application Functionality and Application Domain components. This layer is entirely decoupled from the underlying infrastructural complexities.
- Business Layer: Provides the core coordination and domain structuring mechanisms. It encompasses the Coordination Module, the Transaction Module, and the Aggregate Module.
- Infrastructure Layer: Manages cross-cutting technical concerns and network operations, including the Messaging Module, Notification Module, Impairment Module, Monitoring Module, and Versioning Module.
The simulator supports multiple execution topologies, ranging from deterministic single-process runs to fully distributed microservice deployments.
| Topology | Process and Data Layout | Command Transport | Event Transport | Typical Profiles | Strategic Value | Core Infrastructure |
|---|---|---|---|---|---|---|
| Centralized Local | Single application process, shared database | In-memory (local) | Internal event persistence and polling | sagas|tcc, local |
Deterministic baseline to debug invariants, workflow ordering, and concurrency interleavings | PostgreSQL, Jaeger |
| Centralized Stream | Single application process, shared database | RabbitMQ command channels | RabbitMQ event-channel |
sagas|tcc, stream |
Intermediate topology to benchmark broker-based communication with shared persistence | PostgreSQL, RabbitMQ, Jaeger |
| Centralized gRPC | Single application process, shared database | gRPC (discovery-based resolution) | RabbitMQ event-channel |
sagas|tcc, grpc |
Intermediate topology to benchmark point-to-point RPC and discovery with shared persistence | PostgreSQL, Eureka, RabbitMQ, Jaeger |
| Distributed Stream | Independent service processes, database-per-service | RabbitMQ command channels | RabbitMQ event-channel |
Service profile + sagas|tcc, stream (e.g., quiz-service, sagas, stream) |
Production-like isolation with broker-mediated coordination across independent services | PostgreSQL per service, Eureka or Spring Cloud Kubernetes, API Gateway, RabbitMQ, Jaeger |
| Distributed gRPC | Independent service processes, database-per-service | gRPC (service-to-service via discovery) | RabbitMQ event-channel |
Service profile + sagas|tcc, grpc (e.g., quiz-service, tcc, grpc) |
Production-like hybrid model with direct RPC commands and asynchronous event propagation | PostgreSQL per service, Eureka or Spring Cloud Kubernetes, API Gateway, RabbitMQ, Jaeger |
Versioning option across topologies: add distributed-version only with sagas to use local Snowflake ID generation.
TCC requires centralized version management because causal conflict resolution depends on a centralized, monotonically
increasing version sequence.
| Module | Purpose | Main Code |
|---|---|---|
| Aggregate | Aggregate identity, invariants, and event subscription contracts | simulator aggregate package |
| Coordination | Workflow orchestration, step dependency ordering, and execution plans | simulator coordination package |
| Transaction (Sagas) | Semantic locks, compensation, and Saga-specific command wrappers | simulator sagas package |
| Transaction (TCC) | Causal snapshots, optimistic conflict detection, and merge commit path | simulator causal package |
| Notification | Event persistence, publication/subscription transport, and polling support | simulator notification package |
| Messaging | Command gateway abstraction across local, stream, and gRPC modes | simulator messaging package |
| Impairment | Fault and delay injection hooks for resilience and behavior testing | simulator impairment package |
| Monitoring | Tracing and observability support for functionalities and workflow steps | simulator monitoring package |
| Versioning | Version ID generation and version-service support across topologies | simulator versioning package |
- A case study for Quizzes Tutor
- The transactional model independent Microservices
- The Sagas implementation for Aggregates and Coordination
- The TCC implementation for Aggregates and Coordination
- The tests of the Quizzes Tutor for Sagas and TCC
The API Gateway is used when running the quizzes application as microservices to route API requests to the appropriate microservice. The gateway operates as an MVC application using a custom dynamic proxy controller to forward REST requests.
The framework significantly minimizes the cognitive load for developers by abstracting distributed infrastructure. The workflow focuses strictly on domain modeling, defining events, and orchestrating business logic.
While the majority of the effort is focused on the microservice being developed, supporting centralized deployment
topologies (such as Centralized Stream or Centralized gRPC) introduces a minor configuration step: the developer must
append the new aggregate's specific network bindings (e.g., stream channels and event broker bindings) into a shared,
unified application.yaml file to ensure the centralized execution environment can correctly route messages between the
aggregates.
| Development Task | Implementation Details & Example | Rationale (Why) |
|---|---|---|
| Define Spring Boot Application | Create the microservice entry point, e.g., TournamentServiceApplication.java with @SpringBootApplication. |
Establishes the bounded context runtime and independent deployability. |
| Define Aggregate | Define the JPA root entity, e.g., Tournament.java, and associated value objects, e.g., TournamentCreator. |
Defines the transactional consistency boundary where invariants are enforced. |
| Define DTOs and Repositories | Create data transfer objects and Spring Data JPA interfaces for data access, e.g., TournamentDto.java, TournamentRepository.java. |
Separates persistence/API contracts from domain behavior and supports query/update paths. |
| Specify Invariants | Override the verifyInvariants() method, e.g., asserting tournament start date is before end date. |
Prevents invalid aggregate versions from being committed. |
| Define Events | Define the events published/subscribed, e.g., UpdateStudentNameEvent.java. |
Makes upstream changes observable by downstream aggregates for eventual consistency. |
| Subscribe Events | Override the getEventSubscriptions() method, adding concrete subscriptions. |
Declares upstream-downstream dependencies explicitly at the domain level. |
| Define Event Subscriptions | Define subscription conditions, e.g., in TournamentSubscribesUpdateStudentName.java a tournament subscribes to creator/participant name updates. |
Filters only relevant upstream events, avoiding unnecessary or inconsistent updates. |
| Define Event Handlers | Delegate handling to processing functionalities, e.g., UpdateStudentNameEventHandler.java. |
Converts raw event intake into deterministic domain actions. |
| Define Aggregate Services | Define the microservice API to register changes, e.g., updateUserName(...). |
Provides stable operation-level contracts used by commands and controllers. |
| Define Web Controllers | Expose REST API endpoints to external clients, e.g., TournamentController.java. |
Enables external access while preserving application/domain layering. |
| Define Event Handling | Define polling logic for the event table, e.g., TournamentEventHandling.java. |
Drives periodic event processing cycles for eventual consistency. |
| Define Event Subscriber Service | Subscribe to Spring Cloud Stream events, e.g., EventSubscriberService.java. |
Bridges broker transport to local event persistence/processing. |
| Define Transactional Aggregates | Extend aggregate for specific models, e.g., SagaTournament.java (locks) and CausalTournament.java (merging). |
Adapts the same domain to model-specific consistency semantics without duplicating business logic. |
| Define Commands | Define remote commands for aggregate services, e.g., AddParticipantCommand.java. |
Formalizes inter-service invocation contracts independent of transport protocol. |
| Create CommandHandler | Receive remote commands and map to services, e.g., TournamentCommandHandler.java. |
Centralizes command routing and isolates transport concerns from domain services. |
| Configure Network Bindings | Set stream channels or grpc ports in the aggregate yaml file, e.g., in application-tournament-service.yaml and append them to the unified application.yaml. |
Activates deployment topologies (both distributed and centralized) without changing business code. |
| Configure API Gateway Routes | Define route mappings in the microservice yaml to route HTTP requests. | Decouples external API paths from internal service locations. |
| Development Task | Implementation Details & Example | Rationale (Why) |
|---|---|---|
| Define Functionality | Extend WorkflowFunctionality to coordinate a specific use-case, e.g., AddParticipantFunctionalitySagas.java. |
Encapsulates one business use case as a reusable coordination unit. |
| Workflow Orchestration | Map execution Steps, dependencies, and transaction triggers within buildWorkflow(), e.g., defining getUserStep and addParticipantStep dependencies. |
Makes ordering, dependency, and rollback/compensation boundaries explicit. |
| Command Dispatching | Instantiate remote Commands and dispatch via the abstract CommandGateway, e.g., sending AddParticipantCommand wrapped in a SagaCommand with semantic locks. |
Executes distributed steps through transport-agnostic contracts while preserving domain isolation. |
The simulator framework acts as the foundation for microservice applications. You can implement multiple applications in
the applications/ directory. The Quizzes application is provided as a complete reference implementation and case
study.
Running the simulator effectively means running an application (like Quizzes) built on top of it. The execution framework provides extensive flexibility depending on your goals, whether debugging domain logic locally or testing distributed resilience on Kubernetes.
| Execution Environment | Best For | Documentation |
|---|---|---|
| Docker Compose | Local testing, switching between centralized/distributed topologies quickly without local dependencies. | Run Using Docker |
| Maven | Development, running individual microservices, and load testing with JMeter. | Run Using Maven |
| IntelliJ IDEA | Debugging and stepping through execution flows using pre-configured run profiles. | Run Using IntelliJ |
| Kubernetes (Local/Cloud) | Testing production-grade orchestration (Kind) or cloud latency (Azure AKS). | Deploy to Kubernetes |
The application uses Spring Boot profiles and YAML configuration files to manage different deployment modes.
The project uses Jaeger for distributed tracing to monitor and visualize the flow of requests across microservices.
- Dashboard: Access the Jaeger UI at http://localhost:16686.
- Collector: The application sends traces to the Jaeger collector on
http://localhost:4317using the OTLP gRPC protocol. - Instrumentation: Custom instrumentation is implemented in
TraceManagerusing the OpenTelemetry SDK to trace functionalities and their steps. - Cross-service correlation: Trace IDs are propagated through workflow execution, command dispatching, and event processing, allowing end-to-end inspection of one functionality across microservices.
In distributed mode, local deployments use Eureka for service discovery. The gateway and each microservice register with
the Eureka server at http://${EUREKA_HOST:localhost}:8761/eureka/. When deploying on Kubernetes, the kubernetes
profile enables Spring Cloud Kubernetes discovery instead of Eureka.
Database settings are defined in application.yaml:
| Profile | Database | Description |
|---|---|---|
| Centralized | msdb |
Single database for all aggregates |
| Distributed | Per-service DBs | Each service has its own database (e.g., tournamentdb, userdb) |
When running in distributed mode, the Quizzes microservices map to the following individual databases:
answer-service->answerdbcourse-service->coursedbexecution-service->executiondbquestion-service->questiondbquiz-service->quizdbtopic-service->topicdbtournament-service->tournamentdbuser-service->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.
Operational intent of channel types:
- Command channels are directional, aggregate-targeted paths for explicit downstream-to-upstream requests.
- Event channel is a shared broadcast path for upstream-to-downstream propagation and eventual consistency.
- Response channel supports correlated replies for synchronous behavior over asynchronous transports.
Alternative remote transport is available with the grpc profile. Each service exposes a gRPC endpoint for
commands (see GrpcServerRunner), and callers use GrpcCommandGateway with Eureka-based discovery. Default and
service-specific gRPC ports are configured in the application-*-service.yaml files (and exposed via Eureka metadata
key grpcPort). Override the default client port with grpc.command.default-port or per-service with
grpc.command.<service>.port when needed.
When running in distributed mode with the distributed-version profile active, each microservice generates version IDs
locally using
a Snowflake ID
generator, removing the need for a centralized version-service. This profile can also be used in centralized mode with
any communication profile (local, stream, or grpc). The 64-bit IDs are composed of a 41-bit timestamp, a 10-bit
machine ID (derived from spring.application.name), and a 12-bit sequence number, guaranteeing globally unique,
monotonically increasing versions across services.
This option is only supported with the sagas transactional model (TCC requires centralized version management). In TCC, centralized ordering is part of causal conflict detection and merge correctness, so decentralized Snowflake generation is intentionally disabled.
| Profile | Version Source | Requires version-service? |
|---|---|---|
| (default) | Centralized VersionService |
Yes |
distributed-version |
Local SnowflakeIdGenerator |
No |
Microservices:
Each microservice runs on a dedicated port:
| Service | Port | Profile File |
|---|---|---|
| Gateway | 8080 | application-gateway.yaml |
| Version Service | 8081 | application-version-service.yaml |
| Answer Service | 8082 | application-answer-service.yaml |
| Course Execution | 8083 | application-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 |
Every service port can be changed, including version-service port 8081, and gateway port 8080. Service Discovery
will map the service name to the service port automatically.
Observability & Infrastructure:
| Service | URL / Port | Purpose |
|---|---|---|
| Jaeger UI | http://localhost:16686 | Distributed tracing; view spans, traces, service dependencies |
| RabbitMQ Management | http://localhost:15672 | Message broker admin |
| Eureka Service Discovery | http://localhost:8761 | View registered services and their instances |
| PostgreSQL | localhost:5432 |
Database access (centralized mode: msdb; distributed: *db) |
Default Credentials:
- PostgreSQL:
postgres/postgres - RabbitMQ:
guest/guest
The Gateway application-gateway.yaml configures:
- Service discovery: Eureka discovery for local distributed deployments; Kubernetes discovery is enabled via the
kubernetesprofile. - Route definitions: The API Gateway is a Spring MVC-based application that dynamically proxies HTTP requests to
backend services. Routes are configured via
gateway.routes.importsreferencing the target microservice application properties, which theDynamicMVCProxyControlleruses to forward REST calls. - Version service URL: The Admin controller endpoints directly interact with the remote microservices for configuration sync.
How these tests map to simulator capabilities:
- Functionality orchestration and ordering: validate workflow step dependencies and execution order.
- Concurrency interleavings: force race scenarios with controlled step progression (for example,
executeUntilStep(...)in coordination tests). - Fault and recovery behavior: inject failures and verify compensation and abort paths.
- Model comparison: run analogous Sagas and TCC tests to compare lock-based and merge-based conflict handling.
Sagas-focused test suites:
- Workflow Test Plan (Simulator)
- Circuit Breaker Tests (Simulator)
- Tournament Functionality Tests (Quizzes)
- Tournament Async Coordination Tests (Quizzes)
- Fault and Recovery Behavior Tests (Quizzes)
TCC-focused test suites:
For deterministic concurrency scenarios and paper-aligned walkthroughs, see Reproducing DAIS2023 Paper Tests.
For repeated deployment benchmarking of tournament scenarios, use:
./scripts/benchmark-deployments.sh --test concurrentAddparticipant.jmxRun with a specific transaction mode and a number of repetitions:
./scripts/benchmark-deployments.sh --test concurrentAddparticipant.jmx --tx-mode sagas --repetitions 5
./scripts/benchmark-deployments.sh --test 5a-updateStudentName-addParticipant-processUpdateNameEvent.jmx --tx-mode tcc --repetitions 5What this script does:
- Boots each configured topology (
centralized-*,distributed-*, anddistributed-versionvariants). - Runs the selected
.jmxtest for each deployment, across the configured repetitions. - Writes per-run and aggregate results under jmeter-results in a timestamped benchmark folder.
- Returns non-zero on benchmark failures, so it can be used in CI/regression workflows.
Requirements and setup details are in Run Using Maven. DAIS paper scenario mapping is in Reproducing DAIS2023 Paper Tests.
- DAIS 2023: D. Pereira and A. R. Silva, "Transactional Causal Consistent Microservices Simulator," in Distributed Applications and Interoperable Systems (DAIS), 2023.
- Science of Computer Programming 2025: P. Pereira and A. R. Silva, "Microservices simulator: An object-oriented framework for transactional causal consistency," Science of Computer Programming, 2025.