Mở đầu — vì sao bài này quan trọng
Nếu bạn từng làm QA/Tester, bạn đã vô tình "đọc" mô hình dữ liệu hàng trăm lần mà không gọi tên nó. Mỗi khi bạn viết test case kiểu "một đơn hàng phải có ít nhất một sản phẩm", "một khách hàng có thể có nhiều địa chỉ giao hàng", hay "không được xóa danh mục khi vẫn còn sản phẩm thuộc danh mục đó" — bạn đang phát biểu chính xác những gì một mô hình dữ liệu (data model) cố gắng diễn đạt. Bạn đã có trực giác về quan hệ giữa các thực thể, về ràng buộc, về tính toàn vẹn dữ liệu. Cái bạn thiếu chỉ là ngôn ngữ chuẩn để vẽ nó ra và làm việc với cả dev lẫn nghiệp vụ.
Đó chính là lý do Data Modeling là một trong những "vũ khí ngầm" mạnh nhất của người từ QA chuyển sang BA. Trong khi nhiều BA xuất thân từ kinh doanh hay marketing thường mơ hồ về dữ liệu — họ vẽ wireframe đẹp nhưng không hiểu sâu một "trạng thái đơn hàng" thực ra là gì trong database — thì bạn lại có lợi thế: bạn đã quen soi từng field, từng giá trị biên (boundary value), từng trường hợp null. Khi BA hiểu được mô hình dữ liệu, các tài liệu yêu cầu (requirement) của họ rõ ràng hơn, dev ít hỏi lại hơn, và những lỗi tích hợp (integration) tốn kém về sau giảm hẳn.
Bài này tập trung duy nhất vào một kỹ năng: dựng và đọc ERD (Entity-Relationship Diagram) qua ba tầng mô hình hóa — Conceptual, Logical, Physical — dưới góc nhìn của BA. Chúng ta sẽ không bàn về business rules tách khỏi feature (đó là Bài 23), cũng không đi sâu về requirements types (Bài 19). Ở đây, bạn học cách biến hiểu biết nghiệp vụ thành một sơ đồ dữ liệu mà cả dev và stakeholder đều đọc được.
Khái niệm cốt lõi
Data Modeling là quá trình biểu diễn dữ liệu của một nghiệp vụ dưới dạng các thực thể (entity), thuộc tính (attribute) và quan hệ (relationship) giữa chúng. Công cụ phổ biến nhất để làm việc này là ERD.
Thực thể, thuộc tính, quan hệ
- Entity (thực thể): một "đối tượng" mà nghiệp vụ cần lưu thông tin. Ví dụ: Khách hàng, Đơn hàng, Sản phẩm, Khóa học, Học viên. Trong database, một entity thường tương ứng với một bảng.
- Attribute (thuộc tính): đặc điểm mô tả entity. Khách hàng có Họ tên, Email, Số điện thoại, Ngày sinh. Một thuộc tính đặc biệt là khóa chính (primary key) — định danh duy nhất cho mỗi bản ghi, ví dụ
customer_id. - Relationship (quan hệ): cách hai entity liên kết với nhau, kèm theo cardinality (số lượng quan hệ): một-một (1:1), một-nhiều (1:N), nhiều-nhiều (N:M).
Cardinality và optionality — chỗ QA giỏi nhất
Với người từ QA, phần thú vị nhất không phải vẽ ô vuông mà là đọc được ràng buộc ẩn trong quan hệ. Có hai câu hỏi luôn phải hỏi cho mỗi quan hệ:
- Cardinality: một bên liên kết với tối đa bao nhiêu bên kia? Một hay nhiều?
- Optionality (tính bắt buộc): quan hệ này có bắt buộc không? Một Đơn hàng bắt buộc phải có Khách hàng (không thể tồn tại đơn hàng mồ côi), nhưng một Khách hàng có thể chưa có đơn hàng nào (khách mới đăng ký).
Ba tầng mô hình hóa: Conceptual, Logical, Physical
Đây là xương sống của bài học. Cùng một nghiệp vụ được mô hình hóa ở ba mức độ chi tiết tăng dần, phục vụ ba nhóm đối tượng khác nhau.
1. Conceptual Data Model — tầng nghiệp vụ. Chỉ gồm entity và relationship, không có thuộc tính chi tiết, không có khóa. Mục tiêu là để stakeholder nghiệp vụ (chủ doanh nghiệp, trưởng phòng) gật đầu: "Đúng rồi, một khóa học có nhiều bài học, một học viên ghi danh nhiều khóa học". Đây là tầng để thống nhất phạm vi và ngôn ngữ chung. BA dùng tầng này nhiều nhất khi làm việc với người không kỹ thuật.
2. Logical Data Model — tầng phân tích. Bổ sung đầy đủ thuộc tính, khóa chính (PK), khóa ngoại (FK), và đã được chuẩn hóa (normalized) để loại bỏ dữ liệu trùng lặp. Tầng này không phụ thuộc vào loại database cụ thể — chưa quan tâm MySQL hay PostgreSQL, chưa nói kiểu dữ liệu chính xác là VARCHAR(255) hay INT. Đây là tầng BA và dev cùng làm việc nhiều nhất.
3. Physical Data Model — tầng kỹ thuật. Cụ thể hóa cho một hệ quản trị cơ sở dữ liệu (DBMS) thật: tên bảng thật, kiểu dữ liệu cụ thể (VARCHAR(255), DECIMAL(12,2), TIMESTAMP), index, ràng buộc, default value. Tầng này chủ yếu do dev/DBA làm. BA không cần dựng tầng này nhưng cần đọc hiểu được nó, vì rất nhiều quyết định nghiệp vụ ẩn ở đây (ví dụ: tiền lưu kiểu gì, đơn vị nào).
Một mẹo ghi nhớ: Conceptual trả lời "Chúng ta quan tâm cái gì?", Logical trả lời "Dữ liệu được tổ chức ra sao?", Physical trả lời "Lưu trong database thế nào?".
Chuẩn hóa (Normalization) — vừa đủ để BA dùng
BA không cần là chuyên gia về các dạng chuẩn (normal forms), nhưng nên nắm tinh thần qua ba mức đầu:
- 1NF: mỗi ô chỉ chứa một giá trị nguyên tử. Không nhồi "0901234567, 0912345678" vào một ô "Số điện thoại".
- 2NF: mọi thuộc tính phụ thuộc đầy đủ vào toàn bộ khóa chính. Không để thông tin tách rời nửa vời.
- 3NF: không có thuộc tính phụ thuộc bắc cầu. Ví dụ, đừng lưu
tên_tỉnhtrong bảng Khách hàng nếu nó đã được suy ra từmã_tỉnh— tách ra bảng Tỉnh riêng.
Tình huống thực tế
Tình huống 1 — Edtech: mô hình hóa "ghi danh" tại một startup ở TP.HCM
Một startup edtech giả định tên HocViet (mô hình tương tự nhiều nền tảng học online Việt Nam) muốn xây tính năng cho phép học viên ghi danh khóa học. BA — vốn xuất thân QA — được giao dựng conceptual model trước khi họp với CEO.
Ban đầu, đội dev định làm đơn giản: bảng Khóa học có thêm cột danh_sach_hoc_vien. BA lập tức nhận ra mùi quen thuộc của một test case sẽ fail: "Nếu một học viên ghi danh 5 khóa, và một khóa có 2.000 học viên, thì lưu kiểu danh sách trong cột sẽ vỡ ngay". Đây chính là quan hệ nhiều-nhiều (N:M): một học viên ghi danh nhiều khóa, một khóa có nhiều học viên.
BA vẽ conceptual model: ba thực thể Học viên — Ghi danh — Khóa học, trong đó Ghi danh (Enrollment) là một entity trung gian (associative entity) để hóa giải quan hệ N:M. Ở tầng logical, Enrollment có khóa chính kết hợp (student_id, course_id), cộng thêm enrolled_at và status (đang học / hoàn thành / hủy). Chính cái cột status này về sau cứu cả dự án, vì nó là nơi gắn nghiệp vụ "cấp chứng chỉ khi hoàn thành".
Bài học: Quan hệ N:M gần như luôn cần một entity trung gian, và entity trung gian đó thường là nơi nghiệp vụ thật sự "sống". Trực giác QA về việc dữ liệu sẽ "vỡ" ở đâu giúp BA chọn đúng mô hình ngay từ conceptual.
Tình huống 2 — E-commerce: cái bẫy "lưu giá trong một chỗ"
Tại một công ty thương mại điện tử giả định tên ShopGo ở Hà Nội, BA nhận yêu cầu làm báo cáo doanh thu. Khi đọc physical model hiện có, BA phát hiện đơn hàng chỉ tham chiếu tới product_id và lấy price trực tiếp từ bảng Sản phẩm.
Với con mắt QA, BA hỏi ngay một câu kinh điển: "Nếu shop đổi giá sản phẩm từ 200.000đ lên 250.000đ vào tháng sau, thì đơn hàng cũ tháng trước sẽ hiển thị giá nào?". Câu trả lời khiến cả đội tái mặt: báo cáo doanh thu sẽ bị tính lại sai toàn bộ lịch sử, vì giá cũ không được "đóng băng".
Giải pháp đúng ở tầng logical: bảng OrderItem (chi tiết đơn hàng) phải có cột unit_price riêng, lưu lại giá tại thời điểm mua, độc lập với bảng Sản phẩm. Đây là một quyết định mô hình hóa nghiệp vụ rất quan trọng — gọi là lưu "snapshot" giá. Ở tầng physical, BA và dev thống nhất kiểu DECIMAL(12,2) cho tiền (không bao giờ dùng FLOAT cho tiền, vì sai số làm lệch vài đồng — đúng kiểu lỗi mà QA ám ảnh).
Bài học: Một số thuộc tính trông như "lặp lại" nhưng thực ra là cố ý — vì nghiệp vụ cần giữ lịch sử. BA giỏi phân biệt được khi nào chuẩn hóa và khi nào cần "đóng băng" dữ liệu. Tư duy boundary và "điều gì xảy ra khi thay đổi theo thời gian" của QA chính là siêu năng lực ở đây.
Tình huống 3 — Banking outsource: đọc ERD để bắt lỗ hổng yêu cầu
Một công ty outsource ở Đà Nẵng nhận dự án ví điện tử cho khách hàng Singapore. BA (cựu Tester có 3 năm test app ngân hàng) được phát một logical ERD dài 24 bảng từ phía khách. Thay vì đọc cho có, BA soi quan hệ giữa Account (tài khoản) và Transaction (giao dịch).
BA phát hiện: quan hệ được vẽ là một Transaction tham chiếu tới một Account. Nhưng từ kinh nghiệm test chuyển khoản, BA biết một giao dịch chuyển tiền luôn có hai phía — tài khoản nguồn và tài khoản đích. Mô hình hiện tại không thể biểu diễn được điều đó, nghĩa là về sau dev sẽ phải chắp vá, và sẽ sinh hàng loạt bug đối soát số dư.
BA đề xuất sửa: Transaction cần hai khóa ngoại from_account_id và to_account_id (hoặc tách thành mô hình double-entry với bảng dòng bút toán). Phát hiện này — chỉ từ việc đọc cardinality của một quan hệ — giúp khách hàng tránh một đợt refactor tốn hàng trăm giờ công.
Bài học: BA không cần tự dựng mô hình banking phức tạp; chỉ cần đọc ERD đủ kỹ để đối chiếu với nghiệp vụ thật. Kỹ năng "không tin cái sơ đồ, phải hỏi điều gì xảy ra ở trường hợp biên" là đúng bản chất QA, và nó cực kỳ giá trị khi review mô hình dữ liệu.
Hướng dẫn từng bước
Đây là quy trình dựng một ERD từ một yêu cầu nghiệp vụ, áp dụng được cho hầu hết bài toán BA.
Bước 1 — Gạch chân danh từ và động từ trong yêu cầu. Đọc mô tả nghiệp vụ, gạch chân các danh từ (ứng viên cho entity: Khách hàng, Đơn hàng, Sản phẩm) và động từ nối (ứng viên cho relationship: đặt, chứa, thuộc về). Đây là kỹ thuật "noun-verb analysis" kinh điển.
Bước 2 — Lọc entity thật. Không phải danh từ nào cũng là entity. "Họ tên" là thuộc tính của Khách hàng, không phải entity riêng. Hỏi: "Mình có cần lưu nhiều bản ghi của thứ này, với nhiều thuộc tính không?" Nếu có, đó là entity.
Bước 3 — Dựng Conceptual Model. Vẽ các entity dạng ô vuông, nối bằng đường quan hệ, ghi rõ động từ ("đặt", "chứa"). Chưa thêm thuộc tính. Mang sơ đồ này đi xác nhận với stakeholder nghiệp vụ — đây là lúc bắt sai sót về phạm vi rẻ nhất.
Bước 4 — Xác định cardinality và optionality cho từng quan hệ. Với mỗi cặp entity, hỏi đủ bốn câu: "Một A có tối đa mấy B? Tối thiểu mấy B? Một B có tối đa mấy A? Tối thiểu mấy A?". Đây là bước phát huy tối đa tư duy QA về trường hợp biên.
Bước 5 — Lên Logical Model. Thêm thuộc tính cho mỗi entity, chọn khóa chính, thêm khóa ngoại để hiện thực hóa quan hệ 1:N. Với mọi quan hệ N:M, tạo entity trung gian. Áp dụng chuẩn hóa tới 3NF để dữ liệu không trùng lặp vô lý.
Bước 6 — Bàn giao cho dev để ra Physical Model. BA cùng dev xác định kiểu dữ liệu, ràng buộc (NOT NULL, UNIQUE), và những quyết định nghiệp vụ ẩn trong kỹ thuật (tiền dùng DECIMAL, thời gian lưu UTC hay giờ địa phương, trạng thái dùng enum gì).
Bước 7 — Đối chiếu ngược với use case. Lấy vài kịch bản nghiệp vụ thật ("khách hủy đơn", "đổi giá sản phẩm") chạy thử trên mô hình. Nếu mô hình không biểu diễn được kịch bản, mô hình sai — đúng tinh thần "test lại thiết kế".
Lỗi thường gặp & mẹo
- Nhầm thuộc tính thành entity (và ngược lại). "Trạng thái đơn hàng" thường là một thuộc tính có giá trị enum, không cần thành entity — trừ khi nghiệp vụ cần lưu lịch sử chuyển trạng thái, lúc đó mới cần entity riêng. Mẹo: hỏi "có cần nhiều thuộc tính cho nó không?".
- Bỏ quên entity trung gian cho quan hệ N:M. Đây là lỗi phổ biến nhất. Mỗi khi thấy "nhiều — nhiều", phản xạ phải là "cần một bảng nối". Và bảng nối thường mang thuộc tính nghiệp vụ riêng (ngày ghi danh, vai trò, số lượng).
- Đi thẳng vào Physical, bỏ qua Conceptual. Nhiều BA mới háo hức bàn kiểu dữ liệu với dev trước khi stakeholder nghiệp vụ kịp xác nhận phạm vi. Hệ quả: xây xong mới biết thiếu hẳn một entity. Luôn đi từ Conceptual lên.
- Quên optionality. Chỉ ghi "1:N" mà không nói bên nào bắt buộc, bên nào không. Câu "một đơn hàng có thể không có sản phẩm nào" nghe vô lý nhưng nếu mô hình cho phép, dev sẽ tạo ra đơn rỗng. Tư duy QA: luôn hỏi "tối thiểu là bao nhiêu, zero có hợp lệ không?".
- Dùng FLOAT cho tiền. Lỗi kinh điển sinh sai số. Luôn dùng
DECIMAL/NUMERIC. Đây đúng là loại bug QA hay bắt, nay bạn chặn nó từ khâu thiết kế. - Mẹo công cụ: dùng draw.io (miễn phí), Lucidchart, hoặc dbdiagram.io. Riêng dbdiagram.io rất hợp với BA xuất thân QA vì bạn gõ mô hình bằng cú pháp text đơn giản và nó tự vẽ ERD — nhanh, dễ version, dễ review như đọc code.
- Mẹo giao tiếp: với stakeholder nghiệp vụ chỉ dùng Conceptual; với dev dùng Logical/Physical. Đừng dí ERD 24 bảng vào mặt CEO.
Bài tập thực hành
Hãy làm bài tập sau cho một nền tảng đặt lịch phòng khám (giả định):
Mô tả nghiệp vụ: "Bệnh nhân đặt lịch hẹn với bác sĩ. Mỗi lịch hẹn thuộc về một bệnh nhân và một bác sĩ, diễn ra tại một phòng khám. Một bác sĩ có thể làm việc ở nhiều phòng khám. Mỗi lịch hẹn có ngày giờ, lý do khám, và trạng thái (đã đặt / đã khám / đã hủy). Sau khi khám, bác sĩ có thể kê một hoặc nhiều đơn thuốc cho lịch hẹn đó."
- Liệt kê các entity bằng cách gạch chân danh từ. (Gợi ý: có ít nhất 5 entity, trong đó có một entity trung gian.)
- Vẽ Conceptual Model với các quan hệ và động từ nối.
- Xác định cardinality và optionality cho mọi quan hệ. Đặc biệt chú ý quan hệ giữa Bác sĩ và Phòng khám.
- Chuyển sang Logical Model: thêm thuộc tính, chọn PK, thêm FK. Quan hệ N:M nào cần entity trung gian?
- Đề xuất kiểu dữ liệu cho 3 thuộc tính sau ở tầng Physical: ngày giờ hẹn, trạng thái, và (nếu có) chi phí khám.
- Câu hỏi QA-mindset: Mô hình của bạn có cho phép một lịch hẹn tồn tại mà không có bệnh nhân không? Có chặn việc đặt hai lịch hẹn trùng giờ cho cùng một bác sĩ không? Nếu chưa, mô hình thiếu ràng buộc gì?
Tóm tắt
Data Modeling không phải là kỹ năng "của dev" — nó là cầu nối giữa nghiệp vụ và kỹ thuật, và đó đúng là vùng đất của BA. Người từ QA bước vào lĩnh vực này với lợi thế hiếm: trực giác về ràng buộc dữ liệu, về trường hợp biên, về điều gì sẽ "vỡ" khi dữ liệu thay đổi theo thời gian.
Ba điều cần khắc cốt ghi tâm:
- Ba tầng mô hình phục vụ ba đối tượng: Conceptual cho nghiệp vụ (entity + relationship), Logical cho phân tích (đủ thuộc tính, chuẩn hóa, không phụ thuộc DB), Physical cho kỹ thuật (kiểu dữ liệu, index, DBMS cụ thể). Luôn đi từ trên xuống.
- Cardinality và optionality là nơi tư duy QA tỏa sáng. Mỗi quan hệ phải trả lời được "tối đa mấy, tối thiểu mấy, bắt buộc hay không".
- Quan hệ N:M luôn cần entity trung gian, và entity đó thường là nơi nghiệp vụ thật sự sống (ghi danh, chi tiết đơn hàng, lịch làm việc).