🔥Được vừa ra mắt khóa học Node.js Super đấy, xem thử đi nào

Thiết kế cơ sở dữ liệu bằng MongoDB sao cho chuẩn

🎉 Nếu anh em thấy Front-End cạnh tranh quá thì chúng ta học thêm Back-End thôi.

Mình đang có khóa học🏆 Node.js Super, với các kiến thức như: Express.js, TypeScript, MongoDB, Socket.io, Docker, AWS, Swagger, ...

Vậy nên anh em có thể mua ngay từ bây giờ để tăng khả năng pass phỏng vấn nhé 😉

Thế kế cơ sở dữ liệu như thế nào với database là MongoDB?

Hay còn gọi là thiết kế MongoDB schema.

Đây là câu hỏi đầu tiên chúng ta đặt ra mà khi chúng ta bắt đầu code một dự án với MongoDB.

Và câu trả lời là, tùy vào từng trường hợp.

Vì có rất nhiều yếu tố ảnh hưởng đến schema của bạn. Ví dụ:

  • Ứng dụng có tính chất đọc hay ghi nhiều?
  • Dữ liệu nào thường được truy cập cùng nhau?
  • Các yếu tố về hiệu suất của bạn như thế nào?
  • Dữ liệu của bạn sẽ tăng và mở rộng như thế nào?

Trong bài viết này, chúng ta tìm hiểu về cách mô hình hóa database bằng việc sử dụng các ví dụ thực tế. Các bạn sẽ học được các phương pháp phổ biến khi thiết kế database schema cho ứng dụng của mình.


🥇Cách tiếp cận thiết kế cơ sở dữ liệu - Relational vs MongoDB

Nhiều anh em đã học môn "Thiết kế cơ sở dữ liệu" ở trường đại học học, thường bê nguyên kiểu thiết kế dành cho SQL database sang MongoDB. Điều này là không đúng.

Vì MongoDB là một NoSQL database, đồng ý là nó sẽ những điểm tương đồng khi thiết kế theo kiểu SQL nhưng nếu các bạn bê nguyên kiểu thiết kế SQL sang MongoDB thì sẽ không tận dụng được những điểm mạnh của MongoDB.

Để dễ dàng tìm ra cách thiết kế đúng trong MongoDB, mình nghĩ rằng chúng ta nên so sánh giữa cách thiết kế trong SQL và MongoDB.

🥈Thiết kế cơ sở dữ liệu quan hệ

Khi thiết kế các schema cho một cơ sở dữ liệu quan hệ, anh em dev chúng ta thường chia dữ liệu thành các bảng, sao cho không bị nhân đôi dữ liệu.

Cùng nhìn ví dụ dưới đây, chúng ta có 3 bảng Users, ProfessionsCars đại diện cho dữ liệu của người dùng.

Khi được chia theo SQL
Khi được chia theo SQL

Dữ liệu người dùng được chia thành những bảng riêng biệt, và chúng có thể được JOINED với nhau bằng cách sử dụng khóa ngoại trong cột user_id của bảng ProfessionsCars

Bây giờ hãy xem cách chúng ta thiết kế lại chúng theo MongoDB nhé!

🥈Thiết kế cơ sở dữ liệu MongoDB

MongoDB schema hoạt động khác với SQL. Với kiểu thiết kế cơ sở dữ liệu theo MongoDB, chúng ta sẽ:

  • Không có quy trình chính thức
  • Không có những thuật toán
  • Không có những quy tắc
Thiết kế cơ sở dữ liệu bằng MongoDB sao cho chuẩn

Khi bạn thiết kế cơ sở dữ liệu bằng MongoDB, bạn chỉ cần quan tâm là thiết kế đó hoạt động tốt cho ứng dụng của bạn là được. 2 App khác nhau, xử dụng data giống hệt nhau nhưng có thể nó sẽ có những schema khác nhau. Đơn giản vì 2 app đó được sử dụng theo những cách khác nhau.

Khi thiết kế cơ sở dữ liệu chúng ta nên quan tâm:

  • Lưu data
  • Cung cấp hiệu suất tốt khi query
  • Yêu cầu phần cứng hợp lý

Bây giờ chúng ta cùng xem lại ví dụ trên như thế nào khi thiết kế theo MongoDB nhé

json
{
"first_name": "Paul",
"surname": "Miller",
"cell": "447557505611",
"city": "London",
"location": [45.123, 47.232],
"profession": ["banking", "finance", "trader"],
"cars": [
{
"model": "Bentley",
"year": 1973
},
{
"model": "Rolls Royce",
"year": 1965
}
]
}

Bạn có thể thấy, thay vì chia nhỏ dữ liệu thành từng collection, chúng ta tận dụng lợi thế của MongoDB document có thể lưu trữ array và object bên trong User object. Bây giờ chỉ với một query đơn giản, chúng ta có thể kéo tất cả dữ liệu về ứng dụng của chúng ta.


🥇Nhúng vs Tham chiếu

Khi thiết kế schema cho MongoDB, chúng ta sẽ đứng ở giữa 2 lựa chọn là Embedding hay Referencing, hay còn gọi là nhúng và tham chiếu.

Nhúng có nghĩa là đưa hết data vào trong một document, còn tham chiếu là lưu trữ data trong một document thuộc collection riêng biệt và tham chiếu đến nó thông qua việc sử dụng khóa ngoại và toán tử $lookup (tương tự JOIN trong SQL).

🥈Nhúng

🥉Ưu điểm

  • Bạn có thể truy xuất tất cả thông tin liên quan trong một query
  • Tránh việc join hoặc lookup trong ứng dụng
  • Update các thông tin liên quan trong một query duy nhất

🥉Hạn chế

  • Khi document lớn lên sẽ gây gánh nặng cho những trường không liên quan. Bạn có thể tăng hiệu suất truy vấn bằng cách hạn chế kích thước của các document mà bạn gửi qua cho mỗi truy vấn.

  • Giới hạn cho document là 16 MB trong MongoDB. Nếu bạn nhúng quá nhiều dữ liệu bên trong một document duy nhất, bạn có thể đụng phải giới hạn này.

🥈Tham chiếu

Tham chiếu hoạt động tương tự như toán tử JOIN trong một truy vấn SQL. Nó cho phép chúng ta chia dữ liệu để tạo ra các truy vấn hiệu quả và có thể mở rộng hơn, nhưng vẫn duy trì mối quan hệ giữa dữ liệu.

🥉Ưu điểm

  • Bằng cách chia dữ liệu, bạn sẽ có các document nhỏ hơn.
  • Ít khả năng đạt giới hạn 16-MB cho mỗi document.
  • Những dữ liệu không cần thiết sẽ không được đính kèm vào các truy vấn.
  • Giảm lượng trùng lặp dữ liệu. Tuy nhiên, điều quan trọng cần lưu ý là đôi khi chúng ta chấp nhận trùng lặp dữ liệu để đem lại một schema tốt hơn.

🥉Hạn chế

  • Để truy xuất được hết data, chúng ta cần tối thiểu là 2 query hoặc dùng $lookup

🥇Các loại quan hệ

🥈Quan hệ 1-1 (One-to-One)

Ví dụ 1 người dùng thì chỉ có một email đăng ký duy nhất và ngược lại, thì đây nghĩa là quan hệ 1-1. Chúng ta có thể mô hình hóa quan hệ 1-1 bằng cặp key-value trong database

json
{
"_id": "ObjectId('AAA')",
"name": "Joe Karlsson",
"email": "joe@gmail.com",
"company": "MongoDB",
"twitter": "@JoeKarlsson1",
"twitch": "joe_karlsson",
"tiktok": "joekarlsson",
"website": "joekarlsson.com"
}

Hoặc một người dùng và bằng lái xe: Mỗi người chỉ có một bằng lái xe, và mỗi bằng lái xe chỉ thuộc về một người. Đây cũng là quan hệ 1-1.

🥈Quan hệ 1 - ít (One-to-Few)

Một người dùng có 1 vài địa chỉ nhận hàng, thì đây là quan hệ 1 - ít.

Với quan hệ 1 - ít thì chúng ta có thể nhúng array bên trong object User như sau

json
{
"_id": "ObjectId('AAA')",
"name": "Joe Karlsson",
"company": "MongoDB",
"twitter": "@JoeKarlsson1",
"twitch": "joe_karlsson",
"tiktok": "joekarlsson",
"website": "joekarlsson.com",
"addresses": [
{ "street": "123 Sesame St", "city": "Anytown", "cc": "USA" },
{ "street": "123 Avenue Q", "city": "New York", "cc": "USA" }
]
}

Có nhớ rằng mình đã nói là không có quy tắc nào khi thiết kế schema cho MongoDB không?

Ừ, mình đã nói dối. Mình đã tạo ra một số quy tắc giúp bạn thiết kế schema dễ dàng hơn.

Quy tắc 1: Ưu tiên nhúng trừ khi chúng ta có một lý do thuyết phục để không làm như vậy

Nói chung thì mình hay nhúng data vào trong document. Mình chỉ lôi nó ra một collection riêng khi nó quá lớn, cần truy cập riêng, hoặc ít dùng đến.

Tóm lại là ưu tiên nhúng cho quan hệ 1 - ít

🥈Quan hệ 1 - Nhiều (One-to-Many)

Được rồi, bây giờ hãy tưởng tượng chúng ta có một cái xe đạp với rất nhiều thành phần bên trong. Số lượng các chi tiết có thể lên đến hàng ngàn chi tiết. Ví dụ như bánh xe, đèn, phanh, đồng hồ, còi, v.v. Các bạn tưởng tượng mỗi cái bánh xe, đèn này chỉ thuộc về cái xe đạp này thôi. Vậy thì đây là quan hệ 1 - nhiều.

Yêu cầu của website là khi vừa vào trang chi tiết sản phẩm, thì hãy show ra thông tin sơ bộ của sản phẩm đó, và các chi tiết của sản phẩm đó. Chúng ta có thể click vào từng phần của cái xe để xem thông tin chi tiết của nó.

Thiết kế cơ sở dữ liệu bằng MongoDB sao cho chuẩn

Với kiểu 1 - nhiều thì chúng ta nếu nhúng thông thường thì rất dễ document của chúng ta sẽ chạm đến giới hạn. Vậy nên giải pháp là tách những thành phần kia thành một collection riêng biệt gọi là Parts. ProductsParts sẽ liên kết với nhau thông qua id

Collection Products:

json
{
"name": "left-handed smoke shifter",
"manufacturer": "Acme Corp",
"catalog_number": "1234",
"parts": ["ObjectID('AAAA')", "ObjectID('BBBB')", "ObjectID('CCCC')"]
}

Collection Parts:

json
{
"_id": "ObjectID('AAAA')",
"partno": "123-aff-456",
"name": "#4 grommet",
"qty": "94",
"cost": "0.94",
"price": " 3.99"
}

Quy tắc 2: Khi cần truy cập vào một đối tượng riêng biệt, đây là lúc không dùng nhúng

Quy tắc 3: Tránh joins/lookups nếu có thể, nhưng cũng đừng sợ nếu nó giúp chúng ta có một schema tốt hơn

🥈Quan hệ 1 - rất nhiều

Điều gì sẽ xảy ra nếu chúng ta có một schema mà có khả năng có đến hàng triệu các document phụ thuộc. À, mình biết bạn đang nghĩ gì, ngoài đời liệu có trường hợp nào có đến hàng triệu, hàng tỉ document phụ thuộc không? Câu trả lời là có, và nó rất thực tế.

Hãy cùng tưởng tượng chúng ta tạo một ứng dụng ghi log server. Mỗi máy chủ có thể lưu trữ hàng tỉ message log. Nếu dùng array trong MongoDB, cho dù các bạn đã dùng array Object ID thì cũng có khả năng các bạn chạm đến giới hạn là 16 MB cho document. Vậy nên chúng ta cần suy nghĩ lại cách thiết kế làm sao cho khi database phình to ra thì vẫn không bị giới hạn.

Bây giờ, thay vì tập trung mối quan hệ giữa host và log message, chúng ta hãy nhìn ngược lại, mỗi log message sẽ lưu trữ một host mà nó thuộc về. Bằng cách này thì chúng ta sẽ không sợ bị giới hạn bởi 16 MB nữa.

Hosts:

json
{
"_id": ObjectID("AAAB"),
"name": "goofy.example.com",
"ipaddr": "127.66.66.66"
}

Log Message:

json
{
"time": ISODate("2014-03-28T09:42:41.382Z"),
"message": "cpu is on fire!",
"host": ObjectID("AAAB")
}

Quy tắc 4: Array không nên phát triển không giới hạn. Nếu có hơn vài trăm document ở phía "nhiều" thì đừng nhúng chúng; Nếu có hơn vài ngàn document ở phía "nhiều" thì đừng sử dụng array ObjectID tham chiếu. Mảng với số lượng lớn item là lý do không nên dùng nhúng.

🥈Quan hệ Nhiều - Nhiều (Many-to-Many)

Schema Pattern cuối cùng mà chúng ta sẽ học là mối quan hệ nhiều nhiều. Đây là kiểu mà chúng ta rất hay gặp trong thực tế. Ví dụ chúng ta build một app todo list, một user có thể có nhiều task, và một task có thể có nhiều user được assign vào.

Lúc này chúng ta cần tham chiếu qua lại giữa các collection.

Users:

json
{
"_id": ObjectID("AAF1"),
"name": "Kate Monster",
"tasks": [ObjectID("ADF9"), ObjectID("AE02"), ObjectID("AE73")]
}

Tasks:

json
{
"_id": ObjectID("ADF9"),
"description": "Write blog post about MongoDB schema design",
"due_date": ISODate("2014-04-01"),
"owners": [ObjectID("AAF1"), ObjectID("BB3G")]
}

Từ ví dụ này, chúng ta có thể thấy mỗi user có một sub array các ObjectID task, và mỗi task cũng có một sub array các ObjectID owners.


🥇Tóm lại

Như các bạn thấy đấy, cơ bản thì thiết kế cơ sở dữ liệu bằng MongoDB nó cũng có phần tương đồng với các hệ quản trị cơ sở dữ liệu quan hệ. Tuy nhiên chúng ta nên biết cách để tận dụng lợi thế của việc nhúng dữ liệu vào document hay tham chiếu document sử dụng toán tử $lookup, từ đó có thể phát huy được tối đa hiệu quả của MongoDB.

Mình muốn tóm tắt bài viết này với một quy tắc quan trọng nhất

Quy tắc 5: Với MongoDB, cách bạn mô hình hóa dữ liệu phụ thuộc vào cách bạn sử dụng dữ liệu. Bạn muốn cấu trúc dữ liệu của bạn phù hợp với cách mà ứng dụng của bạn query và update nó.

Hãy nhớ rằng mỗi ứng dụng có một yêu cầu riêng, vậy nên thiết kế của schema sẽ phản ánh nhu cầu cụ thể ứng dụng đó.

🥈Recap

  • 1 - 1: Ưu tiên cặp key-value trong document
  • 1 - ít: Ưu tiên nhúng
  • 1 - nhiều: Ưu tiên tham chiếu
  • 1 - rất nhiều: Ưu tiên tham chiếu
  • Nhiều - Nhiều: Ưu tiên tham chiếu

Các quy tắc khi thiết kế cơ sở dữ liệu bằng MongoDB

  • Quy tắc 1: Ưu tiên nhúng trừ khi chúng ta có một lý do thuyết phục để không làm như vậy

  • Quy tắc 2: Khi cần truy cập vào một đối tượng riêng biệt, đây là lúc không dùng nhúng

  • Quy tắc 3: Tránh joins/lookups nếu có thể, nhưng cũng đừng sợ nếu nó giúp chúng ta có một schema tốt hơn

  • Quy tắc 4: Array không nên phát triển không giới hạn. Nếu có hơn vài trăm document ở phía "nhiều" thì đừng nhúng chúng; Nếu có hơn vài ngàn document ở phía "nhiều" thì đừng sử dụng array ObjectID tham chiếu. Mảng với số lượng lớn item là lý do không nên dùng nhúng.

  • Quy tắc 5: Với MongoDB, cách bạn mô hình hóa dữ liệu phụ thuộc vào cách bạn sử dụng dữ liệu. Bạn muốn cấu trúc dữ liệu của bạn phù hợp với cách mà ứng dụng của bạn query và update nó.


🥇Tài liệu tham khảo thêm


Khóa học ReactJs giúp bạn chinh phục mức lương 25 - 30 triệu/tháng

Phew! Cuối cùng bạn cũng đã đọc xong. Bài viết này có hơi dài một tí vì mình muốn nó đầy đủ nhất có thể 😅

Chúng ta đều hiểu rằng Javascript và React không hề dễ, chúng có quá nhiều concept cần phải học. Mình cũng cảm thấy nó khó! Nay lại có thể Typescript nữa 🥲, thật sự khó nuốt.

Nhưng đừng lo: Bạn có thể nắm vững các kiến thức trên chỉ trong một khóa học ReactJs Super - Shopee Clone Typescript

Mình đã bắt đầu code React vào năm 2019, và nó đã trở thành thư viện ưa thích của mình để xây dựng UI và web app. Mình cũng đã làm việc với nhiều framework khác như Angular, Vue nhưng thực sự chỉ có React là đem lại cho mình cảm xúc và sự hiệu quả. 💓

Nếu bạn đang gặp khó khăn với React, mình ở đây để giúp bạn!

Mình đã dành hơn 6 tháng để phát triển khóa học ReactJs Super - Shopee Clone Typescript. Trong khóa này bạn sẽ được học mọi thứ về thư viện ReactJs, các kiến thức từ cơ bản cho đến nâng cao nhất, mục đích của mình là giúp bạn chinh phục mức lương 25 - 30 triêu/tháng

Nếu bạn cảm thấy bài viết này của mình hữu ích, mình nghĩ bạn sẽ thích hợp với phong cách dạy của mình. Không như bài viết này, khóa học là sự kết hợp giữa các bài viết, video, quizz, bài tập nhỏ và dự án lớn có thể xin việc được ngay. Học xong mình đảm bảo bạn sẽ lên tay ngay. 💪🏻

Avatar Dư Thanh Được
Dư Thanh Được
Một developer thích nghiên cứu và chia sẻ kiến thức về lập trình, blockchain, marketing. Chuyên code và dạy lập trình website