Personal project
BookLK: Event-Driven Service Booking Marketplace
Customers book time slots with salons, clinics and repair shops, vendors get paid out, and the platform keeps a commission. It is four services plus a job worker, joined by a transactional outbox to Kafka, with a Go WebSocket service for live updates. Payments run on Stripe test mode and the PayHere sandbox, and they are reconciled every night.
The problem
- When many customers press "book" on the same slot at the same moment, the system must sell it exactly once.
- Booking changes must reach every connected browser in real time without losing or reordering events.
- Money must stay correct when payment webhooks arrive twice, late, or never.
Architecture and key decisions
- NestJS API with 45 REST handlers and a public GraphQL endpoint, a Next.js 14 PWA, a Go realtime service on gorilla/websocket, an Express webhooks service, and a BullMQ job worker.
- Double booking is blocked four times over: an idempotency key, a Redis SET NX lock, SELECT … FOR UPDATE inside a READ COMMITTED transaction, and a unique active-slot constraint as the database backstop.
- Transactional outbox: the booking and its event commit in one Postgres transaction, and a relay publishes them to Kafka (Redpanda) using FOR UPDATE SKIP LOCKED. Consumers dedupe on eventId. Failures land in a per-topic DLQ that an admin can replay.
- Payments sit behind a provider port (Stripe, PayHere and a deterministic fake). Webhooks are verified against the raw body. Money is in integer cents and commission in integer basis points; the ledger is append-only, and a refund is a reversal entry.
- A nightly job compares provider transactions, payments and the ledger, records each gap, and can repair a missing webhook from the provider's own record.
- Read side: MongoDB read models fed from Kafka, and GraphQL for public catalog reads with DataLoader batching, depth and cost limits, and persisted queries.
- Kubernetes on a local k3d cluster: one Helm chart covering all six workloads, ArgoCD (app of apps) syncing from an in-cluster Gitea, and Prometheus and Grafana with multi-window SLO burn-rate alerts.
- Terraform for a single EC2 + K3s node with a USD 10 budget alarm. It is tested with a mocked provider and applied to LocalStack; it has never touched real AWS.
Results
- An integration test fires 20 parallel bookings at one slot and asserts that exactly one wins.
- k6 booking spikes (six runs of 200 virtual users on 20 slots, one of 500 on 50): zero double bookings in all seven, checked directly in Postgres, and no 5xx errors.
- Serializable isolation was aborting bookings for different slots (264 conflicts in one run) and leaving slots unsold. READ COMMITTED with row locks took conflicts to 0 and the median from 468–530 ms down to about 247 ms.
- The latency target is not met. p95 is about 3.2 s at 200 virtual users against a 300 ms goal. cgroup CPU stats showed the API pinned at one CPU while Postgres sat mostly idle, so I moved to two API pods (85–92 req/s became 126–140). Profiling a single request is the next step.
- A crash test kills the outbox relay before and after it publishes. No event is lost.
Tech stack
- TypeScript
- Next.js 14
- NestJS
- Go
- PostgreSQL
- Redis
- Kafka (Redpanda)
- MongoDB
- GraphQL
- BullMQ
- Stripe
- PayHere
- Kubernetes (k3d)
- Helm
- ArgoCD
- Terraform
- Prometheus
- Grafana
- k6
- Playwright
Links
- Architecture notes and code excerpts: github.com/jani358/BookLK-showcase. The main repo is private. Full source available on request.
- Case study by Janindu Gayanga, full-stack software engineer in Colombo, Sri Lanka
- Profile on GitHub and LinkedIn
Other projects
- AskLK: Bilingual AI Assistant with RAG, Agents and MCP: Multi-tenant RAG assistant that answers in Sinhala or English, with citations
- QueueLK: Real-Time Queue Management for Clinics: Live queue display, phone page and Expo patient app in Sinhala, Tamil and English
- ApplyWise: CV Tailoring Where Every Line Cites a Fact: Each line of a tailored CV cites a fact from your own CV, and a verifier removes the rest
- iGaming Platform: Go Microservices, Payments and Compliance: Real-money gaming platform with 12,299 registered players, moving from a Node.js monolith to Go