A distributed, event-driven fitness tracking platform built as a 6-microservice system.
Transforms workout tracking into an RPG-style experience with avatar progression, gamification quests, and AI-powered workout recommendations.
┌──────────────────────────────────────────────────────────────┐
│ API Gateway (8080) │
│ Route all traffic · JWT validation │
└─────────────────────┬────────────────────────────────────────┘
│
┌─────────────┼──────────────────┐
│ │ │
┌───────▼──────┐ ┌────▼──────────┐ ┌────▼───────────────┐
│ User Service │ │Workout Service│ │ Gamification Service│
│ (8081) │ │ (8082) │ │ (8083) │
│ MySQL │ │ MongoDB │ │ MongoDB │
│ JWT Auth │ │ │ │ │
└──────┬───────┘ └──────┬────────┘ └──────────▲──────────┘
│ │ │
│ Apache Kafka │
├──── UserRegistered ───────────────────┤
└──── WorkoutCompleted ─────────────────┤
PersonalRecordEvent ──────────────┘
│
┌─────────────▼──────────────────────────┐
│ Service Discovery (8761) │
│ Eureka Registry │
│ User · Workout · Gamification · AI │
└─────────────┬──────────────────────────┘
│
┌───────▼─────────┐
│ AI Service │
│ (8084) │
│ Spring WebFlux │
│ LLaMA 1B model │
└─────────────────┘
| Service | Port | Database | Purpose |
|---|---|---|---|
muscledia-api-gateway |
8080 | — | Routes all client requests, JWT validation |
muscledia-user-service |
8081 | MySQL | Authentication, profiles, avatars — publishes events to Kafka |
muscledia-workout-service |
8082 | MongoDB | Workout logging, analytics, personal records — publishes events to Kafka |
gamification-service |
8083 | MongoDB | XP, badges, quests, avatar progression — consumes Kafka events |
muscledia-ai-service |
8084 | — | AI-powered personalised workout recommendations via Spring WebFlux, registered with Eureka |
muscledia-service-discovery |
8761 | — | Eureka service registry — all services register here |
Services communicate asynchronously via Apache Kafka rather than synchronous REST calls.
| Event | Producer | Consumer | Purpose |
|---|---|---|---|
UserRegistered |
User Service | Gamification Service | Initialise XP profile and avatar state on account creation |
WorkoutCompleted |
Workout Service | Gamification Service | Award XP, update active quests, check badge eligibility |
PersonalRecordEvent |
Workout Service | Gamification Service | Trigger special PR-related rewards and achievement unlocks |
This decoupling means the Gamification Service can be down without affecting workout logging — events queue in Kafka and are processed on recovery.
- MySQL — User accounts require ACID-compliant relational storage with strict consistency for authentication and profile data
- MongoDB — Workout and gamification data stored as flexible BSON documents, enabling schema-free iteration across workout types and badge definitions without migrations
The AI recommendation service initially used a self-hosted LLaMA 3B model. Response times exceeded 5 minutes under load — completely unusable. Through systematic profiling, three bottlenecks were identified and resolved:
- Model downsizing — Switched from 3B to 1B parameter model. Response time: 5 min → ~2 min
- Payload reduction — Reduced prompt context from 2,500 to 800 tokens using focused prompting with only relevant workout history
- Reactive architecture — Replaced blocking thread calls with Spring WebFlux non-blocking I/O — threads freed immediately while model inference runs
Result: 87% improvement — 312s → 41s — sustaining 450 req/sec under load
The AI Service registers with Eureka Service Discovery alongside all other services, enabling the API Gateway to route recommendation requests without hardcoded URLs.
All client traffic enters through a single Spring Cloud Gateway instance which handles JWT validation before forwarding to downstream services — keeping auth logic centralised and out of individual services.
| Metric | Result |
|---|---|
| Service availability | 99.99% over 400+ hours of production monitoring |
| AI response time improvement | 87% (312s → 41s) |
| Throughput under load | 450 req/sec sustained |
| Database query improvement | 85–90% via advanced indexing |
| REST API endpoints | 52 across all services |
| Infrastructure cost | $0/month (Oracle Cloud Always Free Tier) |
| Layer | Technologies |
|---|---|
| Backend | Java 21, Spring Boot 3, Spring WebFlux, Spring Security, Spring Cloud Gateway |
| Messaging | Apache Kafka |
| Databases | MySQL, MongoDB |
| Infrastructure | Docker, Docker Compose, Oracle Cloud Infrastructure |
| Service Discovery | Spring Cloud Eureka |
| API | REST, OpenAPI/Swagger |
| Testing | JUnit 5, Mockito |
- Java 17+
- Docker & Docker Compose
- Maven 3.6+
- Git
git clone --recursive https://github.com/Muscledia/Muscledia.git
cd Muscledia
# If already cloned without --recursive
git submodule update --init --recursiveWindows (PowerShell):
.\setup-mongodb-keyfile.ps1If you encounter permission errors:
Remove-Item -Path .\config\mongodb -Recurse -Force
New-Item -ItemType Directory -Force -Path .\config\mongodb
$folder = Get-Item .\config\mongodb
$folder.Attributes = $folder.Attributes -band (-bnot [System.IO.FileAttributes]::ReadOnly)
$bytes = New-Object byte[] 756
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$rng.GetBytes($bytes)
$base64 = [Convert]::ToBase64String($bytes)
$base64 | Out-File -FilePath .\config\mongodb\mongo-keyfile -Encoding ASCII -NoNewline
$rng.Dispose()
Test-Path .\config\mongodb\mongo-keyfiledocker-compose up --build -d
# Or with logs visible
docker-compose up --builddocker-compose ps
curl http://localhost:8080/actuator/health
open http://localhost:8761 # Eureka dashboard — all 6 services should appear# Register
curl -X POST http://localhost:8080/api/users/register \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"email": "test@example.com",
"password": "SecurePassword123!",
"birthDate": "1990-01-01",
"gender": "MALE",
"height": 180,
"initialWeight": 75,
"goalType": "BUILD_STRENGTH",
"initialAvatarType": "WEREWOLF"
}'
# Login and receive JWT
curl -X POST http://localhost:8080/api/users/login \
-H "Content-Type: application/json" \
-d '{"username": "testuser", "password": "SecurePassword123!"}'| Service | Swagger UI |
|---|---|
| User Service | http://localhost:8081/swagger-ui.html |
| Workout Service | http://localhost:8082/swagger-ui.html |
| Gamification Service | http://localhost:8083/swagger-ui.html |
# Auth
POST /api/users/register
POST /api/users/login
GET /api/users/me
# Workouts
GET /api/v1/workouts
POST /api/v1/workouts
GET /api/v1/analytics/dashboard
# Gamification
GET /api/badges
GET /api/quests
GET /api/users/{id}/profile# MySQL
docker exec -it muscledia-mysql mysql -u springstudent -p
# Password: springstudent
# MongoDB
docker exec -it muscledia-mongodb mongosh -u admin -p
# Password: secure_mongo_password_123
use muscledia_workouts
use gamification_db| Variable | Description |
|---|---|
JWT_SECRET |
JWT signing secret |
MYSQL_ROOT_PASSWORD |
MySQL root password |
MONGO_INITDB_ROOT_PASSWORD |
MongoDB root password |
# Port conflicts
netstat -tulpn | grep :8080
# Database issues
docker-compose logs mysql
docker-compose logs mongodb
docker-compose restart mysql mongodb
# Service registration — verify all 6 services appear in Eureka
open http://localhost:8761
# Health checks
curl http://localhost:8080/actuator/health
curl http://localhost:8081/actuator/health
curl http://localhost:8082/actuator/health
curl http://localhost:8083/actuator/health
curl http://localhost:8084/actuator/health
# Logs
docker-compose logs -f user-service
docker-compose logs -f- Test coverage — Automated tests were not implemented for the Kafka event pipeline (WorkoutCompleted, PersonalRecordEvent, UserRegistered). Integration testing for event-driven flows is an identified gap and a priority for future development.
- AI service — The LLaMA model runs locally via Docker. On machines with limited RAM, inference times will vary from the benchmarked results.
Building Muscledia as a solo project surfaced lessons that internship work hadn't:
- Never assume performance — The LLaMA 3B model was chosen without benchmarking under production constraints. The 5-minute response time only surfaced during user testing two weeks before the thesis defence. Every external dependency now gets a load test before integration.
- POC over debate — When the database strategy was contested, a 2-day benchmark (43ms MongoDB vs 200ms MySQL for personal record detection) resolved the disagreement faster than any argument could.
- Kafka decoupling has real operational value — When the Gamification Service was redeployed during testing, workout logging continued uninterrupted. Events queued and replayed cleanly on recovery. That resilience wasn't planned — it was a consequence of the architecture.
- Test coverage for async flows is harder than for REST — Testing Kafka consumers requires embedded brokers or Testcontainers. This was underestimated during planning and left as a gap. The next version will have integration tests for all three event types.
- Operational cost of polyglot persistence — Running two databases adds deployment and maintenance complexity. The performance tradeoff justified it here, but it is a real cost.
Built as a diploma project at Uniwersytet WSB Merito Poznań.
Evaluated by academic reviewers and technical assessors.
Questions about implementation details or architectural decisions: ericmuganga@outlook.com