A production-grade, event-driven backend built with Spring Boot microservices. Services communicate asynchronously via Apache Kafka (saga pattern) and synchronously via REST through a central API Gateway with JWT authentication.
"Polyglot Persistence · Choreography-Based Saga · Zero-Downtime Kubernetes Deployments"
- Key Highlights
- Architecture
- Order Saga Flow
- 🚀 Quick Start
- Service Port Reference
- Services
- Kafka Topics
- Kubernetes
- Load Testing Results
- Kafka Partition Scaling
- Observability
- Project Structure
- Tech Stack
- Event-Driven Saga — Checkout flows are orchestrated through Kafka events with automatic compensating rollbacks. No single point of failure.
- Polyglot Persistence — Each service owns its data store. PostgreSQL for transactions, MongoDB for documents, Redis for session cache.
- Gateway-Level Security — JWT is validated once at the API Gateway. All downstream services receive trusted identity headers — no repeated token parsing.
- PDF Invoice Generation — On every successful order, the notification service auto-generates a styled PDF invoice (via OpenPDF / iText) and attaches it to the confirmation email. Invoices are stored as binary in MongoDB and downloadable anytime via
GET /api/notifications/invoice/{orderId}. - Kubernetes-Ready — HPA-configured with CPU/memory scaling, zero-downtime rolling updates, liveness/readiness probes, and Prometheus scraping out of the box.
- Full Observability — A dedicated logging service aggregates structured logs across all 15 services with cross-service traceId correlation and a 30-day TTL.
- Analytics Service (new) — CQRS pattern with a dedicated analytics PostgreSQL DB separate from transactional DBs. Batch Kafka consumer delivers ~500 events/sec throughput. Tracks revenue, top customers, and daily order trends.
- Poison Pill + DLQ Pattern — The analytics consumer detects malformed/unprocessable events and routes them to
order-analytics-dlqfor safe reprocessing without blocking the main consumer. - Idempotency via
event_id— A unique constraint onevent_idin the analytics DB prevents duplicate event ingestion even under consumer restarts or replay. - Prometheus + Grafana Observability — Micrometer-instrumented services expose HTTP latency p50/p95/p99, Kafka consumer throughput, JVM heap usage, and HikariCP connection pool metrics. Grafana dashboards at
localhost:3005. - GitHub Actions CI Pipeline — Builds all 16 modules on every push to ensure the multi-module Maven project compiles cleanly across the entire codebase.
graph TB
Client([Client]) --> GW[API Gateway :8080]
subgraph Infra
KAFKA[Apache Kafka :9092]
PG[(PostgreSQL :5432)]
MONGO[(MongoDB :27017)]
REDIS[(Redis :6379)]
EUREKA[Eureka :8761]
end
GW --> AUTH[auth-service :8086]
GW --> USER[user-service :8087]
GW --> PRODUCT[product-service :8088]
GW --> SELLER[seller-service :8091]
GW --> CART[cart-service :8089]
GW --> WISH[wishlist-service :8090]
GW --> ORDER[order-service :8081]
GW --> INV[inventory-service :8082]
GW --> PAY[payment-service :8083]
GW --> SHIP[shipping-service :8085]
GW --> NOTIF[notification-service :8084]
GW --> LOG[logging-service :8092]
GW --> ANALYTICS[analytics-service :8093]
ORDER -- order-events --> KAFKA
KAFKA -- order-events --> INV
INV -- inventory-events --> KAFKA
KAFKA -- inventory-events --> ORDER
KAFKA -- inventory-events --> PAY
PAY -- payment-events --> KAFKA
KAFKA -- payment-events --> ORDER
KAFKA -- payment-events --> SHIP
KAFKA -- payment-events --> NOTIF
SHIP -- shipping-events --> KAFKA
KAFKA -- shipping-events --> ORDER
KAFKA -- shipping-events --> NOTIF
KAFKA -- order-events --> ANALYTICS
WISH -- REST --> CART
SELLER -- REST --> PRODUCT
SELLER -- REST --> ORDER
CART -- REST --> PRODUCT
CART -- REST --> INV
AUTH --- PG
USER --- PG
PRODUCT --- PG
SELLER --- PG
ORDER --- PG
PAY --- PG
SHIP --- PG
ANALYTICS --- PG
INV --- MONGO
WISH --- MONGO
NOTIF --- MONGO
LOG --- MONGO
CART --- REDIS
The core checkout flow uses a choreography-based saga — no central orchestrator. Each service reacts to events and publishes the next event in the chain.
sequenceDiagram
participant C as Client
participant OS as Order Service
participant IS as Inventory Service
participant PS as Payment Service
participant SS as Shipping Service
participant NS as Notification Service
C->>OS: POST /api/orders
OS->>OS: Save Order (PENDING)
OS-->>IS: OrderCreatedEvent [order-events]
IS->>IS: Check & reserve stock
alt Stock available
IS-->>OS: InventoryReservedEvent [inventory-events]
IS-->>PS: InventoryReservedEvent [inventory-events]
OS->>OS: Status → INVENTORY_RESERVED
PS->>PS: Process payment (Razorpay / Mock)
alt Payment success
PS-->>OS: PaymentProcessedEvent [payment-events]
PS-->>SS: PaymentProcessedEvent [payment-events]
OS->>OS: Status → PAYMENT_COMPLETED
SS->>SS: Create shipment
SS-->>OS: ShipmentProcessedEvent [shipping-events]
SS-->>NS: ShipmentProcessedEvent [shipping-events]
OS->>OS: Status → SHIPPED
NS->>NS: Send email to customer
else Payment failed
PS-->>OS: PaymentFailedEvent [payment-events]
OS->>OS: Status → FAILED
end
else Stock unavailable
IS-->>OS: InventoryReservationFailedEvent [inventory-events]
OS->>OS: Status → CANCELLED
end
The fastest way to run Zexxity is with Docker Compose. It spins up Kafka (KRaft), PostgreSQL (7 databases), MongoDB, Redis, and Kafka UI — no manual database setup required.
| Tool | Version |
|---|---|
| Java | 21+ |
| Docker Desktop | Latest |
| Maven | 3.9+ |
Create a .env file in the project root:
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres123
# JWT — HMAC-SHA256 Base64-encoded secret
JWT_SECRET=your_jwt_secret_here
# Google OAuth2
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
# Razorpay (payment gateway)
RAZORPAY_KEY_ID=your_razorpay_key_id
RAZORPAY_KEY_SECRET=your_razorpay_key_secret
# Email — auth-service (AWS SES)
MAIL_USERNAME=your_ses_smtp_username
MAIL_PASSWORD=your_ses_smtp_password
# Email — notification-service (Resend)
RESEND_API_KEY=your_resend_api_keydocker-compose up -dThis starts:
| Container | Port | Notes |
|---|---|---|
| Kafka (KRaft) | 9092 |
No ZooKeeper required |
| PostgreSQL 16 | 5432 |
Auto-creates 7 databases |
| MongoDB | 27017 |
|
| Redis 7 | 6379 |
Persistence enabled |
| Kafka UI | 8071 |
http://localhost:8071 |
./mvnw clean install -DskipTests1. service-registry ← Eureka must be up first
2. api-gateway
3. auth-service
4. All remaining services (any order)
Kafka reset on Windows? Run
FIX-KAFKA.ps1(PowerShell) orFIX-KAFKA.bat.
| Service | Port | Database |
|---|---|---|
| service-registry | 8761 |
— |
| api-gateway | 8080 |
— |
| auth-service | 8086 |
PostgreSQL auth_db |
| user-service | 8087 |
PostgreSQL user_db |
| product-service | 8088 |
PostgreSQL product_db |
| seller-service | 8091 |
PostgreSQL seller_db |
| cart-service | 8089 |
Redis |
| wishlist-service | 8090 |
MongoDB wishlist_db |
| order-service | 8081 |
PostgreSQL order_db |
| inventory-service | 8082 |
MongoDB inventory |
| payment-service | 8083 |
PostgreSQL payment_db |
| shipping-service | 8085 |
PostgreSQL shipping_db |
| notification-service | 8084 |
MongoDB notification_db |
| logging-service | 8092 |
MongoDB logging_db |
| analytics-service | 8093 |
PostgreSQL analytics_db |
| Kafka UI | 8071 |
— |
| Prometheus | 9095 |
— |
| Grafana | 3005 |
— |
All services register with Eureka and are reachable through the API Gateway at http://localhost:8080.
Port: 8761 | Eureka Server
Central service discovery. Every microservice registers here on startup and the API Gateway uses it for load-balanced routing (lb://service-name).
- Dashboard: http://localhost:8761
- Infrastructure only — no REST API.
Port: 8080 | Spring Cloud Gateway
Single entry point for all client traffic. Validates JWT and injects identity headers (X-User-Id, X-User-Email, X-User-Role) into every downstream request.
JWT Validation
- Algorithm: HMAC-SHA256
- Token source:
Authorization: Bearer <token> - Success → injects identity headers
- Failure →
401 Unauthorized
Route Table
| Path | Service | JWT |
|---|---|---|
/api/auth/** |
auth-service | ❌ |
/api/products/** GET |
product-service | ❌ |
/api/orders/** |
order-service | ✅ |
/api/payments/** |
payment-service | ✅ |
/api/inventory/** |
inventory-service | ✅ |
/api/shipping/** |
shipping-service | ✅ |
/api/users/** |
user-service | ✅ |
/api/cart/** |
cart-service | ✅ |
/api/wishlist/** |
wishlist-service | ✅ |
/api/seller/** |
seller-service | ✅ |
/api/notifications/** |
notification-service | ✅ |
/api/logs/** |
logging-service | ✅ |
/api/analytics/** |
analytics-service | ✅ |
Type: Shared Maven module (no server)
Contains all shared Kafka event classes and DTOs. Every saga participant imports this library, ensuring type-safe event contracts across services.
Base Event (all events extend BaseEvent)
| Field | Type | Purpose |
|---|---|---|
eventId |
UUID | Unique event identifier |
correlationId |
UUID | Links all events in one saga transaction |
timestamp |
Instant | Event creation time |
Kafka Events
| Class | Topic | Key Fields |
|---|---|---|
OrderCreatedEvent |
order-events |
orderId, customerId, items, totalAmount |
InventoryReservedEvent |
inventory-events |
orderId, customerId, totalAmount |
InventoryReservationFailedEvent |
inventory-events |
orderId, reason |
PaymentProcessedEvent |
payment-events |
orderId, paymentId, customerId |
PaymentFailedEvent |
payment-events |
orderId, reason |
ShipmentProcessedEvent |
shipping-events |
orderId, shipmentId, trackingNumber |
ShipmentFailedEvent |
shipping-events |
orderId, reason |
Port: 8086 | Database: PostgreSQL auth_db
Handles registration, email OTP verification, login, JWT issuance, refresh token rotation, password reset, and Google OAuth2.
| Token | Lifetime |
|---|---|
| Access token | 15 minutes |
| Refresh token | 7 days (rotated on each use) |
| OTP | 15 minutes (6-digit, via AWS SES) |
API Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register |
Public | Register — sends email OTP |
| POST | /api/auth/verify-email |
Public | Verify with OTP |
| POST | /api/auth/login |
Public | Login → accessToken + refreshToken |
| POST | /api/auth/refresh |
Public | Rotate refresh token |
| POST | /api/auth/logout |
Public | Revoke refresh token |
| POST | /api/auth/forgot-password |
Public | Send reset OTP |
| POST | /api/auth/reset-password |
Public | Reset with OTP |
| GET | /api/auth/me |
JWT | Get current user |
| GET | /api/auth/oauth2/authorize/google |
Public | Start Google OAuth2 |
Login Response
{
"accessToken": "eyJ...",
"refreshToken": "uuid-token",
"tokenType": "Bearer",
"expiresIn": 900,
"userId": "uuid",
"email": "john@example.com",
"role": "ROLE_USER"
}Database Schema
users table
| Column | Type | Notes |
|---|---|---|
| id | UUID PK | auto-generated |
| name | VARCHAR | |
| VARCHAR UNIQUE | ||
| password_hash | VARCHAR | null for OAuth2 users |
| provider | ENUM | LOCAL, GOOGLE |
| role | ENUM | ROLE_USER, ROLE_SELLER, ROLE_ADMIN |
| email_verified | BOOLEAN | |
| otp / otp_expires_at | VARCHAR / TIMESTAMP | 15-min window |
refresh_tokens table
| Column | Type | Notes |
|---|---|---|
| id | UUID PK | |
| token | VARCHAR UNIQUE | |
| user_id | UUID FK → users | |
| expires_at | TIMESTAMP | |
| revoked | BOOLEAN |
Port: 8087 | Database: PostgreSQL user_db
Manages user profiles and shipping addresses. Uses the X-User-Id header (injected by gateway) to identify callers — shares the same UUID as auth-service.
API Endpoints
| Method | Path | Description |
|---|---|---|
| POST/GET/PUT | /api/users/profile |
Create / read / update profile |
| DELETE | /api/users/profile |
Deactivate account |
| PATCH | /api/users/profile/preferences |
Language, currency, notifications |
| GET/POST | /api/users/addresses |
List / add addresses |
| PUT/DELETE | /api/users/addresses/{id} |
Update / delete address |
| PATCH | /api/users/addresses/{id}/default |
Set default address |
Port: 8088 | Database: PostgreSQL product_db
Full product catalog with category hierarchy, full-text search, price filtering, pagination, and sorting.
API Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /api/products |
Create product (seller only) |
| POST | /api/products/bulk |
Bulk create |
| GET | /api/products/{id} |
Get by ID or SKU |
| PUT/PATCH/DELETE | /api/products/{id} |
Update / change status / delete |
| GET | /api/products |
Search with filters |
| GET | /api/products/my-products |
Seller's own listings |
Search params: keyword, categoryId, minPrice, maxPrice, brand, page, size, sortBy, sortDir
Port: 8091 | Database: PostgreSQL seller_db
Merchant profile management and verification lifecycle. Delegates product and order lookups to their respective services via REST.
API Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /api/seller/register |
Register as seller |
| GET/PUT | /api/seller/profile |
View / update store profile |
| GET | /api/seller/products |
Seller's product listings |
| GET | /api/seller/orders |
Orders with seller's items |
| GET | /api/seller/analytics |
Revenue, order count, avg value |
| GET/POST | /api/admin/sellers/** |
Admin verification |
Verification status: PENDING → VERIFIED / REJECTED / SUSPENDED
Port: 8089 | Database: Redis (TTL: 7 days)
Session-based cart stored in Redis. Validates stock with inventory-service before adding items.
API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/cart |
Get cart (auto-created if empty) |
| POST | /api/cart/items |
Add item (validates stock) |
| PUT | /api/cart/items/{productId} |
Update quantity |
| DELETE | /api/cart/items/{productId} |
Remove item |
| DELETE | /api/cart |
Clear cart |
Redis key: cart:{userId} → serialized Cart JSON
Port: 8090 | Database: MongoDB wishlist_db
Save products for later. Compound unique index on {userId, productId} prevents duplicates (returns 409). Supports one-click move-to-cart.
API Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /api/wishlist/add |
Add to wishlist |
| DELETE | /api/wishlist/remove/{productId} |
Remove |
| GET | /api/wishlist/{userId} |
Get full wishlist |
| POST | /api/wishlist/move-to-cart/{productId} |
Move to cart |
Port: 8081 | Database: PostgreSQL order_db
Creates orders and drives the entire saga by publishing OrderCreatedEvent then reacting to events from three downstream services.
API Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /api/orders |
Place order — triggers saga |
| GET | /api/orders/{orderId} |
Get by ID |
| GET | /api/orders/customer/{customerId} |
Orders by customer |
Kafka: Publishes → order-events | Consumes → inventory-events, payment-events, shipping-events
Order Status Lifecycle
PENDING → INVENTORY_CHECKING → INVENTORY_RESERVED → PAYMENT_PROCESSING
→ PAYMENT_COMPLETED → SHIPPING_PROCESSING → SHIPPED → COMPLETED
↘ CANCELLED (inventory fail)
↘ FAILED (payment / shipping fail)
Port: 8082 | Database: MongoDB inventory
Manages stock levels. Reserves stock on order, publishes success/failure, and emits low-stock alerts below a configurable threshold.
API Endpoints
| Method | Path | Description |
|---|---|---|
| POST/GET | /api/inventory |
Create / list records |
| GET | /api/inventory/product/{productId} |
Stock by product |
| POST | /api/inventory/{id}/restock |
Add units |
| GET | /api/inventory/check |
Check availability |
| GET | /api/inventory/low-stock |
Low stock items |
Kafka: Consumes → order-events | Publishes → inventory-events, inventory-alerts
Port: 8083 | Database: PostgreSQL payment_db
Supports Razorpay, Stripe, and a Mock adapter. Handles automatic saga-driven payments and manual frontend-initiated flows with refund support.
Payment Adapters: Razorpay · Stripe (stub) · Mock (dev/test)
API Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /api/payments/initiate |
Create gateway order |
| POST | /api/payments/verify |
Verify signature & capture |
| POST | /api/payments/refund |
Full or partial refund |
| GET | /api/payments/{paymentId} |
Get payment |
| GET | /api/payments/order/{orderId} |
Payment by order |
Kafka: Consumes → inventory-events | Publishes → payment-events
Status: PENDING → AUTHORIZED → COMPLETED / FAILED / REFUND_PENDING → REFUNDED
Port: 8085 | Database: PostgreSQL shipping_db
Creates shipments when payment completes. Assigns tracking numbers and carrier, then publishes ShipmentProcessedEvent.
API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/shipping/{shipmentId} |
Get shipment |
| GET | /api/shipping/order/{orderId} |
Shipment by order |
Kafka: Consumes → payment-events | Publishes → shipping-events
Port: 8084 | Database: MongoDB notification_db
Sends transactional emails via Resend and stores in-app notifications. Failed deliveries are retried up to 3 times via a scheduled job.
API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/notifications |
All notifications |
| GET | /api/notifications/unread |
Unread only |
| GET | /api/notifications/unread/count |
Badge count |
| PATCH | /api/notifications/{id}/read |
Mark as read |
| PATCH | /api/notifications/read-all |
Mark all read |
| GET | /api/notifications/invoice/{orderId} |
Download PDF invoice |
PDF Invoice Generation
On every ORDER_PLACED event, the notification service:
- Generates a styled PDF invoice using OpenPDF (iText) — includes order number, itemised table, subtotal, tax, and grand total
- Attaches the PDF to the order confirmation email sent via Resend
- Stores the raw PDF bytes in MongoDB alongside the notification record
- Exposes it for re-download at any time via
GET /api/notifications/invoice/{orderId}→ returnsapplication/pdf
Kafka: Consumes → order-events, payment-events, shipping-events
Email types: ORDER_PLACED · PAYMENT_SUCCESS · PAYMENT_FAILED · ORDER_SHIPPED · ORDER_DELIVERED
Port: 8092 | Database: MongoDB logging_db
Intercepts all Kafka business events and converts them into structured log entries with cross-service traceId correlation. Logs are auto-expired after 30 days via a MongoDB TTL index.
API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/logs |
Search (serviceName, level, keyword, traceId, date range) |
| GET | /api/logs/{id} |
Single entry |
| GET | /api/logs/trace/{traceId} |
All logs for one saga |
| GET | /api/logs/stats |
Aggregated stats per service/level |
| GET | /api/logs/errors/recent |
Recent errors for monitoring |
Kafka: Consumes → service-logs, order-events, payment-events, inventory-events, shipping-events
Port: 8093 | Database: PostgreSQL analytics_db
Dedicated read-side analytics store implementing the CQRS pattern — all analytical queries run against a separate PostgreSQL database, leaving transactional DBs untouched. Consumes order-events in batches and projects aggregate metrics in real time.
Design Decisions
| Concern | Solution |
|---|---|
| CQRS | Dedicated analytics_db separate from order_db |
| Throughput | Batch Kafka consumer — up to 500 events/batch, ~500 events/sec |
| Idempotency | Unique constraint on event_id — safe for consumer restarts and replay |
| Resilience | Poison pill detection routes bad events to order-analytics-dlq |
Kafka: Consumes → order-events (batch) | Publishes failed events → order-analytics-dlq
API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/analytics/summary |
Total orders, total revenue, unique customers |
| GET | /api/analytics/top-customers |
Top customers ranked by spend |
| GET | /api/analytics/revenue-per-day |
Daily revenue time series |
| GET | /api/analytics/orders |
Paginated order analytics records |
Performance Stats (from load testing)
| Metric | Value |
|---|---|
| Total events ingested | 19,717 |
| Total revenue tracked | ₹1,774,510,283 |
| Consumer throughput | ~500 events/sec |
| Batch size | 500 events/batch |
DLQ Handling
Any event that fails deserialization or violates a DB constraint beyond the idempotency check is forwarded to order-analytics-dlq. A separate consumer can replay or inspect failed events without blocking the main pipeline.
| Topic | Producer | Consumers | Events |
|---|---|---|---|
order-events |
order-service | inventory-service, analytics-service, logging-service | OrderCreatedEvent |
inventory-events |
inventory-service | order-service, payment-service, logging-service | InventoryReservedEvent, InventoryReservationFailedEvent |
inventory-alerts |
inventory-service | ops / monitoring | Low-stock alerts |
payment-events |
payment-service | order-service, shipping-service, notification-service, logging-service | PaymentProcessedEvent, PaymentFailedEvent |
shipping-events |
shipping-service | order-service, notification-service, logging-service | ShipmentProcessedEvent, ShipmentFailedEvent |
service-logs |
any service | logging-service | Explicit log entries |
wishlist-events |
wishlist-service | future use | Wishlist activity |
order-analytics-dlq |
analytics-service | ops / monitoring | Poison pill / failed analytics events |
Kafka configuration (docker-compose)
| Property | Value |
|---|---|
| Mode | KRaft — no ZooKeeper |
| External port | 9092 |
| Internal port | 29092 (container-to-container) |
| Auto topic creation | Enabled |
| UI | http://localhost:8071 |
Manifests live in /k8s. The order-service is the reference deployment and demonstrates the standard pattern for any service.
HPA — order-service
| Property | Value |
|---|---|
| Min replicas | 2 |
| Max replicas | 8 |
| Scale-up trigger | CPU > 60% or Memory > 70% |
| Scale-up speed | +2 pods / 30 s (max 100% increase / 60 s) |
| Scale-down cooldown | 120 s stabilization |
Deployment features
- Zero-downtime rolling update (
maxUnavailable: 0,maxSurge: 1) preStopsleep of 10 s to drain connections before SIGTERM- Separate startup, liveness, and readiness probes via
/actuator/health - Prometheus scraping annotations on pod template
- Pod anti-affinity to spread replicas across nodes
Deploy
# 1. Build image
cd order-service
docker build -t order-service:latest .
# 2. Apply manifests
kubectl apply -f k8s/order-deployment.yaml
kubectl apply -f k8s/order-service.yaml
kubectl apply -f k8s/order-hpa.yamlTo containerize any other service: add a
Dockerfile, createapplication-k8s.ymlwith overrides, then addDeployment+Service+HPAmanifests to/k8s.
Load tests run against order-service (port 8081) directly using Apache JMeter 5.6.3.
Target: POST /api/orders — Samsung Galaxy S26, single item per order.
Tool: load-tests/order-load-test.jmx
| Property | Value |
|---|---|
| Threads (users) | 50 |
| Ramp-up period | 10 seconds |
| Loop count | 300 |
| Total orders placed | 15,000 |
| Metric | Value |
|---|---|
| Min response time | 8 ms |
| Max response time | 24,564 ms |
| Average | 209 ms |
| Median (P50) | 20 ms |
| P90 | 271 ms |
| P95 | 741 ms |
| P99 | 3,374 ms |
| Throughput | 30.3 req/s |
| Error rate | 0.00% |
| Received KB/s | 18.80 |
| Sent KB/s | 11.85 |
| Property | Value |
|---|---|
| Threads (users) | 50 |
| Ramp-up period | 100 seconds |
| Loop count | 300 |
| Total orders placed | 15,000 |
| Metric | Value |
|---|---|
| Min response time | 8 ms |
| Max response time | ~27,000 ms |
| Average | ~209 ms |
| Median (P50) | ~30 ms |
| P90 | ~300 ms |
| P95 | ~1,000 ms |
| P99 | ~7,000 ms |
| Throughput | ~20 req/s |
| Error rate | 0.00% |
| Run | Orders | Threads | Ramp-up | Throughput | Errors |
|---|---|---|---|---|---|
| Warm-up | 15,000 | 50 | 10s | 30.3/s | 0% |
| Sustained | 15,000 | 50 | 100s | ~20/s | 0% |
| Total | 30,000 | 0% |
All 30,000 orders triggered the full Kafka saga:
order-events → inventory-events → payment-events → shipping-events → notification (PDF email)Zero errors across 30,000 requests confirms the saga is stable under sustained concurrent load.
Increasing the number of Kafka partitions allows more consumers to process events in parallel, directly reducing end-to-end saga processing time. The table below shows observed processing times for a full-saga run at equivalent load across three partition configurations.
| Service / Topic | Partitions | Observed Processing Time | Notes |
|---|---|---|---|
order-service (order-events) |
1 | > 30 minutes | Single partition — all events processed sequentially by one consumer |
payment-service (payment-events) |
5 | ~10 minutes | 5-way parallelism — significant reduction in queue depth |
shipping-service (shipping-events) |
10 | ~4 minutes | 10-way parallelism — fastest throughput, minimal consumer lag |
Key takeaway: Moving from 1 partition to 10 partitions reduced processing time by over 87% (from >30 min down to ~4 min). Each additional partition enables an additional consumer instance to process events concurrently, so throughput scales nearly linearly with partition count up to the number of available consumer instances.
Note: Partition count can only be increased on an existing Kafka topic, never decreased. Plan your partition count based on expected peak throughput before going to production.
All services are instrumented with Micrometer and expose a /actuator/prometheus endpoint. Prometheus scrapes these endpoints and Grafana visualises the data.
| Component | Role |
|---|---|
| Micrometer | Metrics instrumentation inside each Spring Boot service |
| Prometheus | Time-series metrics scraping and storage |
| Grafana | Dashboard visualisation and alerting |
| Tool | URL | Credentials |
|---|---|---|
| Prometheus | http://localhost:9095 | — |
| Grafana | http://localhost:3005 | admin / admin |
# Start Prometheus
docker run -d \
--name prometheus \
-p 9095:9090 \
-v $(pwd)/monitoring/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus
# Start Grafana
docker run -d \
--name grafana \
-p 3005:3000 \
-e GF_SECURITY_ADMIN_PASSWORD=admin \
grafana/grafanaOn Windows with PowerShell replace
$(pwd)with${PWD}.
After Grafana starts:
- Open http://localhost:3005 and log in with
admin / admin - Add Prometheus as a data source:
http://host.docker.internal:9095 - Import the dashboard JSON from
monitoring/grafana-dashboard.json
The Grafana dashboard includes 8 panels:
| Panel | Metric |
|---|---|
| HTTP Request Rate | Requests per second across all services |
| HTTP Latency p50 / p95 / p99 | http_server_requests_seconds percentiles |
| Kafka Consumer Lag | Messages behind per topic / consumer group |
| Kafka Throughput | Events ingested per second (analytics consumer) |
| JVM Heap Usage | jvm_memory_used_bytes — heap vs. non-heap |
| JVM GC Pause Time | Garbage collection pause duration |
| HikariCP Active Connections | Active DB connections per service |
| HikariCP Pending Threads | Connection pool saturation indicator |
ecommerce-microservices/
├── pom.xml # Parent POM — Java 21, Spring Boot 3.2.12
├── docker-compose.yml # Kafka, PostgreSQL, MongoDB, Redis, Kafka UI
├── .env # Environment variables
├── scripts/
│ └── create-multiple-postgres-dbs.sh # Auto-creates 7 PostgreSQL databases
├── k8s/ # Kubernetes manifests (order-service reference)
├── monitoring/ # Prometheus & Grafana config
│ ├── prometheus.yml # Scrape configs for all services
│ └── grafana-dashboard.json # Pre-built Grafana dashboard (8 panels)
├── common-library/ # Shared Kafka events & DTOs
├── service-registry/ # Eureka server
├── api-gateway/ # Spring Cloud Gateway + JWT filter
├── auth-service/ # Authentication, JWT, OAuth2
├── user-service/ # User profiles & addresses
├── product-service/ # Product catalog & categories
├── seller-service/ # Merchant management & verification
├── cart-service/ # Redis shopping cart
├── wishlist-service/ # MongoDB wishlists
├── order-service/ # Orders + saga orchestration
├── inventory-service/ # Stock management
├── payment-service/ # Razorpay / Stripe / Mock payments
├── shipping-service/ # Shipment tracking
├── notification-service/ # Email + in-app notifications
├── logging-service/ # Centralized log aggregation (30-day TTL)
└── analytics-service/ # CQRS analytics — batch Kafka consumer + REST API
| Category | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.2.12, Spring Cloud 2023.0.2 |
| Service Discovery | Netflix Eureka |
| API Gateway | Spring Cloud Gateway |
| Messaging | Apache Kafka (KRaft, Confluent 7.5.0) |
| Relational DB | PostgreSQL 16 (7 isolated databases) |
| Document DB | MongoDB (4 databases) |
| Cache | Redis 7 |
| Auth | JWT (JJWT 0.12), Spring Security, Google OAuth2 |
| Payment | Razorpay, Stripe (stub), Mock |
| AWS SES (auth-service), Resend (notification-service) | |
| Analytics | Spring Kafka batch consumer, Spring Data JPA, PostgreSQL |
| Observability | Micrometer, Prometheus, Grafana |
| CI/CD | GitHub Actions |
| Build | Maven multi-module |
| Containerization | Docker Compose, Kubernetes (HPA) |
| Code Generation | Lombok, MapStruct |
Built with ☕ and Spring Boot · Java 21 · Apache Kafka