Cấu trúc mã nguồn & quy ước làm việc
Trang này dành cho đội phát triển. Đi cùng Chuẩn lập trình.
Cấu trúc kho mã
adang_mvp/
├── docs/ tài liệu (trang bạn đang đọc)
├── docs-site/ bộ dựng tài liệu
├── connect/ công cụ nối Trello
├── scripts/ kịch bản triển khai, cấu hình máy chủ web
├── server/ máy chủ ứng dụng — NestJS
└── web/ giao diện — React + Vite
Hai thư mục server/ và web/ nằm chung một kho. Với đội nhỏ, tách kho chỉ làm việc đồng bộ thay đổi khó hơn.
Máy chủ ứng dụng
server/src/
├── main.ts
├── app.module.ts
├── prisma/
│ ├── schema.prisma
│ └── migrations/ tệp SQL, gồm phần ràng buộc viết tay
├── common/
│ ├── guards/ JwtAuthGuard, RolesGuard, ScopeGuard
│ ├── decorators/ @CurrentUser, @Roles, @Scope
│ ├── scope/ tính phạm vi dữ liệu theo vai trò
│ ├── errors/ AppError, danh sách mã lỗi
│ ├── filters/ bộ lọc lỗi toàn cục
│ ├── i18n/messages.vi.ts toàn bộ chuỗi tiếng Việt
│ └── time/vn-time.ts xử lý múi giờ, cách tính trễ hạn
├── auth/
├── users/ tài khoản, phòng ban, vai trò
├── tasks/
│ ├── tasks.controller.ts
│ ├── tasks.service.ts
│ ├── tasks.repository.ts
│ ├── task-transition.rules.ts
│ ├── comments/
│ ├── attachments/
│ └── dto/
├── dashboard/
├── notifications/
│ ├── channels/ mỗi kênh gửi một tệp
│ └── notifications.scheduler.ts
└── reports/
Bảy mô-đun khớp với năm nhóm chức năng, chỉ tách thêm auth khỏi users và tách dashboard khỏi tasks vì hai phần này chỉ đọc.
Giao diện
web/src/
├── main.tsx
├── routes/ khai báo đường dẫn và phân quyền hiển thị
├── pages/ mỗi màn hình trong wireframe một thư mục
├── components/
│ ├── ui/ lấy từ mẫu shadcn-admin
│ └── task/ thành phần riêng của nghiệp vụ
├── api/ lớp gọi máy chủ, một tệp một nhóm
├── types/ kiểu dùng chung với máy chủ
├── lib/
│ ├── permissions.ts quyết định nút nào hiện, nút nào ẩn
│ ├── format.ts định dạng ngày giờ, số
│ └── labels.ts ánh xạ mã sang chữ tiếng Việt
└── hooks/
Quy tắc phụ thuộc
Mũi tên chỉ chiều được phép gọi. Không có mũi tên ngược.
Controller ──→ Service ──→ Repository ──→ Prisma
│ │
└──→ DTO └──→ common/ (errors, i18n, time, scope)
Bốn điều cấm:
| Cấm | Vì sao |
|---|---|
| Service gọi Controller | Ngược chiều, sinh phụ thuộc vòng |
| Controller gọi thẳng Repository | Bỏ qua tầng nghiệp vụ và tầng kiểm quyền |
| Repository chứa quy tắc nghiệp vụ | Quy tắc nằm rải hai chỗ, sửa sót một chỗ |
| Mô-đun này gọi Repository của mô-đun khác | Gọi qua Service của mô-đun đó |
Ràng buộc này kiểm tự động bằng luật import/no-restricted-paths của bộ rà mã, không dựa vào trí nhớ.
Quy trình làm việc
Từ thẻ Trello đến khi gộp mã
Thẻ ở Backlog
└─ Đủ điều kiện bắt đầu → chuyển In Progress, tạo nhánh
└─ Code + kiểm thử → mở yêu cầu gộp mã, chuyển In Review
└─ Người khác duyệt → chuyển Testing
└─ Chạy thử theo điều kiện nghiệm thu, đính bằng chứng → Done
Điều kiện để bắt đầu một thẻ
Thẻ chỉ được chuyển sang In Progress khi đã có đủ:
- Mã chức năng liên quan (
FR-...) hoặc mã mô tả người dùng (US-...) - Điều kiện nghiệm thu đo được, không mơ hồ
- Biết rõ đụng vào bảng nào, đường dẫn nào
- Không bị thẻ khác chặn
Thiếu một mục thì chuyển thẻ sang ⏸ Chờ quyết định thay vì đoán.
Điều kiện để đóng một thẻ
- Biên dịch không lỗi cả máy chủ lẫn giao diện
- Kiểm thử của phần vừa làm chạy qua
- Điều kiện nghiệm thu đạt hết
- Bằng chứng đính vào thẻ — ảnh chụp màn hình hoặc kết quả chạy lệnh
- Đã có người thứ hai duyệt mã
- Tài liệu liên quan đã cập nhật nếu có thay đổi
Mục bằng chứng là bắt buộc. Ghi "đã xong" trong bình luận không tính.
Nhánh
main luôn ở trạng thái phát hành được
└── <loại>/<mã-thẻ>-<mô-tả-ngắn>
| Loại | Dùng khi |
|---|---|
feat | Thêm chức năng |
fix | Sửa lỗi |
refactor | Đổi cấu trúc, không đổi hành vi |
docs | Chỉ sửa tài liệu |
chore | Cấu hình, phụ thuộc, công cụ |
Ví dụ: feat/xcWfUD8c-tao-cong-viec
Thông điệp chuyển giao mã
Dòng đầu tiếng Việt, dưới 72 ký tự, mô tả thay đổi gì chứ không phải làm gì:
Thêm chức năng tạo công việc mới
- Kiểm tra đủ ba trường bắt buộc theo FR-TASK-01
- Trưởng phòng mặc định phòng mình, Ban Giám hiệu phải chọn
- Tạo công việc và người phụ trách trong cùng một giao dịch
Thẻ: https://trello.com/c/xcWfUD8c
Không viết "sửa lỗi", "cập nhật", "wip" — sáu tháng sau không ai hiểu.
Yêu cầu gộp mã
Kích thước: dưới 400 dòng thay đổi. Lớn hơn thì tách. Yêu cầu gộp 1.000 dòng chỉ nhận được lời duyệt hình thức.
Mô tả phải có:
## Việc gì
Một câu.
## Vì sao
Liên kết thẻ Trello và mã chức năng.
## Cách kiểm chứng
Các bước để người duyệt tự chạy lại.
## Ảnh hưởng
Có đổi cấu trúc bảng không? Có phá tương thích không?
Duyệt mã
Người duyệt kiểm theo thứ tự ưu tiên:
- Đúng nghiệp vụ chưa — đối chiếu mã chức năng và quy tắc nghiệp vụ liên quan
- Phạm vi dữ liệu có bị hở không — mọi truy vấn công việc phải lọc theo phạm vi
- Có rò dữ liệu ra ngoài không — có trả thẳng đối tượng Prisma không
- Xử lý lỗi — có nuốt lỗi không, thông điệp đã bằng tiếng Việt chưa
- Đọc hiểu được không — người khác đọc có hiểu ngay không
- Còn lại là góp ý, không chặn gộp mã
Ba mục đầu là chặn. Ba mục sau là góp ý.
Cách viết nhận xét: nói rõ mức độ.
CHẶN: hàm này lấy công việc không lọc theo phòng ban, nhân viên phòng A đọc được việc phòng B.
GÓP Ý: tách đoạn này thành hàm riêng thì dễ đọc hơn, nhưng không chặn.
HỎI: chỗ này cố ý bỏ qua trường hợp rỗng, hay sót?
Phát hành
Gộp vào main
└─ Tự động đưa lên bản thử
└─ Nghiệm thu ở bản thử
└─ Duyệt → đưa lên bản chính thức
Không bao giờ sửa thẳng trên bản chính thức. Chi tiết môi trường xem Phân chia môi trường.
Đổi cấu trúc cơ sở dữ liệu
Loại thay đổi này rủi ro nhất vì không lùi lại dễ như mã nguồn.
- Sinh khung bằng
prisma migrate dev --create-only - Mở tệp SQL và viết tay phần ràng buộc kiểm tra, chỉ mục có điều kiện, duy nhất theo hàm — xem Chuẩn lập trình
- Viết phần quay lui
- Chạy thử trên máy cá nhân với dữ liệu mẫu
- Chạy trên bản thử, kiểm bằng kịch bản đối chiếu ràng buộc
- Sao lưu cơ sở dữ liệu trước khi chạy trên bản chính thức
Không bao giờ dùng prisma db push — lệnh này bỏ qua tệp chuyển đổi và xoá mất phần viết tay.
Công cụ bắt buộc
| Công cụ | Việc |
|---|---|
| ESLint | Bắt lỗi lập trình, cấm any, kiểm quy tắc phụ thuộc giữa tầng |
| Prettier | Định dạng mã, không tranh luận về dấu cách |
| TypeScript ở chế độ nghiêm | Bắt lỗi kiểu lúc biên dịch |
| Vitest | Kiểm thử |
| Móc trước khi chuyển giao mã | Chạy rà mã và định dạng trên tệp vừa sửa |
Quy trình tích hợp chặn gộp mã nếu: biên dịch lỗi, rà mã lỗi, kiểm thử hỏng, hoặc kịch bản đối chiếu ràng buộc báo thiếu.
Ba việc dễ sai nhất trong dự án này
Ghi riêng vì đều là loại lỗi âm thầm, không báo lỗi lúc chạy.
1. Quên lọc theo phạm vi dữ liệu
Truy vấn thiếu điều kiện phạm vi vẫn chạy đúng, chỉ là trả về cả dữ liệu phòng khác. Không ai phát hiện tới khi có người nhìn thấy việc không phải của mình.
Cách phòng: mọi hàm trong repository nhận công việc bắt buộc có tham số phạm vi. Hàm không nhận tham số đó là lỗi thiết kế, không phải tiện lợi.
2. Ràng buộc cơ sở dữ liệu bị xoá sau khi chuyển đổi
Prisma không biết tới 19 ràng buộc viết tay. Một lần chạy prisma migrate dev không cẩn thận là mất hết, và cơ sở dữ liệu vẫn hoạt động bình thường — chỉ là không còn bảo vệ gì.
Cách phòng: kịch bản đối chiếu chạy trong quy trình tích hợp sau mỗi lần chuyển đổi.
3. Múi giờ
Máy chủ chạy giờ quốc tế, người dùng ở giờ Việt Nam. Lệch bảy tiếng nghĩa là việc đến hạn hôm nay bị tính trễ từ 7 giờ sáng, hoặc ngược lại.
Cách phòng: mọi phép so sánh ngày giờ đi qua common/time/vn-time.ts. Không gọi thẳng new Date() để so với hạn hoàn thành.