Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 19 — Hands-on: ERD cho E-commerce VN

Data Modeling and ERD Mastery Bài 19/60

Mở đầu — vì sao bài này quan trọng

Bạn đã đi qua một chặng đường dài: từ entity, attribute, primary key, relationship, cardinality, cho đến ba tầng mô hình (conceptual, logical, physical) và cả chuẩn hóa. Bài 18 trước đó bạn đã thực hành ERD cho một hệ thống ngân hàng — nơi tính chính xác và tính toàn vẹn dữ liệu được đặt lên hàng đầu. Hôm nay chúng ta bước sang một thế giới rất khác: marketplace e-commerce kiểu Tiki, Shopee, Lazada.

Vì sao e-commerce lại đáng để dành riêng một bài thực hành? Bởi vì nó là một trong những domain phức tạp và "đời" nhất mà một Business Analyst gặp phải. Nó không chỉ có khách hàng và sản phẩm. Nó là một marketplace nhiều bên (multi-sided): có người mua, có người bán (seller/merchant), có sàn đứng giữa, có shipper, có kho, có khuyến mãi, có giỏ hàng, có đơn hàng, có thanh toán, có đánh giá. Mỗi thực thể đều ràng buộc lẫn nhau. Một quyết định mô hình sai ở đây — ví dụ gắn giá vào sản phẩm thay vì gắn vào biến thể — sẽ khiến cả hệ thống không thể bán nổi một chiếc áo có nhiều size màu.

Đây cũng là domain mà gần như mọi BA Việt Nam sẽ chạm tới, dù bạn làm cho startup bán lẻ, cho ngân hàng làm ví điện tử, hay cho một doanh nghiệp truyền thống muốn lên sàn. Nắm vững ERD e-commerce nghĩa là bạn nắm được "ngôn ngữ chung" của thương mại số. Bài này sẽ dẫn bạn dựng một ERD hoàn chỉnh từ con số không, bám sát bối cảnh sàn Việt Nam.

Khái niệm cốt lõi

Trước khi vẽ, hãy thống nhất bức tranh các thực thể cốt lõi của một marketplace. Tôi sẽ không lặp lại lý thuyết về cách định nghĩa entity hay cardinality (bạn đã học ở Bài 4–8) — thay vào đó tập trung vào cách áp dụng chúng vào đúng domain e-commerce VN.

Hai vai trò người dùng: Customer và Seller

Trong marketplace, có hai loại "người" hoàn toàn khác nhau về nghiệp vụ:

  • Customer (khách mua): có thông tin liên hệ, nhiều địa chỉ giao hàng, lịch sử đơn hàng.
  • Seller / Merchant (nhà bán hàng): có thông tin gian hàng (shop), mã số thuế, tài khoản nhận tiền, danh mục sản phẩm họ đăng bán.
Một bài học quan trọng: trên Shopee hay Tiki, một người vừa có thể mua vừa có thể bán. Vì vậy nhiều hệ thống tách User (tài khoản đăng nhập) ra khỏi CustomerSeller (hai hồ sơ vai trò gắn vào cùng một User). Đây là một quyết định thiết kế bạn cần cân nhắc ngay từ đầu.

Product, Variant và SKU — nơi người mới hay vấp nhất

Đây là trái tim của ERD e-commerce, và cũng là chỗ dễ sai nhất. Hãy phân biệt rõ ba tầng:

  • Product (sản phẩm cha): ví dụ "Áo thun nam Cotton 100%". Mang thông tin chung: tên, mô tả, danh mục, thuộc seller nào.
  • Variant / SKU (biến thể): "Áo thun nam — Size L — màu Đen". Đây mới là thứ thực sự có giátồn kho riêng. Mỗi tổ hợp size-màu là một SKU (Stock Keeping Unit).
  • Category (danh mục): phân loại sản phẩm theo cây phân cấp (Thời trang > Nam > Áo thun).
Nguyên tắc vàng: giá và tồn kho thuộc về variant, không thuộc về product. Nếu bạn gắn giá vào Product, bạn sẽ không bao giờ bán được một sản phẩm có nhiều mức giá theo size.

Order, Order Item và Payment

Khi khách bấm "Đặt hàng", phát sinh chuỗi thực thể:

  • Cart (giỏ hàng): tạm thời, chứa các CartItem trỏ tới variant.
  • Order (đơn hàng): bản ghi cố định khi đã đặt — gắn với Customer, địa chỉ giao, trạng thái, tổng tiền.
  • Order Item (chi tiết đơn): mỗi dòng là một variant với giá tại thời điểm mua (price snapshot) và số lượng.
  • Payment (thanh toán): phương thức (COD, VNPay, MoMo, ZaloPay, thẻ), trạng thái, số tiền.
  • Shipment (vận chuyển): đơn vị giao (GHN, GHTK, Viettel Post), mã vận đơn, trạng thái.
Một điểm cực kỳ quan trọng và rất "Việt Nam": một Order trên marketplace thường bị tách thành nhiều đơn con theo từng Seller. Khách bỏ vào giỏ 3 món của 2 shop khác nhau → khi đặt sẽ sinh 2 đơn (2 shop, 2 phí ship, 2 luồng giao). Bạn phải mô hình hóa được điều này.

Promotion, Review và các thực thể vệ tinh

  • Promotion / Voucher: mã giảm giá, freeship, áp theo điều kiện.
  • Review / Rating: đánh giá sản phẩm, gắn với Customer + Product (hoặc Order Item để chống review ảo).
  • Wishlist, Address, Notification: các thực thể phụ trợ.

Tình huống thực tế

Ví dụ 1 — Sàn "ChợViệt" và bài toán biến thể sản phẩm

Một startup giả định tên ChợViệt muốn làm marketplace thời trang. Đội dev ban đầu thiết kế đơn giản: bảng Product có cột gia, cột so_luong_ton. Mọi thứ chạy ngon — cho đến khi một shop đăng bán "Giày sneaker" có 5 size (38–42) và 3 màu, tức 15 tổ hợp, mỗi tổ hợp giá và tồn kho khác nhau.

Hệ thống cũ buộc shop phải tạo 15 sản phẩm riêng lẻ "Giày sneaker size 38 đen", "Giày sneaker size 38 trắng"... Kết quả: trang sản phẩm hiển thị 15 thẻ giày trông y hệt nhau, khách rối loạn, SEO trùng lặp, và khi shop muốn sửa mô tả thì phải sửa 15 lần.

Diễn giải: ChợViệt thiếu tầng ProductVariant. ERD đúng phải là: Product (1) — (N) ProductVariant, trong đó variant giữ gia, so_luong_ton, sku_code, và các thuộc tính phân biệt (size, color) thông qua bảng VariantAttribute. Product chỉ giữ thông tin chung.

Bài học rút ra: Ngay khi domain của bạn có sản phẩm "có thể có nhiều phiên bản", hãy tách Product/Variant từ đầu. Đây gần như là quyết định không thể đảo ngược rẻ tiền — sửa sau khi đã có dữ liệu thật rất đắt.

Ví dụ 2 — Tiki-like và đơn hàng tách theo seller

Hãy hình dung một sàn kiểu Tiki. Khách hàng tên Lan bỏ vào giỏ: 1 quyển sách (bán bởi Tiki Trading), 1 chai dầu gội (bán bởi Shop Mỹ Phẩm An), 1 củ sạc (bán bởi Shop Điện Tử Bình). Khi Lan bấm "Mua", chuyện gì xảy ra ở tầng dữ liệu?

Nếu mô hình của bạn chỉ có một Order chứa tất cả OrderItem, bạn sẽ gặp ác mộng vận hành: ba shop ở ba tỉnh, ba phí ship khác nhau, ba luồng đóng gói, ba thời gian giao. Khi Shop An giao xong nhưng Shop Bình hủy đơn, trạng thái của "đơn hàng" là gì? Không thể trả lời.

Sàn thật giải quyết bằng cách: từ một lần "checkout" (ta gọi là OrderGroup hoặc giữ một Order cha), hệ thống tách ra nhiều Order con theo từng Seller. Mỗi Order con có trạng thái độc lập, phí ship riêng, shipment riêng. Payment thì có thể gom một lần ở cấp OrderGroup (khách trả một lần qua VNPay 850.000đ), rồi đối soát chia về từng seller sau.

Diễn giải ERD: Quan hệ trở thành OrderGroup (1) — (N) Order, Order (1) — (1) Seller, Order (1) — (N) OrderItem. Payment gắn ở OrderGroup.

Bài học rút ra: Bản chất marketplace là multi-seller. Cardinality giữa Order và Seller phải là N–1, và bạn cần một tầng gom nhóm cho trải nghiệm "một lần thanh toán".

Ví dụ 3 — Voucher freeship của Shopee và bài toán phạm vi áp dụng

Trong các đợt sale 9.9, 11.11, Shopee tung ra hàng loạt voucher: mã giảm 50K toàn sàn, mã freeship của riêng shop, mã giảm theo ngành hàng. Một sinh viên thiết kế ERD thường gắn voucher_code thẳng vào bảng Order như một cột text. Vấn đề: làm sao biết voucher đó áp được cho shop nào, ngành hàng nào, đơn tối thiểu bao nhiêu, dùng được mấy lần?

Diễn giải: Cần một thực thể Promotion độc lập với các thuộc tính: loai (giảm tiền/giảm %/freeship), gia_tri, don_toi_thieu, pham_vi (toàn sàn/theo seller/theo category), so_luot_toi_da, ngay_bat_dau, ngay_ket_thuc. Và một bảng nối OrderPromotion (N–N giữa Order và Promotion) vì một đơn có thể dùng cùng lúc nhiều mã (1 mã giảm giá + 1 mã freeship). Việc một customer chỉ được dùng mã 1 lần lại cần thêm bảng PromotionUsage.

Bài học rút ra: Khi một "thuộc tính" có vòng đời, điều kiện và quy tắc riêng (như voucher), nó là một entity, không phải một cột. Đây là tư duy phân biệt entity với attribute mà BA giỏi luôn áp dụng.

Hướng dẫn từng bước

Hãy cùng dựng ERD cho marketplace ChợViệt theo quy trình thực chiến. Tôi khuyến khích bạn mở dbdiagram.io và gõ song song.

Bước 1 — Liệt kê thực thể từ user story. Đọc nghiệp vụ và gạch chân danh từ: "Khách hàng đặt mua sản phẩm của nhà bán hàng, thanh toán, được giao hàng, rồi đánh giá." → Customer, Product, Seller, Order, Payment, Shipment, Review. Bổ sung các thực thể ẩn: User, ProductVariant, Category, Cart, OrderItem, Address, Promotion.

Bước 2 — Phân nhóm thực thể. Chia thành cụm cho dễ quản trị:

  • Cụm người dùng: User, Customer, Seller, Address.
  • Cụm catalog: Category, Product, ProductVariant, VariantAttribute.
  • Cụm giao dịch: Cart, CartItem, Order, OrderItem, Payment, Shipment.
  • Cụm phụ trợ: Promotion, Review, Wishlist.
Bước 3 — Xác định primary key. Hầu hết dùng surrogate key (id tự tăng hoặc UUID — bạn đã học ở Bài 6). Riêng ProductVariant nên có thêm sku_code unique để dễ tích hợp kho.

Bước 4 — Vẽ relationship và cardinality. Dùng Crow's Foot:

  • Customer (1) —< (N) Order
  • Seller (1) —< (N) Product
  • Product (1) —< (N) ProductVariant
  • Order (1) —< (N) OrderItem >— (1) ProductVariant
  • Order (1) — (1) Payment, Order (1) —< (N) Shipment
  • Customer (1) —< (N) Address
  • Order (N) >—< (N) Promotion → phân rã thành bảng nối OrderPromotion
  • Category tự tham chiếu (parent_id) cho cây danh mục — gợi nhớ Bài 21.
Bước 5 — Định nghĩa thuộc tính then chốt. Với OrderItem, bắt buộc có gia_tai_thoi_diem_mua (price snapshot) và so_luong. Với Order có trang_thai (pending/confirmed/shipping/delivered/cancelled), tong_tien, phi_ship. Với Payment có phuong_thuc, trang_thai, ma_giao_dich.

Bước 6 — Xử lý multi-seller. Thêm OrderGroup đứng trên Order, mỗi Order gắn đúng một Seller. Quyết định Payment gắn ở OrderGroup.

Bước 7 — Rà soát và chuẩn hóa. Đảm bảo không có dữ liệu lặp (tên seller không nhét vào Order), khóa ngoại đầy đủ, và các thực thể đều ở 3NF (Bài 12–14). Sau đó kiểm tra ngược: "Tôi có truy được toàn bộ đơn của một shop trong tháng không? Có truy được sản phẩm nào bán chạy nhất không?" Nếu chưa, ERD còn thiếu.

Lỗi thường gặp & mẹo

Lỗi 1 — Gắn giá và tồn kho vào Product. Như ví dụ ChợViệt, đây là lỗi kinh điển. Luôn để giá/tồn ở variant.

Lỗi 2 — Không snapshot giá trong OrderItem. Nếu OrderItem chỉ trỏ tới variant mà không lưu giá lúc mua, thì khi shop đổi giá ngày mai, đơn hàng cũ của khách sẽ "biến hình" theo giá mới. Hóa đơn sai lệch, kế toán không đối soát được. Mẹo: mọi giá trị giao dịch trong quá khứ phải được "đóng băng".

Lỗi 3 — Quên rằng một người vừa mua vừa bán. Đừng nhồi cờ is_seller vào bảng Customer. Tách User ra, gắn hồ sơ Customer và Seller như hai vai trò.

Lỗi 4 — Review không gắn với giao dịch thật. Nếu Review chỉ gắn Customer–Product, ai cũng review được dù chưa mua → review ảo tràn lan. Mẹo: gắn Review với OrderItem (đã giao thành công) để đảm bảo "verified purchase".

Lỗi 5 — Một Order ôm nhiều seller. Như ví dụ Tiki, điều này phá vỡ luồng vận hành. Tách đơn theo seller.

Lỗi 6 — Address dùng cột text tự do. Địa chỉ VN có cấu trúc tỉnh/huyện/xã (Bài 25 sẽ đào sâu). Ít nhất hãy tách Address thành thực thể riêng, nhiều địa chỉ cho một Customer, và đánh dấu địa chỉ mặc định.

Mẹo tổng quát: Khi phân vân "cái này là cột hay là bảng?", hãy hỏi: nó có vòng đời riêng, trạng thái riêng, hay được tham chiếu từ nhiều nơi không? Nếu có — nó là entity.

Bài tập thực hành

Hãy dựng một ERD marketplace hoàn chỉnh cho sàn giả định "ChợViệt" bằng dbdiagram.io hoặc draw.io với yêu cầu sau:

  • Cụm người dùng: Mô hình hóa User, Customer, Seller sao cho một người có thể vừa mua vừa bán. Customer có nhiều Address với một địa chỉ mặc định.
  • Cụm catalog: Dựng Category (cây phân cấp tự tham chiếu), Product, ProductVariant (giữ giá + tồn kho + sku_code), và bảng VariantAttribute để lưu size/màu linh hoạt.
  • Cụm giao dịch: Mô hình Cart → Order → OrderItem với price snapshot. Áp dụng tách đơn theo seller bằng OrderGroup. Thêm Payment (COD/VNPay/MoMo) và Shipment (GHN/GHTK).
  • Khuyến mãi & đánh giá: Thêm Promotion với phạm vi áp dụng, bảng nối OrderPromotion (N–N), bảng PromotionUsage để giới hạn lượt dùng/khách, và Review gắn với OrderItem.
  • Câu hỏi kiểm tra (trả lời bằng cách chỉ ra đường đi trên ERD):
- Liệt kê toàn bộ đơn hàng của một shop trong tháng 6. - Tìm 5 sản phẩm bán chạy nhất theo số lượng. - Một khách dùng được mã "FREESHIP9.9" mấy lần và đã dùng chưa? - Một sản phẩm có điểm đánh giá trung bình bao nhiêu, chỉ tính từ người đã thực mua?

Thử thách nâng cao: Vẽ thêm vòng đời trạng thái của Order (state machine) và chỉ ra mỗi chuyển trạng thái ảnh hưởng tới Payment và Shipment ra sao. Nếu Shop B hủy đơn con trong khi Shop A đã giao, OrderGroup nên thể hiện điều này thế nào?

Hãy lưu file ERD lại — bạn sẽ tái sử dụng nó cho các bài về index, constraint và data warehouse sau này.

Tóm tắt

Trong bài này, chúng ta đã dựng một ERD hoàn chỉnh cho marketplace e-commerce kiểu Việt Nam, từ user story đến mô hình chi tiết. Những điểm cốt lõi cần khắc ghi:

  • Marketplace là multi-sided: tách User khỏi Customer và Seller vì một người có thể đóng cả hai vai.
  • Giá và tồn kho thuộc về ProductVariant, không thuộc Product — đây là quyết định mô hình quan trọng nhất của domain này.
  • Snapshot giá trong OrderItem để dữ liệu lịch sử bất biến.
  • Marketplace cần tách đơn theo seller (OrderGroup → nhiều Order), với Payment gom ở cấp nhóm.
  • Voucher, Review là entity có vòng đời riêng, không phải cột text — đánh giá nên gắn với giao dịch thật để chống review ảo.
Ba tình huống ChợViệt, Tiki-like và voucher Shopee cho thấy: sai lầm ERD ở e-commerce không phải lỗi lý thuyết, mà là lỗi khiến cả sàn không vận hành nổi. Ở Bài 20, chúng ta sẽ học subtype/supertype — công cụ giúp bạn mô hình hóa gọn gàng hơn những thực thể như "phương thức thanh toán" hay "loại người dùng" mà bạn vừa gặp ở đây.