A sandbox for system design: a ticket-selling service for large events, built to showcase the hard parts: contention over scarce inventory, caching, and load balancing.
This repo is deliberately a foundation. The plumbing runs and is testable by hand; the interesting decisions are left open as seams to be filled in.
PostgreSQL is the durable source of truth, shared by every API instance. Overselling is prevented by a single atomic conditional UPDATE, the relational equivalent of a CAS (compare-and-swap):
UPDATE inventory SET available = available - @q WHERE event_id = @id AND available >= @q RETURNING available;Redis is in the stack and reachable, but not on the hot path yet. It enters once the database is measured to be the bottleneck under load, not before.
src/RushHour.Api/
Domain/ Event - the model (intentionally minimal)
Abstractions/ IEventCatalog, IInventoryStore <- the seams for design decisions
Infrastructure/ PostgresEventCatalog, PostgresInventoryStore, DatabaseInitializer
Endpoints/ /events, /events/{id}/reservations
Program.cs DI wiring; Postgres source of truth, Redis
deploy/nginx.conf Load balancer config (round-robin over api1/api2)
docker-compose.yml postgres + redis + 2 api instances + nginx
tests/requests.http Manual smoke tests
tests/load-test.sh Concurrency test (reveals behaviour under load)
docs/adr/ Where design decisions are recorded
- Docker Desktop (for PostgreSQL, Redis, and the load balancer)
- .NET 8 SDK (only if you want to run the API outside Docker)
docker compose up --build| URL | What |
|---|---|
| http://localhost:8080 | nginx load balancer (round-robins to api1/api2) |
| http://localhost:8081 | api1 directly |
| http://localhost:8082 | api2 directly |
| localhost:5432 | PostgreSQL (user/pass/db: rushhour) |
| localhost:6379 | Redis |
docker compose up -d postgres redis # just the infrastructure
dotnet run --project src/RushHour.Api # API on the host; port printed on startup