This repository implements an event-driven sports betting settlement workflow for a hiring assignment.
The system accepts an event outcome over HTTP, publishes it to Kafka, matches affected bets, publishes settlement commands to RocketMQ, and persists the final settlement result.
The codebase is a Maven mono-repo with four modules:
common-libevent-outcome-servicebet-matching-servicebet-settlement-service
The implementation follows a pragmatic DDD and clean-architecture direction:
domain: rich domain models and business rulesservices: orchestration onlyinfrastructure: transport, persistence, messaging, DTOs, mappers
Boundary rules used in this solution:
- controllers handle HTTP only
- services orchestrate use cases
- repositories and publishers/listeners contain no business logic
- MapStruct is used at object translation boundaries
- domain models stay free from Spring, JPA, and transport concerns
End-to-end flow:
event-outcome-service (HTTP)
-> Kafka topic: event-outcomes
-> bet-matching-service (consumer)
-> RocketMQ topic: bet-settlements
-> bet-settlement-service (consumer)
-> H2 persistence of final settlement result
common-lib
- Holds shared immutable message contracts only.
event-outcome-service
- Exposes
POST /api/event-outcomes. - Validates the HTTP payload.
- Persists accepted outcomes in its own H2 database.
- Publishes
EventOutcomeMessageto Kafka.
bet-matching-service
- Consumes
EventOutcomeMessagefrom Kafka. - Loads seeded bets from its own H2 database.
- Uses domain logic to compute
WONorLOST. - Publishes
BetSettlementMessageto RocketMQ.
bet-settlement-service
- Consumes
BetSettlementMessagefrom RocketMQ. - Validates and maps the message into a rich domain model.
- Persists the final settlement record in its own H2 database.
Application and library versions used in the implementation:
- Java:
17 - Spring Boot:
3.5.11 - MapStruct:
1.6.3 - RocketMQ Spring Boot Starter:
2.3.4
Messaging and runtime image tags used in docker-compose.yml:
- Apache Kafka:
apache/kafka:4.2.0 - Apache RocketMQ:
apache/rocketmq:5.4.0 - Java runtime base image for service containers:
eclipse-temurin:17-jre
bet-matching-service seeds these bets through Flyway:
bet-1forevent-1, predicted winnerwinner-1, amount10.00bet-2forevent-1, predicted winnerwinner-2, amount20.00bet-3forevent-2, predicted winnerwinner-3, amount15.00
If you publish an outcome for event-1 with eventWinnerId=winner-1:
bet-1becomesWONbet-2becomesLOSTbet-3is unaffected
Prerequisites:
- Docker with the Compose plugin
- Java
17
Build the JARs first because each Dockerfile copies target/*.jar:
./mvnw -q clean packageStart the stack:
docker compose up --buildExposed entry points:
event-outcome-service:http://localhost:8081- Kafka external listener for local debugging:
localhost:9092 - RocketMQ nameserver:
localhost:9876
Notes:
bet-matching-serviceandbet-settlement-serviceare worker services. They do not expose public HTTP endpoints.- All service databases are in-memory H2 instances, so data resets whenever the containers stop.
Publish an event outcome:
curl -X POST http://localhost:8081/api/event-outcomes \
-H "Content-Type: application/json" \
-d '{
"eventId": "event-1",
"eventName": "Arsenal vs Benfica",
"eventWinnerId": "winner-1"
}'Expected response:
HTTP/1.1 202 Accepted
That single request should trigger:
- one event outcome persisted by
event-outcome-service - two matched bets processed by
bet-matching-service - two settlement rows persisted by
bet-settlement-service
Each service has its own H2 database instance, configured in application.properties. You can connect to them using the H2 Console or any JDBC client:
| Service | Console URL | JDBC URL |
|---|---|---|
| Event Outcome | http://localhost:8081/h2-console | jdbc:h2:mem:eventoutcomedb |
| Bet Matching | http://localhost:8082/h2-console | jdbc:h2:mem:betmatchingdb |
| Bet Settlement | http://localhost:8083/h2-console | jdbc:h2:mem:betsettlementdb |
The current test suite is intentionally fast and focused:
- domain unit tests for business rules and invariants
- service unit tests for orchestration
- listener and publisher unit tests with Mockito
- @WebMvcTest for the HTTP controller
- @DataJpaTest for repository query behavior where it adds value
Verification commands used during implementation:
./mvnw testIntentionally excluded in this phase:
- integration tests
- Testcontainers
- embedded Kafka or RocketMQ broker tests
- end-to-end automated Compose smoke tests
Deliberate simplifications for the assignment:
- no outbox pattern
- no retry orchestration or dead-letter flow
- no idempotency safeguards on message consumption
- no distributed tracing
- no authentication or authorization
- no production-grade observability setup
- H2 is in-memory per service, so persistence is ephemeral
- Dockerfiles depend on a prior Maven package step instead of doing a multi-stage build
These trade-offs keep the code readable and focused on the core event-driven workflow while still demonstrating:
- clearly defined service boundaries
- rich, business-focused domain models
- a pragmatic Clean Architecture structure
- strong object-oriented design
- clear ownership of persistence within each service
- a lean, practical microservice setup
- consistent application of SOLID principles
- a maintainable foundation that can evolve toward more production-ready capabilities