Bạn có chắc chắn muốn xóa bài viết này không ?
Bạn có chắc chắn muốn xóa bình luận này không ?
Meteor: Sử dụng Publish & Subscribe với hàm tạo Query
Có một thứ mà hầu như mọi lập trình viên đều không thích, đó là lặp lại code. Nếu như phải kể đến hai thứ mà lập trình viên không thích, thì đó là lặp lại code và code không bảo mật.
Chúng ta sẽ cùng tìm hiểu một số pattern giúp giải quyết hai vấn đề trên.
Một hàm publish đơn giản
Để tìm hiểu về pattern này, đầu tiên hãy cùng xem xét một ví dụ đơn giản về hàm publish trả về 10 bài post mới nhất:
// server
Meteor.publish('posts', function() {
return Posts.find({}, {sort: {createdAt: -1}, limit: 10});
});
Ở phía client, chúng ta sẽ subscribe (bên trong router hoặc template):
// client
Meteor.subscribe('posts');
Và cuối cùng, query bài viết với:
// client
var latestPosts = Posts.find();
Mặc dù đoạn code phía trên hoạt động đúng cho trường hợp của chúng ta, nhưng nếu vì một lý do nào đó, chúng ta đã subscribe nhiều hơn là 10 bài post, thì lệnh find()
sẽ không còn chắc chắn sẽ trả về đúng 10 bài post mới nhất. Chính vì vậy mà chúng ta sẽ phải thêm điều kiện query cho cả phía client:
// client
var latestPosts = Posts.find({}, {sort: {createdAt: -1}, limit: 10});
Tới đây, có lẽ bạn đã nhận ra chúng ta đã bị lặp lại code: câu query bên trong hàm Posts.find()
bây giờ bị lặp lại cả client và hàm publish. Hãy cùng thử khắc phục vấn đề này!
Giải pháp sai
Chúng ta có thể giải quyết bằng cách chỉ định nghĩa query cho hàm Posts.find()
ở phía client, sau đó truyền tham số lên server. Implement sẽ như sau:
// server
Meteor.publish('posts', function(parameters) {
return Posts.find(parameters.find, parameters.options);
});
Ở phía client:
// client
var parameters = {
find: {},
options: {sort: {createdAt: -1}, limit: 10}
};
Meteor.subscribe('posts', parameters);
var latestPosts = Posts.find(parameters.find, parameters.options);
Giải pháp này không tốt vì chúng ta đã tạo ra một lỗ hổng bảo mật cực kỳ lớn. Trong khi chúng ta có thể chắc chắn rằng ứng dụng của mình chỉ truyền tham số sạch, gửi lên đúng parameters.find
và parameters.options
, chúng ta không thể đảm bảo cho mọi client nói chung.
Không có gì ngăn cản một người dùng nào đó dùng console trình duyệt và subscribe với một bản dữ liệu parameters.find
riêng, điều đó sẽ làm cho toàn bộ dự liệu của collection bị lộ ra, dù bạn có muốn hay không!
Dùng tham số tập trung
Chúng ta không thể tin tưởng vào client, vậy thay vào đó tại sao không thử tạo một object trung tâm, share giữa client và server, để giữ tham số Mongo? Nó sẽ giống như đoạn code sau:
// client & server
latestPost = {
find: {},
options: {sort: {createdAt: -1}, limit: 10}
}
// server
Meteor.publish('posts', function() {
return Posts.find(latestPost.find, latestPost.options);
});
// client
Meteor.subscribe('posts');
var latestPosts = Posts.find(latestPost.find, latestPost.options);
Cách này cũng có vẻ ổn, nhưng chưa thực sự mềm dẻo vì chúng ta đã phải hard-code cho object. Ví dụ, điều gì sẽ xảy ra nếu chúng ta muốn client có thể tự quyết định giá trị limit
cho subscribe thành 10, 20 hoặc 30 bài viết?
Hàm tạo Query
Vậy hãy cùng thử thêm một lần nữa, nhưng lần này chúng ta sẽ dùng hàm thay cho object:
// client & server
latestPost = function (limit) {
return {
find: {},
options: {sort: {createdAt: -1}, limit: limit}
};
}
// server
Meteor.publish('posts', function(limit) {
return Posts.find(latestPost(limit).find, latestPost(limit).options);
});
// client
var limit = 20;
Meteor.subscribe('posts', limit);
var latestPosts = Posts.find(latestPost(limit).find, latestPost(limit).options);
Về bản chất, hàm này lấy yêu cầu cho tham số để query ("hãy cho tôi X bài viết mới nhất") và tạo ra thứ gì đó mà MongoDB có thể hiểu được ({}, {sort: {createdAt: -1}, limit: X}
). Bởi vậy chúng ta sẽ gọi nó là hàm tạo query (Query Constructor).
Hàm tạo query của chúng ta còn có thể làm được nhiều hơn thế: ví dụ, có thể bạn muốn một giới hạn cho chính tham số limit
là 100 chẳng hạn, để ngăn cản người dùng có thể query 10 triệu bản ghi một lúc, tránh quá tải cho server. Với pattern tạo query như trên, chúng ta chỉ phải giới hạn một chỗ duy nhất:
// client & server
latestPost = function (limit) {
if (limit > 100) {
limit = 100;
}
return {
find: {},
sort: {sort: {createdAt: -1}, limit: limit}
};
}
Query View
Cho tới bây giờ, chúng ta mới chỉ xét đến một cách đơn để lọc dữ liệu (lấy bản ghi mới nhất). Tuy nhiên, trên thực tế, bạn sẽ muốn nhiều hơn một cách để truy vấn dữ liệu: sắp xếp theo thời gian tạo dữ liệu, thời gian sửa đổi gần nhất, độ phổ biến của bài viết... Hãy cùng gọi mỗi cách như vậy là một view
(khái niệm này không liên quan đến phần View trong cấu trúc MVC).
Đầu tiên, mỗi view
sẽ được định nghĩa bởi hàm riêng:
views = {};
// client & server
views.latestPosts = function (terms) {
return {
find: {},
sort: {sort: {createdAt: -1}, limit: terms.limit}
};
}
views.mostPopularPosts = function (terms) {
return {
find: {},
sort: {sort: {score: -1}, limit: terms.limit}
};
}
Để cho code thêm mềm dẻo, chúng ta sẽ truyền vào object là terms
thay vì chỉ truyền vào limit
. Điều này sẽ dễ dàng để truyền vào nhiều option trong tương lai, ví dụ như là terms.category
, terms.date
...
Thêm vào đó, hãy chú ý cách chúng ta đã dùng để loại bỏ limit. Chúng ta sẽ thêm vào hàm tạo query, một cách tổng quát hơn cho tất cả các view
:
// client & server
queryConstructor = function (terms) {
var viewFunction = views[terms.viewName]
var parameters = viewFunction(terms);
if (parameters.limit > 100) {
parameters.limit = 100;
}
return parameters;
}
Cuối cùng, đây sẽ là cách chúng ta sử dụng hàm tạo query:
// server
Meteor.publish('posts', function(terms) {
var parameters = queryConstructor(terms);
return Posts.find(parameters.find, parameters.options);
});
// client
var terms = {
viewName: Session.get('view'),
limit: 20
}
Meteor.subscribe(terms);
var parameters = queryConstructor(terms);
var latestPosts = Posts.find(parameters.find, parameters.options);
Thực hành
Một khi bạn đã quen với pattern view/hàm tạo
, việc thêm view
sẽ cực kỳ đơn giản. Ví dụ, sau đây là cách mà Telescope thiết lập một số view
:
/**
* New view
*/
Posts.views.add("new", function (terms) {
return {
options: {sort: {sticky: -1, postedAt: -1}}
};
});
/**
* Best view
*/
Posts.views.add("best", function (terms) {
return {
options: {sort: {sticky: -1, baseScore: -1}}
};
});
/**
* Pending view
*/
Posts.views.add("pending", function (terms) {
return {
find: {
status: Posts.config.STATUS_PENDING
},
options: {sort: {createdAt: -1}},
showFuture: true
};
});
Chúng ta sẽ không đi sâu vào hàm tạo query của Telescope, tuy nhiên sau đây là một số task mà nó xử lý:
- Định nghĩa một số option
find/sort
mặc định dùng trong trường hợpview
hiện tại không cung cấp. - Thêm vào query
sort
bởi_id
- Giới hạn số lượng bài viết
- Giấu bài viết đã được lập lịch công khai trong tương lai
Kết luận
Đây là một patter đơn giản, nhưng rất hữu ích cho việc xử lý những vấn đề cơ bản chung trong Meteor khi publish và subscribe.
Nguồn: Managing Publications & Subscriptions With Query Constructors







