Final Project: Ticket Booking Platform
Học xong rồi, giờ mình muốn tự code một project để kiểm tra toàn bộ kiến thức. Làm gì nhỉ?
Làm Ticket Booking Platform: hệ thống đặt vé sự kiện, Java/Spring Boot, microservices. Đây không phải tutorial CRUD. Đây là bài thi cuối: bạn tự code, AI chỉ ra đề, review và phỏng vấn.
1. Vì sao chọn đặt vé?
Vì bài toán này chạm gần như mọi chương đã học:
- Hai người cùng giữ một ghế → concurrency, lock, transaction.
- Giữ ghế 10 phút rồi hết hạn → state machine, scheduled job.
- Thanh toán có thể timeout, trả UNKNOWN → idempotency, reconciliation.
- Booking → Payment → Notification qua nhiều service → Saga, Outbox, Kafka, RabbitMQ.
- Flash sale 100k người vào cùng lúc → rate limit, Redis, queue.
Nó chính là bài Ticket booking và Flash sale của chương System Design, ghép với chương Distributed. Lần này bạn code thật, làm hỏng thật, đo thật.
2. Nguyên tắc
- Không frontend. Kiểm tra bằng REST, Postman collection, integration test, load test.
- Không lặp lý thuyết. Mỗi milestone chỉ link về bài đã học. Quên thì quay lại đọc.
- Mỗi milestone đi đủ 7 bước:
problem → design → implementation → failure simulation
→ benchmark → trade-offs → interview review
Mỗi milestone sẽ có: đề bài, acceptance criteria, test cases, failure scenarios, checklist.
3. Kiến trúc
Postman / k6 / integration test
│
┌─────────▼─────────┐ ┌──────────┐
│ gateway │─────▶│ Keycloak │ (JWT)
│ SC Gateway │ └──────────┘
│ JWT + rate limit │── Redis
└──┬──────────┬─────┘
│ │
┌────────▼───┐ ┌───▼────────┐ REST ┌──────────────┐
│ catalog │ │ booking │──────────▶│ payment │
│ events, │ │ hold seat, │◀─ webhook │ mock PSP + │
│ seats,cache│ │ confirm, │ │ fault inject │
└──┬─────┬───┘ │ saga │ └──────┬───────┘
│ │ └──┬─────┬───┘ │
Postgres Redis Postgres │ outbox ─▶ Kafka ◀─────┘
│
┌────▼──────────┐
│ notification │◀── RabbitMQ
│ consumer, DLQ │ (quorum queue)
└───────────────┘
Toxiproxy chen giữa các kết nối để giả lập lỗi mạng.
Prometheus + Grafana + OTel quan sát toàn bộ.
| Service | Trách nhiệm | Công nghệ chính |
|---|---|---|
| gateway | Entry point, validate JWT, rate limit | Spring Cloud Gateway, Keycloak, Redis |
| catalog | Sự kiện, sơ đồ ghế, đọc nhiều | Postgres, Caffeine + Redis cache |
| booking | Giữ ghế, xác nhận, điều phối saga | Postgres, Redis, Kafka, Outbox |
| payment | Mock PSP: chậm, lỗi, UNKNOWN, webhook | Postgres, idempotency key, fault injection API |
| notification | Gửi email/SMS giả | RabbitMQ consumer, DLQ, priority |
Hạ tầng (docker-compose): Postgres, Redis, Kafka, RabbitMQ, Keycloak, Toxiproxy, Prometheus, Grafana. Test: JUnit 5, Testcontainers, k6.
4. 12 milestone
| # | Mục tiêu | Ôn lại | Kiến thức mới | Xong khi |
|---|---|---|---|---|
| M0 | Requirements, capacity, dựng hạ tầng | Framework, SLO & capacity | docker-compose đủ infra, skeleton 5 service | Có tài liệu FR/NFR + ước lượng QPS; docker compose up xanh; health check 5 service OK |
| M1 | Chống bán trùng ghế | Race condition, DB locks, Redis lock | 5 cách: synchronized → atomic UPDATE → FOR UPDATE → @Version → Redis SET NX | Test 1000 request đồng thời vào 1 ghế: đúng 1 thành công; bảng benchmark cả 5 cách; chứng minh synchronized hỏng khi chạy 2 instance |
| M2 | Hold + expiry | Ticket booking | State machine HELD → CONFIRMED / EXPIRED, job quét hết hạn với ShedLock | Ghế tự nhả sau TTL; 2 instance job không xử lý trùng; chuyển trạng thái sai bị từ chối |
| M3 | Thanh toán an toàn | Payment, Timeout & retry | Idempotency key, trạng thái UNKNOWN, webhook, reconciliation job | Gửi lại cùng key không trừ tiền 2 lần; PSP timeout → UNKNOWN → reconciliation chốt đúng |
| M4 | Lỗi dây chuyền A→B→C | Partial failure, Circuit breaker, Resilience4j | Timeout, retry + backoff, circuit breaker, fallback; Toxiproxy tiêm latency, cắt kết nối | Payment chậm 5s không làm booking cạn thread; dashboard thấy trạng thái circuit |
| M5 | Saga + Outbox + Inbox | Saga, Outbox/Inbox, Order–inventory–payment, Delivery semantics | Saga qua Kafka, outbox relay, inbox dedupe, compensation | Kill service giữa chừng rồi bật lại: không mất event, không xử lý trùng, ghế được nhả khi payment fail |
| M6 | Notification | Notification, Quorum queue, Retry & DLX | Quorum queue, DLQ, priority, manual ack | Message lỗi vào DLQ sau N lần; email xác nhận vé được ưu tiên hơn marketing |
| M7 | Flash sale | Flash sale, Rate limiting | Rate limit ở gateway, trừ tồn kho Redis-first (Lua), queue ghi DB | k6 10k VU: không oversell, DB không quá tải, user bị chặn nhận 429 rõ ràng |
| M8 | Scale catalog | Cache strategies, Replication & sharding, Scale app & DB | Cache 2 tầng Caffeine + Redis, read replica, bài tập tư duy sharding | Đo được cache hit ratio; invalidation đúng; có tài liệu chọn shard key cho bảng seat |
| M9 | Benchmark mô hình chạy | MVC vs WebFlux vs VT, Virtual threads | Cùng endpoint 4 kiểu: sync, async, reactive, virtual threads; k6 đo p50/p95/p99 | Có bảng số liệu + giải thích vì sao kết quả như vậy |
| M10 | Security + observability | Resource server + Keycloak, Tracing | Keycloak realm, role, resource server; OTel trace xuyên service, Grafana dashboard | Không token → 401, sai role → 403; một trace thấy đủ gateway → booking → payment → Kafka |
| M11 | High availability | HA, Kafka replication, Sentinel, Helm | Kafka 3 broker, RabbitMQ cluster, Redis Sentinel, deploy K8s bằng Helm, CI pipeline | Kill 1 broker, kill Redis master: vẫn nhận booking; CI chạy test xanh |
| M12 | Game day + mock interview | Toàn chương Distributed và System Design | Sự cố ngẫu nhiên do AI ra, viết postmortem | Xử lý được sự cố không báo trước; trình bày lại toàn bộ thiết kế trong 45 phút phỏng vấn |
5. Cấu trúc repo gợi ý
ticket-booking/
├── docker-compose.yml # Postgres, Redis, Kafka, RabbitMQ, Keycloak,
│ # Toxiproxy, Prometheus, Grafana
├── .env.example # biến môi trường, không commit secret
├── gateway/
├── catalog-service/
├── booking-service/
├── payment-service/ # mock PSP + fault injection API
├── notification-service/
├── common/ # event schema, error model (giữ nhỏ)
├── tests/
│ ├── postman/ # collection theo milestone
│ └── k6/ # load test script
├── infra/
│ ├── keycloak/realm.json
│ ├── grafana/dashboards/
│ └── helm/
└── docs/
├── requirements.md # M0
├── adr/ # mỗi quyết định = 1 ADR
├── benchmarks/ # số liệu M1, M7, M9
└── postmortems/ # M12
6. Thang chấm (3 mức)
| Mức | Mô tả |
|---|---|
| Đạt | Happy path chạy đúng, acceptance criteria xanh, có integration test với Testcontainers. |
| Khá | Thêm: mọi failure scenario đã chạy thử, có số liệu benchmark, có ADR giải thích trade-off. |
| Sẵn sàng phỏng vấn senior | Thêm: giải thích được vì sao số liệu như vậy, biết giới hạn của thiết kế, trả lời được "tải x10 thì sao?" mà không cần nhìn code. |
7. Definition of Done (mỗi milestone)
- Acceptance criteria đều có test tự động và xanh.
- Mọi failure scenario đã chạy thật (Toxiproxy, kill container), có ghi kết quả.
- Benchmark (nếu có) lưu trong
docs/benchmarks/. - Có ít nhất 1 ADR cho quyết định quan trọng.
- Không hardcode password, config qua
${ENV}. - Đã qua lượt review và lượt phỏng vấn của AI.
Mỗi milestone có trang chi tiết riêng: bắt đầu từ M0, hoặc chọn trong mục lục bên trái.
12 milestone nhiều quá, làm theo thứ tự nào?
Đúng là nhiều. Nhưng không cần làm theo số thứ tự cứng. Có một trục bắt buộc, phần còn lại là nhánh.
M0 ─▶ M1 ─▶ M2 ─▶ M3 ─▶ M4 ─▶ M5 (trục lõi: dữ liệu đúng)
│
┌─────────────────┼──────────────┐
▼ ▼ ▼
M6 M7 M10
(notification) (flash sale) (security/obs)
│
M8 ─▶ M9 (hiệu năng)
│
M11 ─▶ M12 (HA + thi cuối)
- M0–M5 phải theo thứ tự. Ghế chưa đúng (M1) thì saga (M5) vô nghĩa.
- M10 làm sớm nếu được: có trace và dashboard thì debug M5–M7 dễ hơn nhiều.
- M8, M9 là bài đo đạc: làm khi hệ thống đã chạy ổn.
- M11, M12 luôn ở cuối.
Mẹo: mỗi milestone là một branch + một PR. Học buổi tối thì ước lượng 1–2 tuần/milestone.
AI hỗ trợ mình thế nào mà không làm hộ?
Bạn nắm đúng điểm quan trọng: nếu AI viết code thì project không còn kiểm tra được bạn. AI chỉ đóng 3 vai:
| Vai | Làm gì | Không làm |
|---|---|---|
| Task giver | Ra đề milestone, acceptance criteria, thêm failure scenario bất ngờ | Không đưa code mẫu |
| Reviewer | Đọc PR, chỉ race condition, transaction sai, thiếu test | Không sửa hộ, chỉ đặt câu hỏi và chỉ chỗ |
| Interviewer | Hỏi "vì sao chọn X", "nếu Y chết thì sao" | Không cho đáp án trước khi bạn trả lời |
Vòng lặp mỗi milestone:
AI ra đề ─▶ bạn design (ADR) ─▶ AI review design
─▶ bạn code + test ─▶ AI review PR ─▶ bạn chạy failure + benchmark
─▶ AI phỏng vấn ─▶ chưa đạt? quay lại bước cần sửa
Cách làm việc với agent xem lại bài Làm việc với AI agent. Quy tắc đơn giản: kẹt quá 2 giờ thì xin gợi ý, không xin lời giải.
Vậy làm xong M12 là đủ đi phỏng vấn chưa?
Gần đủ, chỉ cần chỉnh một điểm: nhà tuyển dụng không chấm project chạy được, họ chấm bạn kể lại được quyết định. Hãy chuẩn bị:
- Một sơ đồ kiến trúc vẽ lại được trong 3 phút.
- Ba câu chuyện sự cố: oversell ở M1, UNKNOWN ở M3, mất event ở M5. Mỗi câu: triệu chứng → nguyên nhân → cách sửa → số liệu.
- Một bảng benchmark mà bạn hiểu từng con số.
Sau đó luyện thêm với Top 100 câu hỏi.
Tự kiểm tra
Vì sao synchronized không đủ chống bán trùng ghế khi chạy 2 instance booking?
Lock chỉ có tác dụng trong một JVM. Hai instance có hai monitor riêng nên vẫn bán trùng. Cần khóa ở DB hoặc Redis.
Payment trả UNKNOWN thì booking nên làm gì?
Không coi là fail ngay. Giữ trạng thái chờ, đợi webhook hoặc chạy reconciliation, gọi lại với cùng idempotency key.
Outbox giải quyết vấn đề gì trong M5?
Dual write: ghi DB thành công nhưng gửi Kafka thất bại. Event được ghi cùng transaction với dữ liệu rồi relay sang Kafka.
Một milestone được coi là xong khi nào?
Khi đủ Definition of Done: test xanh, failure scenario đã chạy thật, có benchmark (nếu có), có ADR, qua review và phỏng vấn của AI.