#07 – Kiến trúc hệ thống & Quyết định kiến trúc (ADR)
Giai đoạn: Thiết kế hệ thống · Phiên bản 0.1 – 29/10/2026
Kiến trúc dưới đây là đề xuất, phụ thuộc kết quả đánh giá
onthi.hoclieu.vn(ADR-001). Các phần độc lập với quyết định đó (mô-đun nghiệp vụ, luồng dữ liệu, bảo mật) giữ nguyên trong mọi phương án.
#1. Động lực kiến trúc (Architecture drivers)
| Động lực | Nguồn | Hệ quả thiết kế |
|---|---|---|
| Mốc MVP1 sau 32 ngày | C1 | Ưu tiên tái sử dụng, dịch vụ quản lý sẵn (managed), modular monolith thay vì microservices |
| ID in trên sách không đổi được | C2, BR-04 | Tầng phân giải ID tách biệt khỏi tài nguyên; ID → tài nguyên là ánh xạ đổi được |
| Đỉnh tải mùa thi, phần lớn truy cập đọc | C4, NFR-SCAL-01 | Cache nội dung đã xuất bản tại CDN/Redis; máy chủ ứng dụng không trạng thái, mở rộng ngang |
| Học sinh dùng 4G, quét QR từ Zalo | NFR-USAB-02 | SSR/streaming cho trang đầu, gói JS nhỏ, PWA, tự lưu ngoại tuyến |
| Bảo vệ đáp án và video | NFR-SEC-02/03 | Chấm điểm phía máy chủ; URL video ký số |
| Dữ liệu hành vi từ ngày đầu | YC15, FR-11.01 | Thu sự kiện bất đồng bộ, tách kho phân tích khỏi CSDL giao dịch |
| Tích hợp Hoclieu (SSO, LMS) chưa rõ | Q02, Q12 | Lớp adapter tích hợp, có phương án dự phòng (tài khoản riêng, mã lớp) |
#2. Các phương án nền tảng
#ADR-001 – Lựa chọn nền tảng
Trạng thái: ⏳ Chờ quyết định (hạn 05/11/2026) · Người quyết định: HEID + SK
| Tiêu chí | A. Mở rộng onthi.hoclieu.vn |
B. Xây mới độc lập, tích hợp Hoclieu qua SSO/API | C. Kết hợp: xây mới lớp hybrid, tái dùng dịch vụ Hoclieu |
|---|---|---|---|
| Tốc độ ra MVP1 | Nhanh nếu mã nguồn tốt và SK được quyền sửa | Chậm nhất | Trung bình |
| Tài khoản, LMS | Có sẵn | Phải tích hợp | Có sẵn qua API |
| Kiểm soát kỹ thuật | Thấp – phụ thuộc mã cũ | Cao | Trung bình |
| Rủi ro ẩn | Nợ kỹ thuật chưa biết, ảnh hưởng hệ thống đang chạy | Khối lượng lớn | Phụ thuộc chất lượng API |
| Phù hợp khi | Hệ thống hiện tại đã có ngân hàng câu hỏi + làm bài tốt, SK có toàn quyền | Hoclieu không có API hoặc mã nguồn không tiếp cận được | Hoclieu có API SSO/LMS ổn định |
Danh mục đánh giá kỹ thuật onthi.hoclieu.vn (3 ngày)
- Quyền truy cập mã nguồn, môi trường, CI/CD; công nghệ đang dùng; ai đang vận hành.
- Có sẵn: ngân hàng câu hỏi (hỗ trợ đúng/sai 4 ý, trả lời ngắn, LaTeX?), làm bài, chấm, CMS?
- API SSO (OIDC/OAuth2?), API lớp học/giao bài, tài liệu API.
- Năng lực chịu tải hiện tại, số người dùng, lịch sử sự cố.
- Chất lượng mã: kiểm thử tự động, cấu trúc mô-đun, khả năng thêm mô-đun mới không ảnh hưởng tính năng cũ.
- Ràng buộc hạ tầng, hợp đồng, bảo mật.
Khuyến nghị sơ bộ của SK: nghiêng về C nếu Hoclieu có SSO; nếu không có API dùng được → B với tài khoản riêng và mã lớp, tích hợp LMS ở R1. Phần còn lại của tài liệu mô tả kiến trúc cho B/C (lớp hybrid do SK xây).
#3. Kiến trúc tổng thể (C4 – mức Container)
flowchart TB
HS(["Học sinh<br/>(điện thoại, trình duyệt Zalo)"])
GV(["Giáo viên"])
BTV(["BTV / Admin"])
subgraph EDGE["Biên"]
CDN["CDN + WAF<br/>cache trang tĩnh, ảnh, chống DDoS"]
end
subgraph APP["Nền tảng hybrid"]
WEB["Web học sinh & giáo viên<br/>Next.js (SSR, PWA)"]
CMS["CMS & Quản trị<br/>SPA React"]
API["Backend API<br/>Modular monolith (REST/OpenAPI)"]
WORKER["Worker nền<br/>import, xuất file, đề xuất, báo cáo"]
EVT["Thu sự kiện<br/>(endpoint nhẹ + hàng đợi)"]
end
subgraph DATA["Dữ liệu"]
PG[("PostgreSQL<br/>dữ liệu giao dịch")]
REDIS[("Redis<br/>cache, phiên, giới hạn tần suất, hàng đợi")]
OBJ[("Object storage<br/>ảnh, file import/xuất, ảnh bài làm")]
DWH[("Kho phân tích<br/>ClickHouse / PostgreSQL phân vùng")]
end
subgraph EXT["Hệ thống ngoài"]
SSO[["Hoclieu SSO"]]
LMS[["LMS Hoclieu"]]
VIDEO[["Dịch vụ video<br/>chuyển mã + HLS + URL ký số"]]
PAY[["Cổng thanh toán"]]
MSG[["SMS / Email / Zalo ZNS"]]
end
HS --> CDN --> WEB
GV --> CDN
BTV --> CMS
WEB --> API
CMS --> API
WEB --> EVT
API --> PG & REDIS & OBJ
API --> WORKER
WORKER --> PG & OBJ & DWH
EVT --> REDIS --> WORKER
API <--> SSO
API <--> LMS
API --> VIDEO
HS -. "phát video" .-> VIDEO
API <--> PAY
WORKER --> MSG
#4. Mô-đun trong Backend (modular monolith)
Mỗi mô-đun sở hữu bảng dữ liệu của mình, giao tiếp qua interface nội bộ và sự kiện miền; có thể tách thành dịch vụ riêng khi cần mở rộng.
| Mô-đun | Trách nhiệm | Epic | Sự kiện miền phát ra |
|---|---|---|---|
identity |
Đăng nhập, SSO, vai trò, hồ sơ, đồng ý | EP01 | UserRegistered |
entitlement |
Quyền sử dụng sách/gói; kiểm tra quyền cho mọi mô-đun khác | EP01, EP08 | EntitlementGranted |
activation |
Lô mã, kích hoạt, chống dò | EP01, EP10 | BookActivated |
catalog |
Sách, cây nội dung, tài nguyên, video, xuất bản, phiên bản | EP03, EP10 | ResourcePublished |
code-resolver |
Sinh/khoá ID, phân giải ID/QR, kiểm tra liên kết | EP02, EP10 | CodeResolved |
taxonomy |
Ma trận kiến thức, gắn tag | EP10 | – |
assessment |
Ngân hàng câu hỏi, đề, lượt làm, tự lưu, chấm điểm, tự chấm | EP04, EP05 | AttemptSubmitted |
recommendation |
Tính đề xuất từ lượt đã nộp | EP06 | RecommendationGenerated |
progress |
Lịch sử, biểu đồ (read model) | EP07 | – |
classroom |
Lớp, bài giao, báo cáo lớp, adapter LMS | EP09 | AssignmentCreated |
commerce |
Gói, đơn hàng, thanh toán, adapter cổng thanh toán | EP08 | OrderPaid |
analytics |
Thu sự kiện, ETL, báo cáo | EP11 | – |
content-import |
Phân tích file Word/Excel, kiểm tra, nhập | EP10 | – |
notification |
Gửi OTP, thông báo | EP01, EP09 | – |
#5. Luồng quan trọng
#5.1 Phân giải ID
sequenceDiagram
actor HS as Học sinh
participant WEB as Web
participant API as API (code-resolver)
participant C as Redis cache
participant DB as PostgreSQL
HS->>WEB: Nhập "tv 012" / quét QR /27/TV012
WEB->>API: GET /codes/TV012?edition=27
API->>API: Chuẩn hoá (BR-05)
API->>C: Tìm (edition, code)
alt Có trong cache
C-->>API: resource_id, kind, book_id
else Không có
API->>DB: Tra CONTENT_CODE (+ sách đã kích hoạt của HS nếu thiếu edition)
DB-->>API: Kết quả
API->>C: Lưu cache 10 phút
end
API->>API: Kiểm tra entitlement (hoặc is_preview)
API-->>WEB: { kind, resourceId, path } hoặc lỗi NOT_FOUND / NOT_ACTIVATED
WEB->>HS: Chuyển tới trang tài nguyên
WEB-)API: Sự kiện code_resolved
#5.2 Làm bài, nộp, chấm, đề xuất
sequenceDiagram
actor HS as Học sinh
participant WEB as Web (PWA)
participant API as API (assessment)
participant Q as Hàng đợi
participant REC as recommendation
HS->>WEB: Bắt đầu
WEB->>API: POST /assessments/{id}/attempts
API-->>WEB: attempt (deadline_at), đề KHÔNG kèm đáp án
loop Mỗi thay đổi
WEB->>WEB: Lưu IndexedDB
WEB->>API: PATCH /attempts/{id}/answers (gộp, debounce 2s)
end
HS->>WEB: Nộp bài
WEB->>API: POST /attempts/{id}/submit (đồng bộ phần còn thiếu)
API->>API: Chấm theo scoring_scheme (09)
API-->>WEB: Kết quả + điểm
API-)Q: AttemptSubmitted
Q-)REC: Tính đề xuất
REC-->>API: Lưu RECOMMENDATION
WEB->>API: GET /attempts/{id}/recommendations (poll/SSE ≤ 2s)
Đề xuất được tính đồng bộ cho bài nhỏ (≤ 40 câu, thường < 50 ms) để trả cùng kết quả; xử lý bất đồng bộ là phương án khi tải cao.
#5.3 Thanh toán gói đề (P3)
sequenceDiagram
actor HS as Học sinh
participant API as API (commerce)
participant PAY as Cổng thanh toán
participant ENT as entitlement
HS->>API: POST /orders {packageId}
API-->>HS: URL thanh toán
HS->>PAY: Thanh toán
PAY->>API: IPN (có chữ ký)
API->>API: Kiểm chữ ký, số tiền, trạng thái, provider_txn_id duy nhất
API->>ENT: Cấp quyền (idempotent)
API-->>PAY: Xác nhận
PAY-->>HS: Chuyển về trang kết quả
HS->>API: GET /orders/{id} → PAID
#6. Công nghệ đề xuất (cho phương án B/C)
| Lớp | Lựa chọn đề xuất | Lý do | Phương án thay thế |
|---|---|---|---|
| Web học sinh/giáo viên | Next.js + TypeScript, React Server Components, PWA | SSR nhanh trên 4G, SEO trang giới thiệu | Nuxt (Vue) |
| CMS | React + thư viện admin (Refine/React-Admin) | Nhanh dựng CRUD | Dùng chung Next.js |
| Soạn thảo nội dung | Trình soạn thảo rich text hỗ trợ LaTeX (Tiptap + KaTeX) | BTV quen công thức | CKEditor + MathType (có phí) |
| Hiển thị công thức | KaTeX (nhanh), MathJax làm dự phòng cho ký hiệu hiếm | NFR-COMP-01 | – |
| Backend | NestJS (TypeScript) modular monolith | Một ngôn ngữ cho cả đội, cấu trúc mô-đun rõ | Spring Boot, .NET, Laravel – theo năng lực đội SK |
| CSDL | PostgreSQL 16 | JSONB cho câu trả lời, ràng buộc mạnh | – |
| Cache/hàng đợi | Redis (BullMQ) | Đơn giản, đủ cho tải dự kiến | RabbitMQ |
| Lưu trữ tệp | Object storage tương thích S3 | – | – |
| Video | Dịch vụ streaming quản lý sẵn có chuyển mã HLS và URL ký số | Không tự vận hành chuyển mã trong 1 tháng | Tự chuyển mã bằng ffmpeg + CDN |
| Phân tích | Bảng sự kiện phân vùng trên PostgreSQL ở MVP → ClickHouse khi > 50 triệu sự kiện | Bắt đầu đơn giản | PostHog tự host |
| Hạ tầng | Docker; MVP: 2 máy chủ ứng dụng sau cân bằng tải + CSDL quản lý sẵn; R1: Kubernetes nếu cần tự mở rộng | Đúng mức cần thiết | – |
| Đám mây | Nhà cung cấp tại Việt Nam (Viettel IDC, VNPT, FPT, CMC, Bizfly…) hoặc theo hạ tầng Hoclieu | Độ trễ thấp, tuân thủ quy định lưu trữ dữ liệu | – |
| Giám sát | OpenTelemetry + Grafana/Prometheus/Loki; Sentry cho lỗi trình duyệt | NFR-OBS-01 | – |
| CI/CD | GitHub/GitLab CI, môi trường dev → staging → production | – | – |
| Kiểm thử | Vitest/Jest, Playwright (E2E), k6 (tải) | 10 | – |
#7. Bảo mật
| Chủ đề | Biện pháp |
|---|---|
| Xác thực | OIDC với Hoclieu hoặc phiên cookie HttpOnly; Secure; SameSite=Lax; token làm mới xoay vòng; 2FA cho CMS vai trò cao |
| Phân quyền | RBAC + kiểm tra entitlement tại tầng dịch vụ; kiểm thử tự động cho mọi endpoint |
| Bảo vệ đáp án | API đề không trả answer, explanation cho đến khi lượt ở trạng thái SUBMITTED, hoặc người dùng yêu cầu chế độ KEY_LOOKUP và có quyền |
| Chống gian lận đơn giản | Thời gian tính phía máy chủ; khoá chấm lại khi đã nộp |
| Chống dò | Rate limit theo tài khoản + IP (Redis) cho kích hoạt, đăng nhập, phân giải ID |
| Video | URL ký số có hạn, giới hạn tên miền phát; watermark mã tài khoản (P1) |
| Dữ liệu | Mã hoá khi truyền (TLS 1.2+), mã hoá khi lưu với ổ đĩa/CSDL; bí mật quản lý bằng secret manager |
| Ứng dụng web | CSP, chống XSS khi hiển thị rich text (lọc HTML phía máy chủ), CSRF token cho thao tác ghi |
| Quan sát an ninh | Nhật ký truy cập CMS, cảnh báo đăng nhập bất thường, AUDIT_LOG cho thao tác ID, mã kích hoạt |
| Kiểm định | Quét phụ thuộc trong CI, quét ZAP trên staging, pentest độc lập trước M3 |
#8. Triển khai & môi trường
flowchart LR
DEV["dev<br/>(mỗi PR có preview)"] --> STG["staging<br/>dữ liệu mẫu + nội dung thật đang biên tập"] --> PRD["production"]
STG -. "UAT, kiểm thử tải" .- STG
| Môi trường | Mục đích | Dữ liệu |
|---|---|---|
| dev | Phát triển, preview theo PR | Dữ liệu giả |
| staging | Kiểm thử tích hợp, UAT, demo M1, BTV nhập thử | Nội dung thật (chưa xuất bản), người dùng thử |
| production | Vận hành | Thật; CMS production là nơi BTV nhập nội dung chính thức từ MVP1 |
Mùa thi (tháng 4–6): tăng số máy chủ ứng dụng theo lịch, bật cache trang tài nguyên đã xuất bản tại CDN, kiểm thử tải trước 4 tuần.
#9. Nhật ký quyết định kiến trúc (ADR)
| ADR | Quyết định | Trạng thái | Tóm tắt |
|---|---|---|---|
| ADR-001 | Lựa chọn nền tảng | ⏳ Chờ (05/11) | Mục 2 |
| ADR-002 | ID có nghĩa 5 ký tự + QR deep link theo phiên bản sách | 📝 Đề xuất | Hỗ trợ cả nhập ID và quét QR; ID không đổi, tài nguyên đích đổi được (06 §3) |
| ADR-003 | Modular monolith | 📝 Đề xuất | Đủ cho quy mô và thời gian; tách dịch vụ sau nếu cần |
| ADR-004 | Dùng dịch vụ video quản lý sẵn | 📝 Đề xuất | Không tự vận hành chuyển mã trong giai đoạn MVP |
| ADR-005 | Thu sự kiện hành vi từ MVP1, tách kho phân tích | 📝 Đề xuất | Dữ liệu mùa thi đầu tiên không thể thu bù |
| ADR-006 | Hỗ trợ cả Giao diện đề thi và Phiếu trả lời | 📝 Đề xuất | Phiếu trả lời rẻ (không cần hiển thị đề), đúng bản chất hybrid; giao diện đề cần cho học sinh không mang sách |
| ADR-007 | Chấm điểm hoàn toàn phía máy chủ, theo scoring_scheme cấu hình |
📝 Đề xuất | Bảo vệ đáp án, thay đổi quy tắc chấm không cần phát hành lại ứng dụng |
| ADR-008 | Tự lưu ngoại tuyến bằng IndexedDB + đồng bộ theo lô | 📝 Đề xuất | Học sinh dùng 4G chập chờn |
| ADR-009 | Web responsive/PWA, chưa làm app native | 📝 Đề xuất | QR mở trình duyệt trực tiếp, không bắt cài app |
Mẫu ADR chi tiết (dùng khi chốt từng quyết định):
# ADR-xxx: <Tiêu đề>
- Trạng thái: Đề xuất | Chấp nhận | Thay thế bởi ADR-yyy
- Ngày: dd/mm/yyyy · Người quyết định: ...
## Bối cảnh
## Các phương án đã cân nhắc
## Quyết định
## Hệ quả (tích cực / tiêu cực / việc phải làm)