Hướng dẫn học kỹ thuật thực chiến qua dự án KCDS (Khí Công Dưỡng Sinh)¶
Bộ tài liệu này đúc kết những kỹ thuật "thực chiến" được dùng ngay trong dự án Discord Bot KCDS — một hệ thống kết hợp giữa phát nhạc và quản lý lịch tập qua web panel.
Mục tiêu của mình là giúp các bạn mới (newbie) không chỉ học lý thuyết suông, mà còn được "mắt thấy tay sờ" cách code vận hành trong một dự án thật. Từ đó, bạn hoàn toàn có thể tự tin "bê" những kiến trúc này sang các dự án cá nhân khác của mình.
Bản đồ học tập¶
Bắt đầu từ đây
│
▼
┌─────────────────────────────┐
│ Bài 0: Nguyên lý nền tảng │ ← Từ vựng cốt lõi: SOLID, SoC, DRY, YAGNI, IoC
└──────────────┬──────────────┘
▼
┌─────────────────────────────┐
│ Bài 1: Khởi tạo project │ ← Setup ban đầu: venv, cấu trúc thư mục, config.py,
│ │ Pydantic input types, uvicorn
└──────────────┬──────────────┘
▼
┌─────────────────────────────┐
│ Bài 2: FastAPI + ASGI │ ← Nền tảng: async Python + HTTP
│ Bài 3: Dependency Injection│ ← Trái tim của FastAPI (IoC)
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ Bài 4: SQLAlchemy Core │ ← Tương tác DB không dùng ORM
│ Bài 5: Layer Architecture │ ← Áp dụng SoC + DIP vào thực tế
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Bài 6: Vite + Vanilla JS │ Bài 7: discord.py │ ← Học song song hai mảng
│ (Frontend) │ (Bot async) │
└─────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ Bài 8: Auth OAuth + JWT │ ← Bảo mật hệ thống
└──────────────┬──────────────┘
▼
┌─────────────────────────────┐
│ Bài 9: Testing │ ← Test qua interface với DI + mock
│ Bài 10: Trade-off & Anti-pt│ ← Khi nào thì KHÔNG NÊN dùng các pattern đã học
└─────────────────────────────┘
Danh sách bài học¶
| Bài | Chủ đề | File tham khảo trong project | Độ khó |
|---|---|---|---|
| 00 | Nguyên lý nền tảng (SOLID, SoC, DRY, IoC, Fail-Fast) | Không có — Khởi động tư duy | ⭐ |
| 01 | Khởi tạo project FastAPI từ con số 0 | admin/src/config.py, .env.example |
⭐ |
| 02 | FastAPI + ASGI + async/await | admin/src/main.py, routers/audio.py |
⭐⭐ |
| 03 | Dependency Injection trong FastAPI | admin/src/auth.py, deps.py |
⭐⭐⭐ |
| 04 | SQLAlchemy Core + aiosqlite | admin/src/db/engine.py, repositories/audio_repo.py |
⭐⭐⭐ |
| 05 | Kiến trúc 3 lớp: Router → Service → Repo | repositories/base.py, services/audio_service.py |
⭐⭐⭐⭐ |
| 06 | Build Frontend với Vite + Vanilla JS | admin/src/ui/src/ |
⭐⭐ |
| 07 | discord.py + Các pattern xử lý Async | bot/src/scheduler.py, notifier.py |
⭐⭐⭐⭐ |
| 08 | Xác thực: Google OAuth2 + JWT + HMAC | admin/src/auth.py, services/signed_url.py |
⭐⭐⭐⭐⭐ |
| 09 | Testing (Unit / Integration / E2E) | Code mẫu tương đương với entity của project | ⭐⭐⭐ |
| 10 | Đánh đổi & Cạm bẫy (Trade-off & Anti-patterns) | Tổng hợp từ Bài 2 đến Bài 8 | ⭐⭐⭐⭐⭐ |
Lộ trình mình khuyên dùng: Hãy bắt đầu từ Bài 0 để nắm tư duy, qua Bài 1 để setup máy móc, rồi nhẩn nha đọc từ Bài 2 đến Bài 8. Chỉ tìm đến Bài 9 khi bạn đã sẵn sàng viết test, và chốt hạ bằng Bài 10 để tập thói quen phản biện lại chính những gì mình vừa học.
Bức tranh toàn cảnh (Tech Stack)¶
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT (Trình duyệt) │
│ Vite + Vanilla JS (ES modules, fetch API, cookie auth) │
└─────────────────────────────┬───────────────────────────────────┘
│ HTTPS (Cloudflare Tunnel)
┌─────────────────────────────▼───────────────────────────────────┐
│ ADMIN API (Backend Python) │
│ FastAPI + Uvicorn (ASGI) │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Router │→ │ Service │→ │ Repository │ │
│ │ (HTTP) │ │(Nghiệp vụ│ │ (SQLite/File)│ │
│ └──────────┘ └──────────┘ └──────────────┘ │
│ Bảo mật: Google OAuth2 → JWT → HttpOnly Cookie │
└─────────────────────────────┬───────────────────────────────────┘
│ Dùng chung ổ cứng (Shared Volume)
┌─────────────────────────────▼───────────────────────────────────┐
│ SQLITE + FILESYSTEM │
│ khi_cong.db (Lưu data) /data/audio/ (Lưu file MP3) │
└─────────────────────────────┬───────────────────────────────────┘
│ Đọc/Ghi
┌─────────────────────────────▼───────────────────────────────────┐
│ BOT (Python) │
│ discord.py + asyncio │
│ Vòng lặp (2s) → Quét lệnh → Check lịch phát nhạc │
│ Truyền FFmpeg PCM → Discord Voice │
└─────────────────────────────────────────────────────────────────┘
Sổ tay các Pattern cốt lõi — Xem nhanh¶
1. Dùng Async/Await cơ bản¶
# Đã là hàm async thì phải nhớ dùng await bên trong cho các tác vụ I/O
async def get_user(session, email: str) -> dict | None:
result = await session.execute( # Dừng lại chờ DB phản hồi
select(users).where(users.c.email == email)
)
return dict(result.fetchone()._mapping) if result.fetchone() else None
2. Chuỗi Dependency Injection (Tiêm phụ thuộc)¶
# Luồng đi: session → tìm user → check quyền → router
async def get_session(): yield session
def get_user_repo(): return SqliteUserRepository()
async def get_current_user(session=Depends(get_session), repo=Depends(get_user_repo)): ...
def require_editor(user=Depends(get_current_user)): ...
@router.post("/resource", dependencies=[Depends(require_editor)])
async def create_resource(session=Depends(get_session)): ...
3. Thứ tự Transaction sinh tử (File vs DB)¶
# Khi Upload: Ghi file TRƯỚC, lưu DB SAU. Nếu DB lỗi thì xóa file đi (rollback).
dest.write_bytes(data)
try:
await repo.create(session, ...)
except:
dest.unlink() # Thu hồi file
raise
# Khi Xóa: Xóa DB TRƯỚC, xóa file SAU. (Lỗi không xóa được file thì chỉ cần ghi log).
await repo.delete(session, id)
try:
dest.unlink()
except OSError as e:
logger.warning(e) # Nhắm mắt làm ngơ, không throw lỗi
4. Dùng Interface trừu tượng (DIP)¶
class MyRepository(ABC):
@abstractmethod
async def create(self, session, *, name: str) -> dict: ...
class SqliteMyRepository(MyRepository):
async def create(self, session, *, name: str) -> dict:
# Code logic thực tế của SQLite nằm ở đây
class MyService:
def __init__(self, repo: MyRepository): # Chỉ nhận cái vỏ (interface), không nhận ruột
self.repo = repo
5. Khóa đồng bộ (Async Mutex)¶
# Chốt trạng thái (SYNC) ngay trước khi gọi await để tránh bị đụng hàng (race condition)
self._current_id = entry["id"] # Chạy đồng bộ, không sợ ai chen ngang
task = asyncio.create_task(...) # Đẩy ra background chạy — không làm đứng luồng chính
6. Ký chữ ký số bảo mật (HMAC)¶
import hmac, hashlib, time
def sign(resource_id: int, secret: str, ttl: int = 1800) -> tuple[str, int]:
expires = int(time.time()) + ttl
sig = hmac.new(secret.encode(), f"{resource_id}:{expires}".encode(), hashlib.sha256).hexdigest()
return sig, expires
def verify(resource_id: int, sig: str, expires: int, secret: str) -> bool:
if expires < time.time(): return False
expected = hmac.new(secret.encode(), f"{resource_id}:{expires}".encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(sig, expected)
Hành trang cần chuẩn bị (Prerequisites)¶
- Nắm cơ bản về Python (Viết hàm, tạo class, xử lý exception).
- Hiểu HTTP hoạt động thế nào (GET/POST, headers, status codes).
- Biết viết vài câu SQL cơ bản (SELECT, INSERT, UPDATE, DELETE, JOIN).
- Biết chút ít JavaScript (Viết hàm, dùng async/await, gọi API bằng fetch).
Và đừng lo, bạn KHÔNG CẦN phải biết trước: React, Vue, Django, SQLAlchemy ORM hay Docker.
Sợi chỉ đỏ xuyên suốt bộ tài liệu¶
Các nguyên lý từ Bài 0 sẽ liên tục "hiện hồn" trong các bài sau. Bảng dưới đây sẽ chỉ cho bạn thấy mảnh code nào đang đại diện cho nguyên lý nào — đây chính là chìa khóa để bạn mang kiến thức này đi đánh đông dẹp bắc ở các dự án khác:
| Nguyên lý | Bài học minh họa | Nơi áp dụng cụ thể |
|---|---|---|
| Separation of Concerns | 2, 5, 6 | Tách bạch Router / Service / Repo; Chia rõ api.js / state / render |
| Dependency Inversion | 3, 5, 9 | Service chỉ nhận cái vỏ AudioRepository (ABC) qua constructor |
| Inversion of Control | 3, 7 | Dùng Depends(), @client.event, @router.get() |
| Single Responsibility | 5 | Mỗi layer chỉ có 1 lý do duy nhất để thay đổi code |
| Fail Fast | 1, 2, 8 | config.py validate() chặn app chạy nếu thiếu env + check JWT_SECRET |
| Fail Soft (Có điều kiện) | 7 | _TICK_RECOVERABLE + gửi bot notification (gửi xong quên luôn, lỗi cũng kệ) |
| DRY (Có chừng mực) | 3, 6 | Gom chung vào deps.py, gom cục gọi API vào api.js |
| YAGNI | 4, 6 | Chưa cần thì không xài ORM, không xài React |
| Explicit > Implicit | 4 | Viết thẳng SQLAlchemy Core nhìn cho rõ, tránh bị ORM "làm phép" ngầm |
| POLS | 2, 7 | Bọc response trong {data}, đặt tên biến _current_entry_id rõ ràng đúng ý nghĩa |
| Defense in Depth | 6, 8 | Chống XSS bằng esc() + CSP + Cookie HttpOnly + check quyền RBAC |
| Least Privilege | 8 | Thời hạn JWT ngắn, mỗi lần có request đều phải móc DB ra check lại quyền |
Cách dùng bảng này: Lần tới, nếu bạn thấy một dòng code hay một công nghệ mới ở dự án khác, hãy tự hỏi: "Cái này đang phục vụ nguyên lý nào nhỉ?". Nếu chưa nghĩ ra → vòng lại đọc Bài 0. Nếu nghĩ ra ngay → chúc mừng, bạn đã thực sự "hấp thụ" được kiến thức.
Cách học "vào" nhất¶
- Bắt đầu với Bài 0 → Nạp đủ từ vựng về nguyên lý thiết kế, sau đó đọc Bài 1-8 mới thấy thấm.
- Đọc lý thuyết trước → Hiểu "tại sao người ta lại làm thế".
- Mở source code dự án ra ngó → Xem trên thực tế nó được viết ra sao.
- Chốt lại ở mục "Nguyên lý tổng quát" cuối mỗi bài → Rút ra bài học cốt lõi để mang đi áp dụng nơi khác.
- Làm bài tập → Tự gõ lại code thay vì copy-paste.
- Trả lời câu hỏi tự kiểm tra → Thấy cấn cấn ở đâu thì lật lên đọc lại ngay ở đó.
- Đọc Bài 10 cuối cùng → Tập thói quen nhìn ra điểm yếu của pattern, không tôn thờ công nghệ một cách mù quáng.
- Mang đi build app của riêng bạn → Cách này hiệu quả gấp 10 lần việc chỉ ngồi đọc.
Lời khuyên chân thành cho các newbie¶
Nếu bạn hoàn toàn mới chập chững bước vào mảng web với Python, đừng cố nhồi nhét đọc hết 10 bài trong 1 tuần. Mình gợi ý tiến độ này cho bạn:
- Tuần 1: Cày Bài 0 (tư duy) + Bài 1 (setup máy + tự làm bài tập Notes API).
- Tuần 2-3: Nhai kỹ Bài 2 + 3 (hiểu sâu về Async và DI).
- Tuần 4-5: Chinh phục Bài 4 + 5 (Chơi với DB và đắp kiến trúc 3 lớp).
- Tuần 6-7: Chọn 1 trong 2 — học Bài 6 (làm Frontend) hoặc Bài 7 (viết Bot).
- Tuần 8: Trùm cuối Bài 8 (hệ thống Auth — phần này khá chua, cần vững nền tảng).
- Tuần 9+: Đọc Bài 9 (Test) và Bài 10 (Trade-off) khi bạn đã tự code được một chiếc app kha khá.
Thử thách thực hành (Gợi ý)¶
Đọc xong bộ này, hãy thử xắn tay áo lên tự làm những món sau:
Level 1 — API CRUD cơ bản:
- Dùng FastAPI + SQLite + SQLAlchemy Core.
- Quản lý đúng 1 đối tượng thôi (ví dụ:
Tasktrong ứng dụng To-Do). - Viết đủ 4 hàm Thêm/Sửa/Xóa/Xem chia làm 3 lớp đàng hoàng.
Level 2 — Gắn thêm cửa khóa (Auth):
- Đăng nhập bằng username/password lấy JWT (chưa cần đụng tới Google OAuth).
- Phân 2 quyền cơ bản: admin, user.
- Chặn không cho người ngoài gọi API bậy bạ.
Level 3 — Lên đồ Full-stack:
- Viết thêm giao diện bằng Vite + Vanilla JS.
- Build ra và ném cho FastAPI StaticFiles chạy.
- Cho phép user upload file (ví dụ: đổi avatar).
Level 4 — Tích hợp Bot Discord:
- Viết con bot hẹn giờ gửi thông báo vào kênh Discord.
- Cho con bot đọc chung cái database SQLite với thằng FastAPI ở trên.
- Ra lệnh cho bot thông qua cơ chế command queue (hàng đợi).