So sánh Mongoose vs MongoDB NodeJS Driver
Châm ngôn của mình là học để kiếm tiền.
Vì thế mình build các khóa học của mình để giúp anh em tiến bộ nhanh hơn x10 lần , để kiếm được nhiều tiền hơn
- 🏆 React.js Super: Trở thành React.js Developer trong 7 ngày với mức thu nhập 20 triệu/tháng
- 🏆 Node.js Super: Giúp bạn học cách phân tích, thiết kế, deploy 1 API Backend bằng Node.js
- 🏆 Next.js Super: Mình sẽ chia sẻ từ A-Z kiến thức về Next.js, thứ giúp mình kiếm hơn 1 tỉ/năm
Trong bài viết này, chúng ta sẽ đi khám phá thư viện Mongoose của cơ sở dữ liệu MongoDB, liệu có đáng để sử dụng thay cho việc dùng MongoDB NodeJS Driver (tức là MongoDB thuần) không.
Chúng ta sẽ tìm hiểu và so sánh về các khía cạnh như:
- Object Data Modeling trong MongoDB
- Thêm validate Schema cho MongoDB
- Populate và lookup
Oke, bắt đầu thôi nha.
🥇Mongoose là gì?
Mongoose là một thư viện Object Data Modeling (ODM) - thư viện mô hình hóa dữ liệu đối tượng cho MongoDB và được xuất bản dưới dạng package npm.
Vấn đề mà Mongoose nhắm đến là giúp ae lập trình viên áp dụng được schema ở tầng ứng dụng (code backend nodejs) của chúng ta. Ngoài việc áp dụng schema, Mongoose cũng cung cấp một loạt các hook, validation model và các tính năng khác nhằm giúp làm cho việc làm việc với MongoDB dễ dàng hơn.
🥇MongoDB Driver là gì?
MongoDB Driver là một thư viện được phát hành chính thức từ MongoDB giúp chúng ta làm việc với MongoDB. Thư viện này có sẵn trên hầu hết mọi ngôn ngữ lập trình, tất nhiên là bao gồm môi trường Node.js.
Dùng MongoDB Driver nghĩa là chúng ta dùng MongoDB thuần túy, không thông qua thư viện trung gian ODM như Mongoose.
🥇Object Data Modeling trong MongoDB
Một lợi ích lớn của việc sử dụng cơ sở dữ liệu NoSQL như MongoDB là bạn không bị ràng buộc vào một mô hình dữ liệu cứng nhắc.
Bạn có thể thêm hoặc xóa các trường, lồng dữ liệu nhiều lớp miễn sao phù hợp với ứng dụng của bạn, ngoài ra nó cũng rất dễ dàng thay đổi trong tương lai.
Nhưng linh hoạt quá mức cũng có thể là một thách thức. Nếu không có sự đồng thuận về cấu trúc mô hình dữ liệu và mỗi document trong một collection chứa các trường khác nhau, chúng ta sẽ gặp khó khăn khi xử lý dữ liệu.
🥈Mongoose Schema và Model
Trước khi bước vào so sánh thì chúng ta tìm hiểu sơ sơ về Schema và Model trong Mongoose đã nhé.
Khi dùng các ODM như Mongoose nó sẽ ép chúng ta vào một schema khá cứng nhắc. Với Mongoose bạn sẽ định nghĩa một object Schema
trong code của mình, nó sẽ ánh xạ với document trong collection trong MongoDB. Sau đó bạn sẽ tạo một Model
từ Schema
trên - model này sẽ dùng để tương tác với collection.
Ví dụ chúng ta thiết kế bài viết cho một blog thì đầu tiên chúng ta sẽ xác định một schema và sau đó là tạo một model tương ứng
const blog = new Schema({
title: String,
slug: String,
published: Boolean,
content: String,
tags: [String],
comments: [
{
user: String,
content: String,
votes: Number
}
]
})
const Blog = mongoose.model('Blog', blog)
Khi mà Mongoose model đã được định nghĩa, chúng ta có thể dễ dàng chạy các câu lệnh query để lấy, cập nhật và xóa dữ liệu trên một collection MongoDB.
Với đoạn code định nghĩa ở trên, chúng ta có thể làm
// Tạo mới một bài viết blog
const article = new Blog({
title: 'Awesome Post!',
slug: 'awesome-post',
published: true,
content: 'This is the best post ever',
tags: ['featured', 'announcement']
})
// Insert bài viết vào MongoDB database
article.save()
// Tìm một bài viết
Blog.findOne({}, (err, post) => {
console.log(post)
})
Bây giờ nếu chúng ta muốn lưu một data mới với kiểu dữ liệu như sau thì Mongoose sẽ báo lỗi, bởi vì nó không có thuộc tính author
trong schema.
{
title: 'Better Post!',
slug: 'a-better-post',
published: true,
author: 'Ado Kukic',
content: 'This is an even better post',
tags: ['featured']
}
🥈Custom model trong MongoDB Driver Nodejs
Nhưng nếu thao tác với MongoDB Driver Nodejs thì lại bình thường, không hề có lỗi lầm nào ở đây
db.collection('posts').insertOne({
title: 'Better Post!',
slug: 'a-better-post',
published: true,
author: 'Ado Kukic',
content: 'This is an even better post',
tags: ['featured']
})
Cơ bản là do MongoDB NodeJS Driver không có khái niệm gì về Model cả, không có nghĩa là chúng ta không thể tạo các model để đại diện cho dữ liệu MongoDB của chúng ta ở mức ứng dụng. Chúng ta có thể dễ dàng tạo một model chung bằng contructor function của javascript.
Chúng ta có thể tạo một model Blog như sau:
function Blog(post) {
this.title = post.title;
this.slug = post.slug;
...
}
Chúng ta có thể sử dụng model này kết hợp với MongoDB Node.js driver, cung cấp cho chúng ta sự linh hoạt khi sử dụng model nhưng không bị ràng buộc bởi nó.
db.collection('posts')
.findOne({})
.then((err, post) => {
let article = new Blog(post)
})
Trong ví dụ này, cơ sở dữ liệu MongoDB của chúng ta vẫn hoàn toàn không nhận thức được về Blog
model ở tầng ứng dụng, nhưng ae dev có thể làm việc với nó, thêm các method và helper cho model và biết rằng model này chỉ được sử dụng trong phạm vi của ứng dụng Node.js của chúng ta.
🥇Thêm Schema Validation
Xác thực Schema là điều cần thiết khi làm việc với database, để đảm bảo dữ liệu chèn vào database luôn thống nhất và chính xác.
Có 2 cách khác nhau để xác thực schema, tương đương 2 tầng
- Tầng ứng dụng
- Tầng database
Tầng database là quan trọng nhất, vì đó là chức năng sẵn có của database, anh em có đổi thư viện, đổi ngôn ngữ khác đi chăng nữa thì nó vẫn áp dụng được.
Còn tầng ứng dụng thì nó chỉ áp dụng được cho code hiện tại của anh em thôi, nếu đổi sang ngôn ngữ khác thì lại không có được. Đơn giản vì MongoDB không biết tầng này.
Mongoose áp dụng Schema Validation ở tầng ứng dụng, anh em có thể nhìn thử cái schema dưới đây, thuộc tính title
có kiểu là String
và bắt buộc truyền vào.
const blog = new Schema({
title: {
type: String,
required: true,
},
slug: {
type: String,
required: true,
},
published: Boolean,
content: {
type: String,
required: true,
minlength: 250
},
...
});
const Blog = mongoose.model('Blog', blog);
MongoDB Nodejs Driver không có cơ chế validation, vì vậy chúng ta phải định nghĩa schema validation cho MongoDB bằng cách sử dụng MongoDB Shell hoặc Compass
💡 Mẹo:
MongoDB Schema Validation là một tính năng có sẵn của cơ sỡ dữ liệu MongoDB, cho phép dễ dàng tạo schema cho MongoDB nhưng vẫn giữ được một mức độ linh hoạt cao, mang lại lợi ích tốt nhất giữa tầng ứng dụng - tầng cơ sở dữ liệu
Chúng ta có thể tạo Schema Validation khi tạo collection hoặc sau khi tạo collection đều được. Bây giờ mình sẽ tạo Schema Validation bằng cách dùng Compass kết hợp với MongoDB Atlas.
💡 Mẹo:
Để tìm hiểu về Schema Validation, các bạn có thể đọc series này
Tạo một collection posts
và chèn 2 document này vào
[
{
"title": "Better Post!",
"slug": "a-better-post",
"published": true,
"author": "Ado Kukic",
"content": "This is an even better post",
"tags": ["featured"]
},
{
"_id": { "$oid": "5e714da7f3a665d9804e6506" },
"title": "Awesome Post",
"slug": "awesome-post",
"published": true,
"content": "This is an awesome post",
"tags": ["featured", "announcement"]
}
]
Trong UI phần mềm Compass, mình sẽ di chuyển đến tab Validation. Hiển nhiên là không có bất kỳ một validate rule nào ở đây cả, điều này nghĩa là dữ liệu nào chèn vào nó cũng cho.
Nhấn vào button Add a Rule để thêm rule cho nó. Cùng thêm require cho thuộc tính author
nhé. Nó sẽ trông như thế này
{
$jsonSchema: {
bsonType: "object",
required: [ "author" ]
}
}
Bây giờ chúng ta sẽ thấy bài post khởi tạo của chúng ta, cái nào không có trường author
sẽ fail validation, cái nào có author
thì sẽ qua được.
Chúng ta có thể thêm validation cho các trường khác bằng cách cập nhật lại schema validation như thế này
{
$jsonSchema: {
bsonType: "object",
required: [ "tags" ],
properties: {
title: {
bsonType: "string",
minLength: 20,
maxLength: 80
}
}
}
}
title
phải có độ dài từ 20 - 80 ký tự.
Có rất là nhiều rule cho chúng ta thêm vào. Để xem chi tiết, các bạn có thể click vào đây. Nâng cao hơn thì nên đọc những bài này để biết thêm về schema validation với array và depedencies.
Thêm nữa về Schema Validation trong MongoDB rất là linh động. Khi chúng ta thêm một schema, validation trên những document tồn tại trước đó sẽ không tự động thực hiện. Validation chỉ thực hiện ở những lệnh update hay insert. Nếu chúng ta muốn để nguyên các document hiện có, chúng ta có thể thay đổi validationLevel
để chỉ có tác dụng với những document mới được thêm vào database.
Khi fail validation, nó sẽ cho ra một error
, nếu chỉ muốn warn
thôi thì đơn giản chỉ cần thay đổi validationAction
là được.
Và cuối cùng nếu cần, chúng ta có thể bypass document validation bằng cách thêm option bypassDocumentValidation
db.collection('posts').insertOne({ title: 'Awesome' }, { bypassDocumentValidation: true })
🥇Populate và Lookup
Cái cuối cùng mà mình muốn so sánh giữa Mongoose và MongoDB NodeJS Driver là hỗ trợ pseudo-joins. Cả 2 thằng đều hỗ trợ điều này, chúng ta có thể kết hợp nhiều document từ nhiều collection trong một database lại với nhau, điều này tương tự như cách chúng ta join trong cơ sở dữ liệu quan hệ.
Cách tiếp cận của Mongoose gọi là Populate. Nó cho phép chúng ta tạo các model mà tham chiếu lẫn nhau và từ đó có thể kết hợp để cho ra kết quả tương ứng.
Ví dụ dưới đây, user
trong comment
của blog
là một _id
, nó sẽ tham chiếu đến collection là User
.
const user = new Schema({
name: String,
email: String
});
const blog = new Schema({
title: String,
slug: String,
published: Boolean,
content: String,
tags: [String],
comments: [{
user: { Schema.Types.ObjectId, ref: 'User' },
content: String,
votes: Number
}]
});
const User = mongoose.model('User', user);
const Blog = mongoose.model('Blog', blog);
comments
nó sẽ có dạng như dưới đây
comments: [{ user: '12345', content: 'Great Post!!!' }]
12345
là _id
của một document User
nào đó. Khi chúng ta đọc một document trong collection Blog
thì chúng ta sẽ không thấy được thông tin như user.name
hay user.email
trong comments
. Để thực hiện được điều này thì chúng ta cần phải Populate.
Blog.findOne({})
.populate('comments.user')
.exec(function (err, post) {
console.log(post.comments[0].user.name) // Tên của user tại comment thứ nhất
})
Populate là một tính năng rất hay của Mongoose, nhưng bạn sẽ chẳng biết nó thực hiện điều gì ở bên dưới. Nếu bạn cần những câu lệnh query phức tạp thì có thể nó sẽ không tối ưu bằng việc bạn dùng MongoDB Driver.
Một vấn đề khác của việc này là nó chỉ hoạt động ở tầng ứng dụng, nếu dựa dẫm vào nó, bạn sẽ không biết tự viết các câu lệnh nâng cao, và tương lai sẽ cắn bạn một cái thật đau.
Trong khí đó MongoDB từ version 3.2 trở lên hỗ trợ $lookup
cho phép ae dev join các collection trong một database dễ dàng. Với ví dụ trên chúng ta có thể làm như sau
db.collection('posts').aggregate(
[
{
$lookup: {
from: 'users',
localField: 'comments.user',
foreignField: '_id',
as: 'users'
}
},
{}
],
(err, post) => {
console.log(post.users) // mảng users
}
)
Với cách viết thuần túy như thế này, chúng ta có thể tùy biến rất sâu, tối ưu được performance và phát huy được hết sức mạnh của MongoDB.
Các bạn có thể tìm hiểu thêm về aggregation tại đây
🥇Cuối cùng thì chúng ta có thực sự cần Mongoose?
Mình sẽ tóm tắt lại bài viết này như sau
Ưu điểm và nhược điểm khi dùng Mongoose
✅ Ưu điểm:
- Có schema và model tường minh
- Có sẵn schema validation ở tầng ứng dụng giúp tăng cường thêm tính nhất quán cho database
- Cung cấp các hàm API để thao tác với MongoDB một cách dễ dàng
❌ Nhược điểm:
- Tốn thời gian học thêm thư viện
- Bị giới hạn bởi thư viện, tính tùy biến không cao bằng việc sử dụng MongoDB Driver dẫn đến hiệu suất sẽ chậm hơn khi thực hiện các câu lệnh phức tạp
- Việc tương tác với MongoDB qua Mongoose có thể tạo ra các lỗi hoặc xung đột bởi vì các hàm API được định nghĩa bởi Mongoose, vì nó không được phát hành chính thức bởi MongoDB.
Ưu điểm và nhược điểm khi dùng MongoDB NodeJS Driver
✅ Ưu điểm:
- Hàng chính chủ của cơ sở dữ liệu MongoDB, document đầy đủ và chi tiết, nếu gặp lỗi thì có thể dễ dàng tìm hướng giải quyết hơn là khi dùng Mongoose
- Không cần học thêm thư viện ODM ngoài
- Viết câu lệnh có thể dùng được ở nhiều môi trường khác nhau như MongoDB Shell (thực ra thì có một số câu lệnh hơi khác 1 tí nhưng đa số là giống)
- Tính tùy biến cao
- Sử dụng được mọi chức năng sẵn có của cơ sở dữ liệu MongoDB mà không bị giới hạn nào
❌ Nhược điểm:
- Không có Schema Validation ở tầng ứng dụng, cá nhân mình thì không cần cái này lắm, vì mình có validate đầu vào bằng express-validator rồi. Cái quan trọng là Schema Validation ở tầng database thôi.
- Câu lệnh thao tác với database sẽ dài hơn 1 tý so với Mongoose
- Bạn phải tự setup nhiều thứ nếu muốn nó chặt chẽ và có quy cũ (cái này thì không thành vấn đề đâu, dễ ồm à)
Cá nhân mình thì không cần một ODM như Mongoose, mình thích dùng MongoDB Driver hơn. Điều này cho phép mình tận dụng được hết sức mạnh của database.
Và mình nghĩ các bạn mới học MongoDB thì nên dùng MongoDB Driver để hiểu rõ hơn về cơ sở dữ liệu này, thay vì dùng Mongoose để tạo ra các model, schema, ... mà không biết nó thực hiện điều gì ở bên dưới.
Các bạn có thể đọc thêm suy nghĩ của các lập trình viên về Mongoose hay MongoDB Driver tại đây: https://stackoverflow.com/questions/18531696/why-should-we-use-mongoose-odm-instead-of-using-directly-mongodb-with-mongodb-dr
Còn các bạn thì sao? Các bạn có thích dùng ODM như Mongoose không? Hãy comment dưới bài viết để cho mình biết nhé!
Tham khảo:
Bài viết có tham khảo từ MongoDB & Mongoose: Compatibility and Comparison
Kiến thức trong khóa học Next.js này đã giúp mình kiếm hơn 1 tỉ đồng/năm
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ể 😅
Website bạn đang đọc được viết bằng Next.js TypeScript và tối ưu từng chi tiết nhỏ như SEO, hiệu suất, nội dung để đảm bảo bạn có trải nghiệm tốt nhất.
Với lượt view trung bình là 30k/tháng (dù website rất ít bài viết). Website này đem lại doanh thu 1 năm vừa qua là hơn 1 tỉ đồng
Đó chính là sức mạnh của SEO, sức mạnh của Next.js.
Mình luôn tin rằng kiến thức là chìa khóa giúp chúng ta đi nhanh nhất.
Mình đã dành hơn 6 tháng để phát triển khóa học Next.js Super | Dự án quản lý quán ăn & gọi món bằng QR Code. Trong khóa này các bạn sẽ được học mọi thứ về framework Next.js, 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, 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. 💪🏻