A distributed event ticket booking platform demonstrating polyglot microservice architecture with event-driven choreography saga.
- What is TicketFlow?
- Architecture Overview
- Booking Saga Flow
- Tech Stack
- Project Structure
- Quick Start
- API Reference Summary
- Environment Variables
- Development Tools
- Kafka Topics
- Why Polyglot?
- Why Kafka over RabbitMQ?
- Contributing
- License
TicketFlow is a production-grade reference implementation of a polyglot microservice platform for selling and managing event tickets. It is designed to demonstrate:
- Polyglot architecture β each service uses the runtime and framework best suited to its workload (Java Spring Boot for transactional services, Python FastAPI for document-centric services, and Bun/Elysia for high-throughput edge logic).
- Event-driven choreography saga β the entire booking lifecycle (seat locking, payment processing, confirmation) is coordinated asynchronously through Apache Kafka with no central orchestrator.
- Database-per-service β every service owns and manages its own datastore; no shared databases, no cross-service SQL joins.
- Production-ready observability β Prometheus metrics, Grafana dashboards, Jaeger distributed tracing, and Loki log aggregation are included out of the box.
- Cloud-native scalability β Kubernetes manifests with KEDA-based autoscaling that reacts to Kafka consumer lag, allowing each service to scale independently under load.
Whether you are exploring microservice design patterns, evaluating polyglot technology choices, or using TicketFlow as a scaffold for a real ticketing product, this repository provides a complete, runnable reference.
graph TB
subgraph "Client Layer"
FE["Frontend\nReact + Vite\n:5173"]
end
subgraph "API Gateway Layer"
GW["API Gateway\nBun + Elysia\n:3000"]
end
subgraph "Java Services"
US["User Service\nSpring Boot 3\n:3001"]
BS["Booking Service\nSpring Boot 3\n:3003"]
end
subgraph "Python Services"
ES["Event Service\nFastAPI\n:3002"]
PS["Payment Service\nFastAPI\n:3005"]
NS["Notification Service\nFastAPI\n:3006"]
end
subgraph "Bun Services"
IS["Inventory Service\nElysia\n:3004"]
end
subgraph "Message Bus"
KF["Apache Kafka"]
end
subgraph "Databases"
PG[("PostgreSQL\nUsers / Bookings / Inventory")]
MG[("MongoDB\nEvents / Payments / Notifications")]
RD[("Redis\nSeat Locks")]
end
FE --> GW
GW --> US
GW --> ES
GW --> BS
GW --> IS
GW --> PS
GW --> NS
US --> PG
BS --> PG
IS --> PG
IS --> RD
ES --> MG
PS --> MG
NS --> MG
BS --> KF
IS --> KF
PS --> KF
NS --> KF
US --> KF
ES --> KF
KF --> IS
KF --> PS
KF --> NS
KF --> BS
All client requests enter through the API Gateway (Bun + Elysia), which handles JWT validation, route proxying, and request logging. For reads and simple commands the gateway forwards directly to the target service over HTTP. For the booking lifecycle the gateway accepts the initial request and returns 202 Accepted; the client then polls for the result while the saga progresses asynchronously via Kafka.
The booking lifecycle is the centrepiece of TicketFlow's event-driven design. No service calls another service directly; every step is triggered by a Kafka event.
sequenceDiagram
participant C as Client
participant GW as Gateway
participant BS as Booking Service
participant KF as Kafka
participant IS as Inventory Service
participant PS as Payment Service
participant NS as Notification Service
C->>GW: POST /api/bookings
GW->>BS: Forward request
BS->>BS: Save booking (PENDING)
BS->>KF: booking.initiated
BS-->>C: 202 Accepted {bookingId}
KF->>IS: booking.initiated
IS->>IS: Lock seats (Redis SETNX)
IS->>IS: Update DB (LOCKED)
IS->>KF: seats.locked
KF->>BS: seats.locked
BS->>KF: payment.requested
KF->>PS: payment.requested
PS->>PS: Process payment
PS->>PS: Save to MongoDB
PS->>KF: payment.processed (SUCCESS)
KF->>BS: payment.processed
BS->>BS: Update booking (CONFIRMED)
BS->>KF: booking.confirmed
BS->>KF: seats.confirm
KF->>IS: seats.confirm
IS->>IS: Update DB (RESERVED)
IS->>IS: Clear Redis locks
KF->>NS: booking.confirmed
NS->>NS: Send confirmation email
NS->>NS: Save notification log
C->>GW: GET /api/bookings/{id}
GW->>BS: Forward request
BS-->>C: {status: "CONFIRMED"}
If any step fails the saga runs compensating transactions in reverse:
| Failure Point | Compensating Event | Compensating Action |
|---|---|---|
| Seat lock fails | seats.lock-failed |
Booking Service marks booking FAILED |
| Payment fails | payment.processed (FAILED) |
Inventory releases locks via seats.release |
| Booking Service crash | Kafka retry / DLT | Message replayed on restart |
| Service | Language | Runtime | Framework | Database | Kafka Role | Port |
|---|---|---|---|---|---|---|
| API Gateway | TypeScript | Bun 1.1 | Elysia | β | β | 3000 |
| User Service | Java | JDK 21 | Spring Boot 3 | PostgreSQL | Producer | 3001 |
| Event Service | Python | CPython 3.12 | FastAPI | MongoDB | Producer | 3002 |
| Booking Service | Java | JDK 21 | Spring Boot 3 | PostgreSQL | Producer + Consumer | 3003 |
| Inventory Service | TypeScript | Bun 1.1 | Elysia | PostgreSQL + Redis | Producer + Consumer | 3004 |
| Payment Service | Python | CPython 3.12 | FastAPI | MongoDB | Producer + Consumer | 3005 |
| Notification Service | Python | CPython 3.12 | FastAPI | MongoDB | Consumer | 3006 |
| Frontend | TypeScript | Node 20 | React 18 + Vite | β | β | 5173 |
| Component | Technology | Purpose |
|---|---|---|
| Message Broker | Apache Kafka | Async inter-service communication |
| Relational DB | PostgreSQL 16 | Users, bookings, seat inventory |
| Document DB | MongoDB 7 | Events, payments, notifications |
| Cache / Lock | Redis 7 | Distributed seat locking, TTL-based |
| Observability | Prometheus + Grafana | Metrics and dashboards |
| Tracing | Jaeger + OpenTelemetry | Distributed trace correlation |
| Log Aggregation | Loki + Promtail | Centralised log storage and search |
| Local Email | Mailpit | Captures outbound email in dev |
| Kafka UI | Redpanda Console | Browse topics and messages |
| Mongo UI | Mongo Express | Browse MongoDB collections |
ticketflow/
βββ README.md
βββ Makefile # Developer shortcuts
βββ docker-compose.yml # Full dev stack
βββ docker-compose.prod.yml # Production overrides
βββ .env.example # Template environment file
β
βββ gateway/ # API Gateway (Bun + Elysia)
β βββ src/
β β βββ index.ts
β β βββ routes/
β β βββ middleware/
β β βββ config.ts
β βββ package.json
β βββ tsconfig.json
β βββ Dockerfile
β
βββ services/
β βββ user-service/ # Java + Spring Boot 3
β β βββ src/main/java/
β β βββ src/main/resources/
β β βββ pom.xml
β β βββ Dockerfile
β β
β βββ event-service/ # Python + FastAPI
β β βββ app/
β β β βββ main.py
β β β βββ routers/
β β β βββ models/
β β β βββ kafka/
β β βββ requirements.txt
β β βββ Dockerfile
β β
β βββ booking-service/ # Java + Spring Boot 3
β β βββ src/main/java/
β β βββ src/main/resources/
β β βββ pom.xml
β β βββ Dockerfile
β β
β βββ inventory-service/ # Bun + Elysia
β β βββ src/
β β βββ package.json
β β βββ Dockerfile
β β
β βββ payment-service/ # Python + FastAPI
β β βββ app/
β β βββ requirements.txt
β β βββ Dockerfile
β β
β βββ notification-service/ # Python + FastAPI
β βββ app/
β βββ requirements.txt
β βββ Dockerfile
β
βββ frontend/ # React 18 + Vite
β βββ src/
β βββ public/
β βββ package.json
β βββ vite.config.ts
β βββ Dockerfile
β
βββ infra/
β βββ kafka/
β β βββ topics.sh # Topic creation script
β βββ postgres/
β β βββ init.sql # Schema initialisation
β βββ mongo/
β β βββ init.js # Collection + index setup
β βββ prometheus/
β β βββ prometheus.yml
β βββ grafana/
β β βββ dashboards/
β βββ jaeger/
β βββ loki/
β
βββ k8s/
β βββ namespace.yaml
β βββ secrets/
β β βββ app-secrets.yaml.example
β βββ gateway/
β βββ services/
β βββ keda/
β βββ scaledobjects.yaml
β
βββ scripts/
β βββ seed.sh
β βββ health-check.sh
β βββ e2e-booking.sh
β
βββ docs/
βββ architecture.md
βββ development.md
βββ deployment.md
βββ event-catalog.md
βββ api-reference.md
βββ observability.md
βββ adr/
β βββ 001-polyglot-architecture.md
β βββ 002-kafka-over-rabbitmq.md
β βββ 003-async-saga-pattern.md
β βββ 004-database-per-service.md
βββ services/
βββ gateway.md
βββ user-service.md
βββ event-service.md
βββ booking-service.md
βββ inventory-service.md
βββ payment-service.md
βββ notification-service.md
| Tool | Minimum Version | Install |
|---|---|---|
| Docker | 24.x | docs.docker.com |
| Docker Compose | 2.x | Bundled with Docker Desktop |
| Make | any | brew install make / apt install make |
| Bun | 1.1+ | curl -fsSL https://bun.sh/install | bash |
| JDK | 21 | sdk install java 21-graalce |
| Python | 3.12 | pyenv install 3.12 |
Note: For Docker Compose development you only strictly need Docker + Make. The language runtimes are required only if you want to run individual services outside of containers.
git clone https://github.com/your-org/ticketflow.git
cd ticketflow
# Copy the example environment file and review/edit as needed
make setup# Starts Kafka, databases, all microservices, and the frontend
make devThis command:
- Pulls or builds all Docker images.
- Starts Zookeeper + Kafka and waits for the broker to be healthy.
- Creates all Kafka topics via
infra/kafka/topics.sh. - Starts PostgreSQL, MongoDB, and Redis; runs migrations and index creation.
- Starts all eight application services.
- Starts Prometheus, Grafana, Jaeger, Loki, Mailpit, and Redpanda Console.
# Creates sample venues, events, and a test user account
make seedCredentials for the seeded test user:
- Email:
test@ticketflow.dev - Password:
Test1234!
make healthExpected output:
gateway β http://localhost:3000/health
user-service β http://localhost:3001/actuator/health
event-service β http://localhost:3002/health
booking-service β http://localhost:3003/actuator/health
inventory-service β http://localhost:3004/health
payment-service β http://localhost:3005/health
notification-service β http://localhost:3006/health
frontend β http://localhost:5173
# Guided script that registers a user, creates a booking, and polls for confirmation
make e2eAll requests go through the gateway at http://localhost:3000. Protected routes require Authorization: Bearer <token>.
| Service | Method | Path | Auth | Description |
|---|---|---|---|---|
| Gateway | GET | /health |
No | Gateway health check |
| User | POST | /api/users/register |
No | Register a new user |
| User | POST | /api/users/login |
No | Authenticate, receive JWT |
| User | GET | /api/users/me |
Yes | Get current user profile |
| Event | GET | /api/events |
No | List all upcoming events |
| Event | GET | /api/events/:id |
No | Get event details |
| Event | POST | /api/events |
Yes (admin) | Create a new event |
| Event | PUT | /api/events/:id |
Yes (admin) | Update event |
| Event | POST | /api/venues |
Yes (admin) | Create a venue |
| Event | GET | /api/venues/:id |
No | Get venue details |
| Inventory | GET | /api/inventory/events/:eventId/seats |
No | List seat availability |
| Booking | POST | /api/bookings |
Yes | Initiate a booking (async) |
| Booking | GET | /api/bookings/my |
Yes | List user's bookings |
| Booking | GET | /api/bookings/:id |
Yes | Get booking status |
| Booking | POST | /api/bookings/:id/cancel |
Yes | Cancel a booking |
| Payment | GET | /api/payments/:id |
Yes | Get payment details |
| Payment | GET | /api/payments/booking/:bookingId |
Yes | Get payment for booking |
| Notification | GET | /api/notifications/recent |
Yes | Recent notifications |
| Notification | GET | /api/notifications/health |
No | Notification service health |
Full request/response schemas and error codes are documented in docs/api-reference.md.
The .env.example file contains all variables. Run make setup to copy it to .env.
| Variable | Description | Default | Required |
|---|---|---|---|
JWT_SECRET |
Secret key for JWT signing | β | Yes |
JWT_EXPIRY_HOURS |
Token lifetime in hours | 24 |
No |
POSTGRES_USER |
PostgreSQL superuser | ticketflow |
Yes |
POSTGRES_PASSWORD |
PostgreSQL password | β | Yes |
POSTGRES_DB |
Default database name | ticketflow |
Yes |
MONGO_INITDB_ROOT_USERNAME |
MongoDB root user | ticketflow |
Yes |
MONGO_INITDB_ROOT_PASSWORD |
MongoDB root password | β | Yes |
REDIS_PASSWORD |
Redis password | β | Yes |
KAFKA_BOOTSTRAP_SERVERS |
Kafka broker address | kafka:9092 |
Yes |
USER_SERVICE_URL |
Internal URL for user service | http://user-service:3001 |
Yes |
EVENT_SERVICE_URL |
Internal URL for event service | http://event-service:3002 |
Yes |
BOOKING_SERVICE_URL |
Internal URL for booking service | http://booking-service:3003 |
Yes |
INVENTORY_SERVICE_URL |
Internal URL for inventory service | http://inventory-service:3004 |
Yes |
PAYMENT_SERVICE_URL |
Internal URL for payment service | http://payment-service:3005 |
Yes |
NOTIFICATION_SERVICE_URL |
Internal URL for notification service | http://notification-service:3006 |
Yes |
SMTP_HOST |
SMTP server host | mailpit |
Yes |
SMTP_PORT |
SMTP server port | 1025 |
Yes |
EMAIL_FROM |
Sender address for notifications | noreply@ticketflow.dev |
Yes |
STRIPE_SECRET_KEY |
Stripe API key (payment) | β | Yes |
STRIPE_WEBHOOK_SECRET |
Stripe webhook signing secret | β | Yes |
PROMETHEUS_ENABLED |
Enable Prometheus metrics | true |
No |
OTEL_EXPORTER_JAEGER_ENDPOINT |
Jaeger collector endpoint | http://jaeger:14268/api/traces |
No |
LOG_LEVEL |
Application log level | info |
No |
| Tool | URL | Description |
|---|---|---|
| API Gateway | http://localhost:3000 | Entry point for all API requests |
| Frontend | http://localhost:5173 | React application (Vite dev server) |
| Redpanda Console (Kafka UI) | http://localhost:8080 | Browse topics, consumer groups, messages |
| Mongo Express | http://localhost:8081 | Browse MongoDB collections |
| Mailpit | http://localhost:8025 | Captures all outbound email (dev only) |
| Topic | Producer | Consumers | Purpose |
|---|---|---|---|
ticketflow.user.registered |
User Service | Notification Service | Welcome email trigger |
ticketflow.event.created |
Event Service | β | Event lifecycle event |
ticketflow.booking.initiated |
Booking Service | Inventory Service | Start seat lock step |
ticketflow.seats.locked |
Inventory Service | Booking Service | Proceed to payment step |
ticketflow.seats.lock-failed |
Inventory Service | Booking Service | Abort saga |
ticketflow.seats.confirm |
Booking Service | Inventory Service | Finalise seat reservation |
ticketflow.seats.release |
Booking Service / Payment Service | Inventory Service | Release seats on failure |
ticketflow.payment.requested |
Booking Service | Payment Service | Trigger payment processing |
ticketflow.payment.processed |
Payment Service | Booking Service | Payment result |
ticketflow.booking.confirmed |
Booking Service | Notification Service | Send confirmation email |
ticketflow.booking.failed |
Booking Service | Notification Service | Send failure notification |
ticketflow.booking.cancelled |
Booking Service | Notification Service, Inventory Service | Cancel flow |
Full payload schemas in docs/event-catalog.md.
Each technology was chosen to match the characteristics of its service.
| Service | Runtime | Rationale |
|---|---|---|
| API Gateway | Bun + Elysia | Extremely low startup time, high throughput for proxying. Bun's native fetch is faster than Node's for proxy workloads. |
| User Service | Java + Spring Boot 3 | Strong type safety, mature Spring Security for JWT, Spring Data JPA for transactional user management. Virtual threads (JDK 21) handle high concurrency. |
| Event Service | Python + FastAPI | Event data is document-centric and schema-flexible. FastAPI's automatic OpenAPI generation suits the read-heavy event catalog. |
| Booking Service | Java + Spring Boot 3 | Booking records require ACID guarantees. Spring Kafka integration supports exactly-once semantics. |
| Inventory Service | Bun + Elysia | Seat availability queries are extremely hot. Bun's performance on tight loops and Redis commands is competitive with Go for this workload. |
| Payment Service | Python + FastAPI | Payment provider SDKs (Stripe) have first-class Python support. Async FastAPI handles webhooks and background tasks naturally. |
| Notification Service | Python + FastAPI | Email templating (Jinja2) and SMTP are idiomatic in Python. Low-throughput service where iteration speed matters more than raw performance. |
TicketFlow originally used RabbitMQ. The migration to Kafka was driven by:
- Event log replay β Kafka retains messages for a configurable period. Services can replay missed events from their last committed offset on restart.
- Consumer groups β Multiple instances of the same service share a consumer group and automatically partition work without additional coordination.
- KEDA integration β KEDA's Kafka trigger scales pod replicas based on consumer lag.
- Partition-based ordering β Booking events for the same
bookingIdare routed to the same partition, guaranteeing ordered processing per booking. - Saga auditability β The Kafka topic acts as an immutable audit log of every saga step.
Full rationale in docs/adr/002-kafka-over-rabbitmq.md.
- Fork the repository.
- Create a feature branch:
git checkout -b feat/your-feature. - Make changes, add tests, ensure
make testpasses. - Open a pull request against
main.
Code style per language:
- Java: Google Java Style Guide
- Python: Black formatter + isort (
make lint) - TypeScript: ESLint + Prettier (
bun run lint)
MIT License. See LICENSE for details.