Kỹ thuật · Hạ tầng · FLYER

Kiến trúc hệ thống FLYER

Bức tranh hiện trạng: ứng dụng nào gọi dịch vụ nào, bốn thế hệ API đang chạy song song, cách đội quản lý cơ sở dữ liệu, triển khai và giám sát.

Phạm vi
37 repo trên Gitea flyer/*
Chốt ngày
28/09/2026
Trạng thái
Hiện trạng, chưa có đề xuất thay đổi
Xuất xứ / Prompt / Giới hạn
Ngày 28/09/2026, yêu cầu được đặt ra nguyên văn như sau: 1. em check và vẽ được diagram database hiện tại của FLYER không? 2. System design structure + api show cách hiện tại team đang quản lý nữa

Tài liệu do trợ lý AI soạn trong một phiên làm việc, bằng cách đọc bản sao 37 repo (cập nhật cuối tháng 8/2026) và các tệp đặc tả API tải ngày 06/09/2026. Đây là bản mô tả hiện trạng để thảo luận, chưa phải kết luận của đội kỹ thuật.

Nguồn: bản sao 37 repo Gitea flyer/*, trong đó các tệp chính là exam/main.go (bảng định tuyến), exam/deploy/lib/app.libsonnet, flyer-rs/deploy/lib/app.libsonnet, flyer-rs/shared/src/jwt.rs, flyer-go/AGENTS.md (quy ước đánh số phiên bản API và migration), partner-web/next.config.js (rewrites), backend-v1/packages/hasura/deploy/environments/production/env.yaml; đặc tả API gồm OpenAPI v3, v4 teacher và hai schema GraphQL (v2, Hasura) tải từ api.flyer.us và staging-api.flyer.us.

Giới hạn:

  • Tài liệu không truy cập hệ thống đang chạy, nên cấu hình trong cụm (metadata Hasura, cờ Hasura, định tuyến ingress) có thể khác mã nguồn.
  • Bảng định tuyến ở tab API là bản dựng lại, vì cấu hình định tuyến thật của api.flyer.us không nằm trong repo nào (ingress chủ yếu bị comment). Nguồn dựng lại: rewrites của partner-web/next.config.js và ingress của admin-web, admin-v1.
  • Sơ đồ cơ sở dữ liệu (câu 1 của yêu cầu) chưa có trong bản này. Việc dựng sơ đồ cần đọc danh mục cấu trúc (tên bảng, cột, khoá ngoại) của Postgres production và đang chờ cấp quyền. Khi có quyền, sơ đồ sẽ nhóm khoảng 300 bảng theo miền: tài khoản và hồ sơ, nội dung học, lượt làm bài, lớp và giao bài, thanh toán, thông báo, CRM và đối tác. Chỉ đọc cấu trúc, không đọc dữ liệu.
  • Sơ đồ kiến trúc không vẽ công cụ nội bộ (customer-success chạy trên Supabase riêng; PowerBI đẩy dữ liệu Postgres sang Parquet trên S3) và các nguyên mẫu (elevenlabs, test-realtime-call, python STT/TTS chưa có cấu hình k8s).
  • Mục 1–5 ở tab Rủi ro liên quan bảo mật và chưa được xử lý, nên chỉ nêu mức độ, hệ quả và hướng xử lý. Vị trí trong mã và cách kiểm tra đã được gửi riêng cho đội kỹ thuật.
Dịch vụ backend
7
đang nhận request qua api.flyer.us
Thế hệ API
4 + Hasura
v1/v2 Go · v3 Rust · v4 Go · GraphQL tự sinh
Công cụ migration
7
cùng ghi vào một Postgres dùng chung

1Sơ đồ tổng thể

Mọi dịch vụ chạy trên một cụm EKS tên flyer (ap-southeast-1) và dùng chung một Postgres trên RDS. Ứng dụng web Next.js của giáo viên và CRM tự chuyển tiếp request sang dịch vụ trong cụm qua rewrites, nên trong thực tế có hai lối vào: cổng api.flyer.us và từng web tự làm cổng.

luồng request chính    gọi phụ, không đồng bộ, hoặc hệ thống cũ

ỨNG DỤNG Web học sinh exam · ielts · study · ranking · signup .flyer.us — Next 15/16 Web giáo viên và CRM teacher.flyer.us (partner-web) crm.flyer.us — tự chuyển tiếp API Quản trị admin.flyer.vn (Next 16) admin-old.flyer.vn (React 16, cũ) 5 ứng dụng di động (Expo) FLYER · FLYER Teacher · IELTS Bingo AI · FLYER (cũ) CỔNG VÀO Cloudflare DNS · CloudFront CDN HAProxy ingress api.flyer.us ≡ api.flyer.vn statics.flyer.us · stream.flyer.us DỊCH VỤ TRÊN EKS · namespace production / development exam-v2 Go · gqlgen · ent /v2/graphql · /rest/v1 /rest/partner/v1 lõi học sinh, AI, thanh toán flyer-backend-rust Rust · Axum · sqlx /v3/* (182 path) CRM, giáo viên, phụ huynh + worker Restate teacher-api Go · Huma · sqlc /v4/teacher/* điểm danh, đá trí tuệ, cửa hàng quà (staging) Hasura v2.40 GraphQL tự sinh từ bảng /graphql xác thực qua webhook actions:5000/auth upload Go · Echo · gRPC /image · /audio · /record xử lý ảnh, âm thanh + imgproxy partner-backend NestJS · TypeORM · Bull /api lớp, giao bài, lịch (bản 2022) rest-api NestJS · Swagger nội bộ /rest cầu nối VNPay, SACE Hasura actions NestJS/Fastify (backend-v1) /auth · /sign · /start_attempt commit cuối 07/2024 report · speaking Next 11 / 15, render server phiếu điểm PDF (Browserless), web luyện nói DỮ LIỆU mọi dịch vụ đọc/ghi thẳng PostgreSQL trên RDS — một CSDL chung bản chính · bản đọc flyer-read · flyer-datastudio ReadySet làm cache truy vấn cho v3 7 công cụ migration cùng ghi Redis Sentinel cache, phiên, hàng đợi Bull S3 statics.flyer.vn statics1 · media Luồng công việc Temporal (exam, v4) Restate (v3) · NATS Qdrant vector cho AI (exam) DỊCH VỤ BÊN NGOÀI · phần lớn gọi từ exam-v2 Thanh toán VNPay · SePay · Polar · RevenueCat (App Store, Google Play) AI và giọng nói OpenAI · Gemini · OpenRouter · Anthropic · Azure Speech SpeechAce · ELSA · Deepgram · AssemblyAI · ElevenLabs · Inworld Thông báo FCM · SMTP (SES) · Zalo OA · Discord Mattermost · n8n · Mautic

2Dịch vụ backend

Dịch vụ (repo)Công nghệVai tròLưu trữ riêngTriển khai
exam-v2 (exam)Go, gqlgen, ent, HumaLõi phía học sinh: thi, học, chấm nói/viết bằng AI, gói cước, thiết bị BingoRedis, S3, Temporal, NATS, Qdrant; gọi Hasura bằng quyền adminTanka
flyer-backend-rust (flyer-rs)Rust, Axum, sqlx, RestateCRM, giáo viên, phụ huynh, xác thực, thanh toán SePayReadySet cache, Restate; tự co giãn 2–10 podTanka
teacher-api (flyer-go)Go, Huma, sqlc, TemporalKhung "base" + một dịch vụ mỗi nhánh (dev/teacher, dev/student, dev/admin); production mới chạy teacherRedis, S3Tanka, migration chạy trong CI
Hasura (backend-v1)Hasura v2.40.2 + actions NestJSGraphQL tự sinh cho web/app cũ, xác thực qua webhook—Kustomize
uploadGo, Echo, gRPCTải và xử lý ảnh, âm thanh, bản ghi âmS3Kustomize
partner-backend (backend-nodejs)NestJS, TypeORM, BullLớp, giao bài, lịch cho trường (thế hệ 2022)Redis (hàng đợi)Kustomize
rest-api (backend-nodejs)NestJSCầu nối VNPay và SACE (iService)—Chỉ có Dockerfile, không có CI

Hai điểm dễ nhầm: backend-nodejs/apps/backend-nodejs là khung NestJS rỗng, GraphQL v2 thật do exam (Go) phục vụ; repo speaking là web Next.js, không phải backend chấm nói.

3Ứng dụng nào gọi API nào

Ứng dụngTên miềnHasurav2 GraphQLv3v4upload
partner-webteacher.flyer.uscócócócó
center-crm-frontendcrm.flyer.uscócócó
admin-webadmin.flyer.vncócócócó
admin-v1admin-old.flyer.vncócócó
webs-examexam.flyer.uscócócó
webs-ieltsielts.flyer.uscócó
webs-rankingranking.flyer.uscócócó
webs-study, webs-signupstudy, signup.flyer.uscó
exam-web (cũ)exam-old.flyer.uscó
mobile-studentApp "FLYER"cócó
mobile-partnerApp "FLYER Teacher"cócó
mobile (cũ)com.flyer.examcócócó
mobile-ielts, mobile-studyIELTS, Bingo AIcó

GraphQL v2 là API được dùng rộng nhất (12/13 nhóm ứng dụng). Hasura vẫn nằm trong 8 nhóm, dù phần lớn web mới chỉ còn dùng nó cho subscription qua websocket.

1Bốn thế hệ API chạy song song

Quy ước đánh số: mỗi dịch vụ giữ một tiền tố phiên bản, dịch vụ mới lấy số trống kế tiếp. Số phiên bản vì vậy chỉ dịch vụ nào sở hữu, không phải bản nâng cấp của cùng một API.

Tiền tốDịch vụKiểuĐịnh nghĩa bằngQuy môTài liệu
/graphqlHasuraGraphQL tự sinhTheo dõi bảng Postgres157 query · 61 mutation · 154 subscriptionIntrospection
/v1, /v2exam-v2GraphQL + RESTSchema-first (gqlgen); REST bằng Huma350 query · 456 mutation · 5 subscriptionApollo Sandbox tại /v2
/v3flyer-backend-rustRESTCode-first (aide + schemars)182 path · 242 thao tác/v3/openapi.json, /v3/docs
/v4/<dịch vụ>teacher-apiRESTCode-first (Huma)29 path · 40 thao tác/v4/teacher/openapi, chỉ staging
/api, /restpartner, rest-apiRESTNestJSkhông có đặc tảKhông công bố

v3 theo nhóm: CRM 101 path, Teacher 60, Learner 10, Parent 5, Auth 2, còn lại 1 mỗi nhóm. v4 teacher: đá trí tuệ 11, cửa hàng quà 8, điểm danh 6, trường 3, thư viện 1.

2Định tuyến trong cụm

Đường dẫnTên dịch vụ trong cụm
/graphqlhasura:4000/v1/graphql
/v2/graphql, /v2/rest/*, /rest/v1, /v2/verificationexam-v2:4000
/v3/*flyer-backend-rust:3000
/v4/*teacher-api:8081
/image, /audio, /recordupload:4000
/apipartner-backend

3Xác thực

LớpCơ chế
v3 (Rust)JWT ký ES256, issuer https://flyer.us, có claim role (vd teacher); header Authorization
v2 và v4 (Go)Dùng chung định dạng token, cookie và bảng users/user_sessions với v3. Quyền theo vai trò trong trường, cộng vai trò quản trị MANAGER / ADMIN / SUPER_MANAGER
HasuraKhông dùng JWT mà gọi webhook actions:5000/auth mỗi request; webhook trả x-hasura-role (user, staged, …) và mã người dùng, hồ sơ
Giữa các dịch vụHeader X_SERVICE_TOKEN; exam và rest-api gọi Hasura bằng quyền admin

4Phía ứng dụng dùng API thế nào

  • GraphQL: sinh mã kiểu bằng GraphQL codegen, trỏ vào staging-api.flyer.us/v2/graphql, rồi gọi qua Apollo Client.
  • REST v3/v4: sinh kiểu từ OpenAPI bằng @hey-api/openapi-ts, gọi qua openapi-fetch. App di động giữ bản sao đặc tả trong repo (mobile-student 194 path, mobile-partner 182 path) nên phải chạy lại bước sinh mã khi backend đổi.
  • Hình dạng dữ liệu phân trang ({data, pagination}) thống nhất giữa v3 và v4 theo hợp đồng của flyer-rs, để một bộ phân trang phía web dùng được cho cả hai.

1Ai được thay đổi cấu trúc

Một Postgres, bảy cách thay đổi cấu trúc. Không có nơi nào giữ toàn bộ lịch sử: repo database rỗng (một commit 04/2025 chỉ có thư mục .idea), metadata Hasura không nằm trong Git.

Dịch vụCông cụSố migrationGhi chú
flyer-rssqlx migrate87Mới nhất 0087, 20/08/2026
flyer-gogolang-migrate990001–0085 chép nguyên từ flyer-rs (bỏ 0033), từ 0086 đánh số riêng, cao nhất 0100
exament + Atlas1009/2023 → 08/2026
uploadsqlc + migrate8
partner (NestJS)TypeORM8Dừng từ 2022
Hasura (backend-v1)SQL viết tay—view, trigger, hàm, chỉ mục; commit cuối 07/2024
customer-successSupabase53CSDL riêng, không phải Postgres chính
Hai sổ migration đã lệch số trên cùng một CSDL

flyer-go cố ý giữ 0001–0085 trùng với flyer-rs để so bằng diff. Từ 0086 hai bên tách: flyer-rs 0086_users_pending_email_phone trong khi flyer-go 0086_bingo_subjects_add_source_columns; cùng một thay đổi rename_duolingo_to_bingo_study mang số 0087 bên Rust và 0094 bên Go. Phép so diff không còn phát hiện được lệch, và không có cơ chế nào ngăn hai dịch vụ cùng sửa một bảng.

Quy ước sở hữu bảng duy nhất đang có: bảng do flyer-go tạo mang tiền tố tên dịch vụ (teacher_class_attendance, teacher_wisdom_stones). Bảng của flyer-rs, exam và Hasura không có tiền tố, nên không biết dịch vụ nào có quyền xoá.

1Luồng triển khai

Gitea37 repo flyer/*
Gitea Actions46 workflow trên 26 repo
Amazon ECRảnh Docker theo tag
Tanka / Kustomizesinh manifest k8s
EKS flyerproduction · development
Mattermostbáo kết quả
  • Kích hoạt: tag semver lên production, đẩy nhánh lên development. Staging (staging-api.flyer.us) chính là namespace development.
  • Hai công cụ manifest: Tanka/Jsonnet cho flyer-rs, flyer-go, exam và hầu hết web; Kustomize cho upload, backend-nodejs, backend-v1, speaking, report.
  • Bí mật: cấp tổ chức Gitea chỉ có 4 khoá (AWS, kubeconfig, Mattermost); biến môi trường trong cụm lấy từ secret k8s backend-secrets.
  • Di động: build bằng gradle, xcodebuild, fastlane; phát bản thử qua Firebase App Distribution.
  • 10 repo không có CI: admin-api, admin-v1, ai-review-rules, backend-v1, database, elevenlabs, mail-template, python, server-preview, test-realtime-call.

2Giám sát

Công cụDùng ở đâu
Sentry tự host (sentry.flyer.us)webs-exam, webs-ielts, webs-ranking
Datadog RUMChỉ partner-web
Mixpanelwebs-exam, webs-signup
MattermostThông báo build/deploy; exam gửi cảnh báo lỗi
Log JSON có tracingflyer-rs

Không tìm thấy công cụ đo hiệu năng phía máy chủ (APM) cho dịch vụ nào. Khi một API chậm, đội chỉ thấy triệu chứng ở trình duyệt (Datadog RUM của partner-web) hoặc log.

1Tổng hợp

Chín vấn đề, xếp theo mức ảnh hưởng.

#Vấn đềMứcHướng xử lý
1Khoá quản trị cơ sở dữ liệu có nguy cơ lộ phía trình duyệt ở một công cụ nội bộNghiêm trọngBỏ khoá khỏi mã phía trình duyệt, xoay khoá
2Cơ chế xác thực của một API có thể chấp nhận khoá ký cũ đang nằm trong repoNghiêm trọngThu hồi khoá cũ, kiểm tra hạn khoá khi xác thực
3Một số khoá dịch vụ được commit trong repo và đóng vào imageCaoXoay khoá, chuyển vào secret của cụm
4Cấu hình GraphQL tự sinh mở rộng hơn mức cầnCaoSiết cấu hình production
5Khoá của CI đặt ở biến thường thay vì biến bí mậtTrung bìnhChuyển sang biến bí mật
6Hai sổ migration lệch số trên cùng một CSDLTrung bìnhChốt một sổ duy nhất
7Cấu hình đang chạy không có trong GitTrung bìnhXuất metadata Hasura và ingress vào repo
8Bốn thế hệ API và các repo cũ không còn bảo trìThấp, tích luỹChốt API đích cho tính năng mới
9Không có giám sát phía máy chủThấp, tích luỹBật tracing cho exam, flyer-rs, flyer-go
Vì sao mục 1 đến 4 cần xử lý cùng nhau

Các mục này cùng xoay quanh một khoá quản trị có quyền đọc ghi toàn bộ dữ liệu. Chỉ xoay khoá thì bản build kế tiếp có thể lộ lại. Chỉ sửa mã mà không xoay khoá thì khoá cũ vẫn dùng được. Hai việc phải đi cùng một đợt.

2Chi tiết từng vấn đề

6 · Hai sổ migration lệch số Trung bình

Sốflyer-rs (sqlx)flyer-go (golang-migrate)
0001–0085Trùng nhau, trừ 0033 (flyer-go có chỉnh sửa riêng)
0086users_pending_email_phonebingo_subjects_add_source_columns
0087rename_duolingo_to_bingo_studyteacher_wisdom_stones_t
0094—rename_duolingo_to_bingo_study (cùng thay đổi với 0087 bên Rust)
0088–0100—13 migration riêng của flyer-go

Hệ quả: không ai biết chắc CSDL production đang ở trạng thái của sổ nào. Một thay đổi có thể chạy hai lần, hoặc một bên thiếu cột mà bên kia đã thêm. Phép so diff mà flyer-go dựa vào để phát hiện lệch đã mất tác dụng từ 0086. Xử lý: chọn một sổ là nguồn duy nhất (flyer-go đã có quy trình rõ trong AGENTS.md), bên còn lại chỉ chép sang; thêm bước CI báo lỗi khi hai sổ khác nhau.

7 · Cấu hình đang chạy không có trong Git Trung bình

Thành phầnTình trạng trong repoRủi ro
Metadata Hasura (bảng được theo dõi, quan hệ, phân quyền theo vai trò)Không có ở repo nào; repo database rỗngKhông xem lại được ai sửa quyền gì; mất cụm là mất toàn bộ phân quyền
Định tuyến api.flyer.usIngress chủ yếu bị commentKhông biết chắc đường dẫn nào vào dịch vụ nào
Hasura và actionsbackend-v1 commit cuối 10/07/2024Dịch vụ xác thực cho Hasura đang chạy bản không ai sửa trong hơn 2 năm

Xử lý: chạy hasura metadata export và kubectl get ingress -o yaml, commit vào repo database (hoặc repo hạ tầng), từ đó thay đổi chỉ đi qua pull request.

8 · Bốn thế hệ API và repo cũ Thấp, tích luỹ

Hạng mụcHiện trạng
API song songHasura, v2 (Go), v3 (Rust), v4 (Go) cùng phục vụ; partner-web gọi cả bốn. Năm ngôn ngữ backend: Go, Rust, TypeScript, Python, SQL
Web cũreport và ranking-web dùng Next 11.1.0 (bản 2021), admin-v1 dùng React 16; commit cuối 09–10/2024 và 03/2025
Không có CI10 repo, trong đó backend-v1 (Hasura) và rest-api (VNPay) vẫn chạy production

Xử lý: chốt quy tắc "tính năng mới chỉ viết ở v3 hoặc v4"; lập danh sách đóng băng cho Hasura và NestJS, kèm mốc gỡ admin-old và exam-old.

9 · Không có giám sát phía máy chủ Thấp, tích luỹ

Không dịch vụ backend nào có APM hay tracing phân tán. Sentry chỉ gắn ở 3 web (exam, ielts, ranking), Datadog chỉ đo trình duyệt ở partner-web. Khi API chậm hoặc lỗi, đội biết qua người dùng hoặc log, không có số liệu độ trễ theo đường dẫn. Xử lý: bật OpenTelemetry cho exam, flyer-rs, flyer-go, đẩy về Sentry sẵn có (sentry.flyer.us hỗ trợ tracing) để không phát sinh chi phí công cụ mới.

3Thứ tự xử lý đề xuất

Khi nàoViệcMục
Trong 24 giờXác minh hai vấn đề nghiêm trọng; nếu đúng thì xoay khoá và thu hồi khoá ký cũ1, 2
Trong tuầnSửa mã gây lộ khoá; xoay các khoá còn lại; siết cấu hình; chuyển khoá CI sang biến bí mật1, 3, 4, 5
Trong thángGộp sổ migration; đưa metadata Hasura và ingress vào Git6, 7
Trong quýQuy tắc API đích, lộ trình gỡ repo cũ; tracing backend8, 9

Tài liệu liên quan: Bản đồ tên miền · Bộ sự kiện đo lường · Danh mục PRD · Roadmap