Mở đầu — vì sao bài này quan trọng
Hãy bắt đầu bằng một câu hỏi tưởng chừng đơn giản: "PM thì có cần biết về database schema không?" Câu trả lời ngắn gọn là: bạn không cần thiết kế bảng, không cần viết câu lệnh SQL, nhưng bạn rất cần biết khi nào phải đưa một phần mô tả dữ liệu vào spec, và mô tả ở mức nào để cả engineer lẫn các team khác hiểu đúng ý bạn.
Tôi từng chứng kiến một tình huống thế này tại một công ty fintech ở TP.HCM. PM viết một spec về tính năng "ví phụ" (sub-wallet) cho phép người dùng tách tiền tiết kiệm ra khỏi số dư chính. Spec mô tả rất chi tiết về UI, về flow, về các nút bấm. Nhưng nó im lặng hoàn toàn về một câu hỏi cốt lõi: một user có thể có bao nhiêu ví phụ? Mỗi giao dịch thuộc về ví chính hay ví phụ? Khi báo cáo số dư tổng, hệ thống cộng cả hai hay chỉ ví chính? Engineer team A hiểu một kiểu, team báo cáo (data) hiểu một kiểu khác. Đến sprint thứ ba, hai team mới phát hiện họ đang lưu dữ liệu theo hai mô hình mâu thuẫn nhau. Phải làm lại gần ba tuần công.
Vấn đề không nằm ở việc PM thiếu kỹ thuật. Vấn đề là PM không nhận ra rằng tính năng này chạm vào một thực thể dữ liệu mới (ví phụ) và tạo ra một quan hệ mới giữa các thực thể (giao dịch giờ thuộc về một ví cụ thể). Khi spec không làm rõ phần dữ liệu, mỗi người tự lấp khoảng trống bằng giả định của mình — và các giả định đó đụng nhau.
Bài học hôm nay sẽ giúp bạn trả lời ba câu hỏi: (1) khi nào PM nên đưa schema vào spec, (2) đưa ở mức độ nào là vừa đủ, và (3) trình bày nó ra sao để hữu ích thay vì làm rối.
Khái niệm cốt lõi
Data schema là gì dưới góc nhìn của PM
Hiểu nôm na, data schema là cách hệ thống tổ chức và lưu trữ thông tin: có những "thực thể" (entity) nào — ví dụ User, Order, Product, Wallet — mỗi thực thể có những "thuộc tính" (field/attribute) gì, và chúng "quan hệ" (relationship) với nhau ra sao.
Bạn không cần dùng đúng từ ngữ của dân database. Với PM, schema chỉ đơn giản là trả lời: "Tính năng này cần hệ thống nhớ những thông tin gì, và những thông tin đó liên kết với nhau thế nào?"
Có ba cấp độ mô tả dữ liệu, và bạn cần phân biệt để chọn đúng cấp:
- Conceptual (khái niệm): Liệt kê các thực thể và quan hệ giữa chúng ở mức ý tưởng. Ví dụ: "Một Đơn hàng thuộc về một Khách hàng, chứa nhiều Sản phẩm." Đây là cấp PM hay dùng nhất.
- Logical (logic): Bắt đầu liệt kê các field cụ thể và kiểu dữ liệu của chúng, nhưng chưa gắn vào công nghệ cụ thể. Ví dụ:
order_id,status(enum: pending/paid/cancelled),created_at. - Physical (vật lý): Bảng thật trong database, kiểu cột, index, khóa ngoại. Đây là việc của engineer, không phải của bạn.
Khi nào PM nên include schema
Đây là phần quan trọng nhất của bài. Không phải spec nào cũng cần mục schema. Đưa schema vào một spec đơn giản (ví dụ đổi màu nút, thêm tooltip) chỉ làm tài liệu dài và loãng. Bạn nên đưa schema vào khi gặp một trong ba dấu hiệu sau:
1. Tính năng tạo ra một thực thể (entity) mới. Khi sản phẩm bắt đầu cần "nhớ" một loại thông tin mà trước đây chưa có. Ví dụ: trước đây app chỉ có User và Order; giờ bạn thêm tính năng "Danh sách yêu thích" (Wishlist) — đó là một thực thể mới. PM cần mô tả: Wishlist có những thông tin gì, một user có nhiều wishlist không, một sản phẩm xuất hiện trong wishlist được lưu thế nào.
2. Tính năng tạo ra hoặc thay đổi một quan hệ then chốt giữa các thực thể. Đôi khi không có entity mới, nhưng cách các entity liên kết thay đổi. Ví dụ: trước đây một tài khoản chỉ thuộc về một người dùng; giờ bạn cho phép "tài khoản chung" với nhiều người dùng. Quan hệ 1-1 trở thành nhiều-nhiều. Đây là thay đổi cực kỳ quan trọng phải nói rõ trong spec, vì nó ảnh hưởng đến phân quyền, báo cáo, và rất nhiều logic phía sau.
3. Có phụ thuộc dữ liệu xuyên team (cross-team data dependency). Khi dữ liệu của tính năng bạn sẽ được team khác đọc hoặc ghi. Ví dụ: team Growth cần đọc trạng thái "đã hoàn thành onboarding" của user để gửi chiến dịch; team Data cần field này để dựng dashboard. Nếu bạn không mô tả rõ field này (tên, ý nghĩa, giá trị có thể có), mỗi team sẽ định nghĩa lại theo cách riêng và số liệu sẽ lệch nhau.
Một quy tắc nhanh để ghi nhớ: nếu hai người đọc spec có thể hình dung ra hai mô hình dữ liệu khác nhau, bạn cần một mục schema. Nếu chỉ có một cách hiểu hiển nhiên, bạn có thể bỏ qua.
Schema trong spec khác gì với database design
Ranh giới này hay gây hiểu lầm, nên tôi muốn nói thẳng. Mục schema trong spec của PM không phải để engineer copy-paste vào database. Nó là để:
- Làm rõ ngữ nghĩa nghiệp vụ (business semantics): field này nghĩa là gì, giá trị nào hợp lệ, ràng buộc nghiệp vụ là gì.
- Đảm bảo các bên hiểu giống nhau về cấu trúc thông tin.
- Lộ ra những câu hỏi mở mà engineer cần trả lời.
Tình huống thực tế
Ví dụ 1 — Thực thể mới: tính năng "Điểm thưởng" của một chuỗi cà phê
Một chuỗi cà phê tại Việt Nam (giả định tên The Daily Brew, khoảng 40 cửa hàng) muốn ra mắt chương trình tích điểm: mỗi 1.000đ chi tiêu được 1 điểm, đổi 100 điểm lấy một ly miễn phí.
PM ban đầu viết spec tập trung vào màn hình "Điểm của tôi" và animation khi tích điểm. Khi review, engineer hỏi dồn: Điểm được lưu ở đâu — trên User hay là một thực thể riêng? Điểm có hạn sử dụng không? Khi user đổi điểm, ta trừ điểm cũ nhất trước hay điểm mới nhất trước? Nếu một giao dịch bị hoàn tiền, điểm đã cộng có bị thu hồi không?
PM nhận ra mình cần một mục schema ở cấp conceptual:
- PointTransaction (thực thể mới): mỗi lần cộng hoặc trừ điểm là một bản ghi, gồm
user_id,amount(dương khi cộng, âm khi trừ),source(enum: purchase / redeem / expire / refund),expires_at,created_at. - Quan hệ: một User có nhiều PointTransaction. "Số dư điểm" = tổng các amount chưa hết hạn.
Ví dụ 2 — Quan hệ thay đổi: từ tài khoản cá nhân sang tài khoản nhóm
Một startup SaaS ở Singapore làm công cụ quản lý công việc. Ban đầu mỗi tài khoản (Account) thuộc về đúng một người (User). Sản phẩm chạy tốt với 5.000 khách. Sau đó họ muốn bán cho doanh nghiệp: một Account giờ có nhiều thành viên với các vai trò khác nhau (owner, admin, member).
PM viết spec cho tính năng "Mời thành viên". Điểm mấu chốt mà spec phải làm rõ không phải là cái nút "Mời", mà là quan hệ giữa User và Account đã thay đổi từ 1-1 sang nhiều-nhiều, kèm thuộc tính vai trò. PM mô tả:
- Thực thể nối Membership: gồm
user_id,account_id,role(enum: owner / admin / member),joined_at. - Ràng buộc nghiệp vụ: mỗi Account phải có ít nhất một owner; không thể xóa owner cuối cùng.
user_id cần được "di cư" để gắn với account_id; phân quyền cần viết lại; báo cáo "số tài khoản hoạt động" cần định nghĩa lại vì giờ một account có nhiều người.Bài học: Thay đổi quan hệ (cardinality) thường nguy hiểm hơn thêm entity mới, vì nó kéo theo việc di cư dữ liệu cũ và viết lại logic. Khi spec của bạn động đến chữ "nhiều" ở chỗ trước đây là "một", hãy dừng lại và viết hẳn một mục schema.
Ví dụ 3 — Phụ thuộc xuyên team: field "trạng thái KYC"
Một ví điện tử tại Đông Nam Á có ba team: team Onboarding (làm KYC — xác minh danh tính), team Payments (cho phép giao dịch), và team Data (dựng báo cáo). Team Onboarding ra spec cho tính năng xác minh giấy tờ và thêm một field kyc_status trên User.
Ban đầu họ chỉ định nghĩa hai giá trị: verified và unverified. Spec không nêu rõ, nên team Payments tự hiểu "cứ không phải verified thì chặn giao dịch". Nhưng thực tế còn các trạng thái trung gian: đang chờ duyệt, bị từ chối, cần bổ sung giấy tờ. Khi team Onboarding âm thầm thêm các giá trị mới, hệ thống Payments coi mọi giá trị lạ là "không verified" và chặn nhầm hàng nghìn user đang chờ duyệt.
Cách làm đúng: trong spec, team Onboarding mô tả rõ field cross-team này như một hợp đồng:
kyc_status: enum gồm chính xácunverified,pending_review,needs_more_docs,rejected,verified.- Quy tắc cho bên đọc: chỉ
verifiedmới được giao dịch không giới hạn;pending_reviewđược giao dịch dưới hạn mức X; các trạng thái còn lại bị chặn. - Cam kết: mọi thay đổi tập giá trị của enum này phải thông báo trước cho team Payments và team Data.
Hướng dẫn từng bước
Khi bạn ngồi xuống viết mục schema cho một spec, hãy đi theo trình tự sau:
Bước 1 — Tự hỏi: tính năng này có chạm dữ liệu mới không? Chạy qua ba dấu hiệu ở trên (entity mới / quan hệ thay đổi / phụ thuộc xuyên team). Nếu cả ba đều "không", có thể bạn không cần mục schema — đừng thêm cho có.
Bước 2 — Liệt kê các thực thể liên quan. Viết ra danh sách những "danh từ" mà tính năng cần hệ thống ghi nhớ: User, Order, Wishlist, PointTransaction... Đánh dấu cái nào mới và cái nào đã có nhưng bị thay đổi.
Bước 3 — Với mỗi thực thể mới, liệt kê các field quan trọng về mặt nghiệp vụ. Không cần liệt kê mọi field kỹ thuật (như id, created_at thường engineer tự thêm). Tập trung vào field mang ý nghĩa nghiệp vụ và đặc biệt là field dạng enum/trạng thái — hãy ghi rõ tập giá trị hợp lệ.
Bước 4 — Mô tả quan hệ giữa các thực thể. Dùng ngôn ngữ "một / nhiều": "Một User có nhiều PointTransaction", "Một Order thuộc về một Account". Nếu có quan hệ nhiều-nhiều, hãy nêu rõ vì nó luôn cần một thực thể nối.
Bước 5 — Ghi các ràng buộc nghiệp vụ (business rules). Ví dụ: "mỗi account có ít nhất một owner", "điểm hết hạn sau 12 tháng", "không cho hai wishlist trùng tên". Đây là phần engineer khó tự đoán nhất, và là phần giá trị nhất bạn đóng góp.
Bước 6 — Đánh dấu các câu hỏi mở. Nếu có chỗ bạn chưa chắc (ví dụ: "điểm có chuyển nhượng được giữa các user không?"), hãy ghi rõ là câu hỏi mở thay vì lờ đi. Để engineer và bạn cùng quyết.
Bước 7 — Trình bày trực quan nếu quan hệ phức tạp. Với hai, ba thực thể thì một đoạn văn là đủ. Khi có nhiều thực thể liên kết chằng chịt, một sơ đồ quan hệ đơn giản (hộp và mũi tên, hoặc một bảng) sẽ hiệu quả hơn nhiều so với chữ.
Một định dạng bảng nhẹ nhàng mà tôi hay khuyên dùng cho mỗi thực thể:
| Field | Ý nghĩa | Giá trị hợp lệ | Ghi chú |
|---|---|---|---|
| status | Trạng thái đơn | pending / paid / cancelled | mặc định pending |
| amount | Số tiền | số nguyên ≥ 0 | đơn vị: đồng |
Lỗi thường gặp & mẹo
Lỗi 1 — Viết quá sâu xuống cấp physical. PM hào hứng đi định nghĩa kiểu cột VARCHAR(255), đặt index, vẽ khóa ngoại. Đây là việc của engineer và bạn dễ viết sai. Hãy dừng ở cấp conceptual/logical: nói cái gì và vì sao, để engineer lo thế nào.
Lỗi 2 — Bỏ quên field trạng thái và tập giá trị của nó. Như ví dụ KYC cho thấy, các field enum/status là nơi sinh ra nhiều bug xuyên team nhất. Mỗi khi có một field "trạng thái", hãy liệt kê đầy đủ và chính xác các giá trị có thể có, và ý nghĩa mỗi giá trị.
Lỗi 3 — Không phân biệt "lưu một con số" với "lưu một sổ giao dịch". Với những thứ như điểm thưởng, số dư, lượt dùng — lưu một con số tổng thì đơn giản nhưng mất khả năng truy vết và xử lý hoàn tác. Bạn không cần quyết định cách lưu, nhưng nên nêu yêu cầu nghiệp vụ "cần truy vết từng lần cộng/trừ" để engineer chọn đúng mô hình.
Lỗi 4 — Im lặng về quan hệ. Nhiều spec mô tả từng thực thể rất kỹ nhưng quên nói chúng liên kết ra sao. Phần lớn hiểu lầm nằm ở quan hệ, không phải ở field. Luôn dành một dòng cho mỗi cặp quan hệ.
Mẹo 1 — Dùng ngôn ngữ nghiệp vụ, không dùng tên bảng. Viết "Một khách hàng có nhiều đơn hàng" dễ hiểu cho mọi bên hơn là "bảng orders có FK customer_id".
Mẹo 2 — Mời engineer review sớm mục schema. Schema là chỗ kiến thức của bạn (nghiệp vụ) và của engineer (khả thi kỹ thuật) bổ sung cho nhau. Một buổi 15 phút cùng xem qua mục này thường tiết kiệm nhiều tuần.
Mẹo 3 — Tái sử dụng từ vựng đã có. Nếu hệ thống đã gọi là "Account" thì đừng tự đặt là "Workspace" trong spec. Thuật ngữ lệch nhau gây nhầm lẫn không kém gì schema sai.
Bài tập thực hành
Hãy chọn một tính năng giả định: "Mã giảm giá" (voucher) cho một sàn thương mại điện tử. Người dùng có thể nhập mã khi thanh toán; mỗi mã có giá trị giảm, thời hạn, và giới hạn số lần dùng.
Nhiệm vụ của bạn:
- Xác định xem tính năng này rơi vào dấu hiệu nào trong ba dấu hiệu cần có schema (entity mới / quan hệ thay đổi / phụ thuộc xuyên team). Giải thích ngắn gọn.
- Liệt kê các thực thể liên quan, đánh dấu cái nào mới.
- Với thực thể Voucher, viết một bảng field gồm: tên field, ý nghĩa, giá trị hợp lệ. Đặc biệt chú ý field trạng thái và field kiểu giảm (theo % hay theo số tiền cố định).
- Mô tả quan hệ: một Voucher dùng được bao nhiêu lần? Một đơn hàng áp được mấy mã? Một user có bị giới hạn dùng một mã mấy lần?
- Viết ít nhất hai ràng buộc nghiệp vụ và một câu hỏi mở.
Tóm tắt
Là PM, bạn không thiết kế database, nhưng bạn chịu trách nhiệm đảm bảo mọi người hiểu giống nhau về cấu trúc thông tin mà tính năng cần. Đưa mục schema vào spec khi gặp một trong ba dấu hiệu: tính năng tạo thực thể mới, thay đổi quan hệ then chốt giữa các thực thể, hoặc tạo phụ thuộc dữ liệu xuyên team. Viết ở cấp conceptual/logical — nêu cái gì cần lưu và vì sao, để engineer lo lưu thế nào. Đặc biệt cẩn thận với các field trạng thái (liệt kê đầy đủ giá trị hợp lệ), với các quan hệ (luôn nói rõ một/nhiều), và với dữ liệu dùng chung giữa các team (coi mỗi field như một hợp đồng). Phép thử cuối cùng luôn là: nếu hai người đọc có thể vẽ ra hai mô hình dữ liệu khác nhau, spec của bạn vẫn chưa đủ rõ.