Tài liệu dự án · Sách ôn thi Hybrid TNTHPT

#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)

  1. 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.
  2. 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?
  3. API SSO (OIDC/OAuth2?), API lớp học/giao bài, tài liệu API.
  4. Năng lực chịu tải hiện tại, số người dùng, lịch sử sự cố.
  5. 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ũ.
  6. 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)