A resilient, fully-observable microservices platform for the IUT Cafeteria β built to survive the Ramadan rush that brought the old monolith down.
Disclosure: This project was built with the help of GitHub Copilot.
# 1. Bring up all 13 containers (builds images on first run)
docker compose up -d --build
# 2. Wait ~30 s for services to pass their health checks, then seed the DB
npm run seed
# 3. Open the student UI
open http://localhost:3000Node 18+ required for the seed script (uses native
fetch).
Everything else runs inside Docker β no local Node needed.
| Role | Username | Password |
|---|---|---|
| Student | student1 |
password123 |
| Admin | admin |
adminpassword |
| Service | URL | Notes |
|---|---|---|
| Student UI | localhost:3000 | Login β order β live tracker |
| Admin Dashboard | localhost:3000/admin | Health grid, chaos controls, latency alert |
| Identity Provider | localhost:3001 | JWT issuance + rate-limiting |
| Order Gateway | localhost:3002 | Fast-fail entry point |
| Stock Service | localhost:3003 | Postgres inventory (optimistic lock) |
| Kitchen Queue | localhost:3004 | RabbitMQ consumer + retry logic |
| Notification Hub | localhost:3005 | Socket.io real-time updates |
| Grafana | localhost:3006 | admin / admin β 12-panel dashboard |
| Prometheus | localhost:9090 | Metrics + alert rules |
| Alertmanager | localhost:9093 | Slack / email routing |
| RabbitMQ UI | localhost:15672 | guest / guest |
Browser
β
ββ Next.js Frontend (3000)
β βββ /api/identity-provider/* ββΊ Identity Provider (3001)
β βββ /api/order-gateway/* ββΊ Order Gateway (3002)
β βββ /api/stock-service/* ββΊ Stock Service (3003)
β βββ /api/kitchen-queue/* ββΊ Kitchen Queue (3004)
β βββ Socket.io ββΊ Notification Hub (3005)
β
ββ /admin ββΊ Admin Dashboard (same Next.js app)
Order Gateway (3002)
βββ Redis fast-fail check (stock:{id})
βββ JWT auth verify β Identity Provider
βββ Publish β RabbitMQ [orders_queue]
Kitchen Queue (3004) β RabbitMQ consumer
βββ Immediate ACK (async processing)
βββ POST /stock/reduce β Stock Service (3003)
β βββ Postgres optimistic lock (version field)
β βββ Redis cache sync
βββ Retry: up to 3Γ with exponential back-off (transient errors)
βββ Publish β RabbitMQ [notifications_queue]
Notification Hub (3005) β RabbitMQ consumer
βββ Socket.io emit β connected browser
Prometheus (9090) ββΊ scrapes all 5 services every 15 s
Grafana (3006) ββΊ pre-provisioned 12-panel dashboard
Alertmanager (9093) ββΊ GatewayHighLatency / ServiceDown / HighOrderFailureRate
| Step | Where | Detail |
|---|---|---|
| 1. Fast-Fail | Order Gateway + Redis | Stock 0 β reject in <50 ms, no DB hit |
| 2. Accept | Order Gateway + RabbitMQ | Enqueue, return 202 Accepted in <2 s |
| 3. In Kitchen | Kitchen Queue | Immediate ACK, status notification emitted |
| 4. Stock Verify | Stock Service | Atomic Postgres update with optimistic lock; on 409 kitchen retries up to 3Γ |
| 5. Notify | Notification Hub β Socket.io | Browser tracker advances: Pending β In Kitchen β Stock Verified β Ready / Rejected |
# Test a single service
cd services/identity-provider && npm test
# Or run every service from the root
for svc in identity-provider order-gateway stock-service kitchen-queue notification-hub; do
echo "βΆ $svc" && (cd services/$svc && npm test)
donenpm run seedThe seed script upserts 19 menu items (mains, sides, drinks, desserts) β safe to run as many times as you like.
# 1. Login β get a JWT
TOKEN=$(curl -s -X POST http://localhost:3001/login \
-H 'Content-Type: application/json' \
-d '{"username":"student1","password":"password123"}' \
| grep -o '"token":"[^"]*"' | cut -d'"' -f4)
echo "Token: $TOKEN"
# 2. Check stock
curl -s http://localhost:3003/stock | jq '.[] | {id, name, quantity}'
# 3. Place an order
curl -s -X POST http://localhost:3002/order \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"itemId":"burger","quantity":1,"orderId":"smoke-test-001"}'
# 4. Check latency stats on the gateway
curl -s http://localhost:3002/latency-stats | jq# 4th attempt within a minute β 429
for i in 1 2 3 4; do
echo "Attempt $i:"
curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:3001/login \
-H 'Content-Type: application/json' \
-d '{"username":"student1","password":"wrongpass"}'
echo
done# Enable chaos on stock-service
curl -s -X POST http://localhost:3003/chaos \
-H 'Content-Type: application/json' \
-d '{"enable":true}'
# Place an order β kitchen-queue will retry then give up
curl -s -X POST http://localhost:3002/order \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"itemId":"ramen","quantity":1,"orderId":"chaos-test-001"}'
# Recover
curl -s -X POST http://localhost:3003/chaos \
-H 'Content-Type: application/json' \
-d '{"enable":false}'Open localhost:3006 (admin / admin) β the IUT Cafeteria dashboard loads automatically.
12 panels:
- Orders accepted / rejected / processed / retried / failed / notifications pushed
- Gateway request rate (req/s)
- Gateway latency p50 / p95 / p99 with 1 s alert threshold
- Login success / failed
- Stock reductions / failures
| Alert | Condition | Severity |
|---|---|---|
GatewayHighLatency |
30 s rolling avg > 1 s for 30 s | warning |
ServiceDown |
up == 0 for 1 min |
critical |
HighOrderFailureRate |
failed / accepted > 10 % over 5 min | warning |
Routes all alerts to Slack and email. Set these env vars before docker compose up to activate real delivery:
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."
export SMTP_FROM="alerts@yourdomain.com"
export SMTP_TO="oncall@yourdomain.com"
export SMTP_HOST="smtp.gmail.com"
export SMTP_PORT="587"
export SMTP_USERNAME="you@gmail.com"
export SMTP_PASSWORD="your-app-password"Without these, the stack still boots β Alertmanager logs delivery errors but does not crash.
| Feature | Where | Detail |
|---|---|---|
| JWT Auth | Order Gateway | All order routes protected; 401 on missing/invalid token |
| Rate Limiting | Identity Provider | 3 login attempts / min per Student ID |
| Redis Fast-Fail | Order Gateway | Instant 422 if Redis says stock = 0 |
| Idempotency Keys | Order Gateway + Redis | order:processed:{orderId} with 24 h TTL prevents duplicate processing |
| Optimistic Locking | Stock Service | Sequelize version: true; concurrent updates get 409 Conflict |
| Async ACK | Kitchen Queue | RabbitMQ message ACK'd immediately; processing happens in background |
| Retry + Back-off | Kitchen Queue | Up to 3 retries on transient errors (5xx / 409); exponential delay (2 s, 4 s, 6 s); 422 = permanent failure, no retry |
| Chaos Mode | All 5 services | POST /chaos {"enable":true} makes a service return 503 β test fault handling live |
| Visual Latency Alert | Admin Dashboard | 30 s rolling ring buffer; banner turns red when avg > 1 s |
| Docker Healthchecks | docker-compose.yml | All 13 containers; depends_on: condition: service_healthy ensures correct startup order |
.
βββ docker-compose.yml # Full 13-container stack
βββ package.json # Root β exposes `npm run seed`
βββ scripts/
β βββ seed.js # Seeds 19 menu items via REST
βββ monitoring/
β βββ prometheus.yml # Scrape config + alert rules wiring
β βββ alerts.yml # Prometheus alert rules (3 rules)
β βββ alertmanager.yml # Slack + email routing
β βββ grafana/
β βββ provisioning/ # Auto-loads datasource + dashboard
β βββ dashboards/
β βββ cafeteria.json # 12-panel Grafana dashboard
βββ services/
β βββ frontend/ # Next.js 16 (React 19, TypeScript, Tailwind)
β βββ identity-provider/ # JWT issuance, bcrypt, rate-limiting
β βββ order-gateway/ # Fast-fail, idempotency, RabbitMQ publish
β βββ stock-service/ # Postgres + Sequelize (optimistic lock) + Redis cache
β βββ kitchen-queue/ # RabbitMQ consumer, retry logic
β βββ notification-hub/ # Socket.io real-time push
βββ .github/
βββ workflows/
βββ ci.yml # Matrix build + lint (frontend) + test
βββ integration-test.yml # Full docker compose stack smoke test
GitHub Actions runs on every push to main:
| Job | Steps |
|---|---|
| ci.yml (matrix across 6 services) | npm ci β build β ESLint (frontend only) β Jest |
| integration-test.yml | docker compose up β wait for health β run integration tests β tear down |
Build fails if any test fails or the frontend has ESLint errors.
| Column | Type | Notes |
|---|---|---|
id |
STRING (PK) | e.g. "burger" |
name |
STRING | Display name |
quantity |
INTEGER | Validated >= 0 |
version |
INTEGER | Optimistic lock counter |
createdAt |
TIMESTAMP | Sequelize auto |
updatedAt |
TIMESTAMP | Sequelize auto |
Hardcoded for dev. Passwords stored as bcrypt hashes (cost factor 10).
Generated for DevSprint 2026. Problem statement: problem_statement.txt.