Publish nâng cao

Sidebar 13.5

Percentage Translated

In this chapter, you will:

  • Học thêm một số kiểu mẫu nâng cao cho việc điều khiển publish.
  • Thấy được publish và subscribe có thể mềm dẻo như thế nào.
  • Cho đến bây giờ chắc hẳn bạn đã nắm vững được cách thức publish và subscribe tương tác với nhau. Vì vậy hãy cùng giải thoát khỏi phần huấn luyện cơ bản và bước vào vài tình huống nâng cao hơn.

    Publish một collection nhiều lần

    Trong chương sidebar đầu tiên của chúng ta về publish, chúng ta đã thấy một vài khuôn mẫu publish và subscribe cơ bản, và chúng ta đã học làm thế nào hàm _publishCursor có thể dễ dàng cài đặt cho ứng dụng của chúng ta.

    Đầu tiên, hãy cùng gợi nhớ lại chính xác _publishCursor là cái gì: nó lấy tất cả tài liệu mà khớp với một con trỏ, và đẩy chúng tới collection có cùng tên ở phía client. Chú ý rằng tên của publication không bị bao gồm.

    Điều này có nghĩa là chúng ta có thể có nhiều hơn một publication liên kết bản collection giữa client và server.

    Chúng ta đã luôn gặp mô hình này trong chương về phân trang, khi mà chúng ta đã publish một tập con phân trang của tất cả bài viết thêm vào bài viết đang được hiển thị.

    Một trường hợp khác được dùng là để publish mô tả khái quáthay của một lượng dữ liệu lớn, hay là toàn bộ chi tiết của một mục dữ liệu đơn lẻ:

    Publishing a collection twice
    Publishing a collection twice
    Meteor.publish('allPosts', function() {
      return Posts.find({}, {fields: {title: true, author: true}});
    });
    
    Meteor.publish('postDetail', function(postId) {
      return Posts.find(postId);
    });
    

    Bây giờ khi mà client subscribe tới những publish này, collection 'posts' của nó nhận dữ liệu sinh ra từ hai nguồn: danh sách tiêu đề và tên tác giả từ subscription thứ nhất, và toàn bộ chi tiết của bài viết từ cái thứ hai.

    Bạn có thể nhận ra rằng bài viết được publish từ postDetail cũng được publish bởi allPosts (mặc dù với chỉ một tập con thuộc tính). Tuy nhiên, Meteor đảm nhận việc phủ lên nhau này bằng cách hợp nhất các trường và chắc chắn rằng không có bài viết bị trùng lặp.

    Điều này rất tuyệt, bởi vì bây giờ khi mà chúng ta dịch ra danh sách tóm tắt của bài viết, chúng ta đang dùng object mà chỉ có đủ dữ liệu để làm việc cần thiết. Tuy nhiên, khi chúng ta đưa ra trang bài viết đơn lẻ, chúng ta cần phải làm mọi thứ để hiển thị nó. Dĩ nhiên, chúng ta cần phải chú ý để client không kỳ vọng tất cả các trường hữu dụng đối với tất cả bài viết trong trường hợp này – đây là một điều cơ bản!

    Cần phải ghi chú rằng bạn không bị hạn chế việc thay đổi thuộc tính của tài liệu. Bạn có thể publish cùng thuộc tính ở cả hai publication, nhưng sắp xếp các mục một cách khác đi.

    Meteor.publish('newPosts', function(limit) {
      return Posts.find({}, {sort: {submitted: -1}, limit: limit});
    });
    
    Meteor.publish('bestPosts', function(limit) {
      return Posts.find({}, {sort: {votes: -1, submitted: -1}, limit: limit});
    });
    
    server/publications.js

    Subscribe tới Publication nhiều lần

    Chúng ta vừa thấy làm thế nào để publish một collection đơn lẻ nhiều hơn một lần. Điều đó dẫn đến bạn có thể hoàn thành một kết quả tương tự với một mô hình khác: tạo một publication đơn, nhưng subscribe tới nó nhiều lần.

    Trong Microscope, chúng ta subscribe tới publication posts nhiều lần, nhưng Iron Router cài đặt và tháo dỡ mỗi subscription cho chúng ta. Do đó không có lý do gì chúng ta không thể subscribe nhiều lần một cách đồng thời.

    Ví dụ, giả sử rằng chúng ta muốn nạp cả những bài viết mới nhất và tốt nhất vào bộ nhớ cùng một lúc:

    Subscribing twice to one publication
    Subscribing twice to one publication

    Chúng ta vừa thiết lập một publication đơn lẻ:

    Meteor.publish('posts', function(options) {
      return Posts.find({}, options);
    });
    

    Và sau đó chúng ta subscribe tới publication này nhiều lần. Thực tế là những gì chúng ta đang làm tương tự như với Microscope:

    Meteor.subscribe('posts', {submitted: -1, limit: 10});
    Meteor.subscribe('posts', {baseScore: -1, submitted: -1, limit: 10});
    

    Điều gì đang thực sự xảy ra ở đây? Mỗi trình duyệt mở ra hai subscription khác nhau, mỗi subscription kết nối tới cùng publication ở phía server.

    Mỗi subscription cung cấp tham số khác nhau cho publication, nhưng về cơ bản, mỗi lần một bộ tài liệu (khác nhau) được dùng kéo từ collection posts và gửi xuống cho collection phía client.

    Bạn còn có thể subscribe tới cùng một publication hai lần với cùng bộ tham số! Khá là khó để nghĩ về một tình huống mà nó hữu ích bây giờ, nhưng sự mềm dẻo này có thể sẽ dùng một ngày nào đó!

    Nhiều collection trong một subscription đơn

    Không giống như hệ cơ sở dữ liệu quan hệ truyền thống như MySQL là thứ dùng joins, cơ sở dữ liệu NoSQL như Mongo có bất chuẩn hoákết cấu lồng. Hãy cùng xem những từ ngữ này trong bối cảnh của Meteor.

    Hãy cùng nhìn vào một ví dụ cụ thể. Chúng ta thêm bình luận đối với bài viết, và đến giờ, chúng ta hoàn toàn hạnh phúc với việc chỉ publish bình luận trên bài viết đơn mà người dùng đang nhìn vào.

    Tuy nhiên, giả sử chúng ta muốn chỉ ra bình luận trên tất cả bài viết trên một trang đầu (chú ý rằng những bài viết này sẽ thay đổi khi chúng ta phân trang chúng). Trường hợp này đưa ra một ví dụ tốt về việc nhúng bình luận vào trong bài viết, và thực tế là chúng ta đã đẩy counts số lượng bình luận bất chuẩn hoá.

    Dĩ nhiên chúng ta có thể luôn luôn nhúng bình luận vào trong bài viết, bỏ qua luôn collection Comments. Nhưng như chúng ta đã thấy trước đó trong chương Bất chuẩn hoá, bằng việc làm như vậy chúng ta sẽ mất một số lợi ích có được từ làm việc với collection riêng lẻ.

    Nhưng cũng có một số thủ thuật tham gia vào việc subscription làm cho việc nhúng bình luận vào khả thi, trong khi vẫn giữ được collection riêng biệt.

    Hãy cùng tưởng tượng rằng ở trang chủ danh sách bài viết, chúng ta muốn subscribe tới danh sách của 2 bình luận mới nhất cho mỗi bài viết.

    Sẽ trở nên khó khăn để hoàn thành điều này mà không dùng publication độc lập cho bình luận, đặc biệt là nếu danh sách bài viết bị giới hạn theo một cách nào đó (ví dụ 10 bài gần nhất). Chúng ta sẽ phải viết publication trông như sau:

    Two collections in one subscription
    Two collections in one subscription
    Meteor.publish('topComments', function(topPostIds) {
      return Comments.find({postId: topPostIds});
    });
    

    Điều này sẽ trở thành vấn đề nếu xét về mặt hiệu năng. Vì publication cần phải chạy nhanh xuống và xuất bản lại mỗi lần danh sách topPostIds thay đổi.

    Có một cách cho điều này. Chúng ta có thể lợi dụng một điều là chúng ta không những có thể có nhiều hơn một publication cho mỗi collection, mà còn có thể có nhiều collection cho một publication:

    Meteor.publish('topPosts', function(limit) {
      var sub = this, commentHandles = [], postHandle = null;
    
      // send over the top two comments attached to a single post
      function publishPostComments(postId) {
        var commentsCursor = Comments.find({postId: postId}, {limit: 2});
        commentHandles[postId] = 
          Mongo.Collection._publishCursor(commentsCursor, sub, 'comments');
      }
    
      postHandle = Posts.find({}, {limit: limit}).observeChanges({
        added: function(id, post) {
          publishPostComments(id);
          sub.added('posts', id, post);
        },
        changed: function(id, fields) {
          sub.changed('posts', id, fields);
        },
        removed: function(id) {
          // stop observing changes on the post's comments
          commentHandles[id] && commentHandles[id].stop();
          // delete the post
          sub.removed('posts', id);
        }
      });
    
      sub.ready();
    
      // make sure we clean everything up (note `_publishCursor`
      //   does this for us with the comment observers)
      sub.onStop(function() { postHandle.stop(); });
    });
    

    Chú ý rằng chúng ta không trả về bất kỳ thứ gì trong publication này, vì chúng ta gửi thông tin bằng tay tới sub (thông qua .added() và những hàm tương tự). Bởi vậy chúng ta không cần phải hỏi _publishCursor để làm điều đó cho chúng ta bằng việc trả về con trỏ.

    Bây giờ, mỗi khi publish một bài viết chúng ta cũng đồng thời publish hai bình luận mới nhất gắn với nó. Và tất cả ở trong cùng một lệnh gọi subscription!

    Mặc dù Meteor không tạo ra điều này một cách trực tiếp, bạn có thể nhìn vào gói publish-with-relations trên Atmosphere, là thứ mục đích làmthì cho mô hình này dễ sử dụng.

    Liên kết collection khác nhau

    Ngoài ra thì kiến thức mới mẻ về sự mềm dẻo của việc subscription còn giúp chúng ta làm được thêm gì nữa? Thực sự thì, nếu chúng ta không dùng _publishCursor, chúng ta không cần tuân thủ sự ràng buộc là collection nguồn ở phía server cần có cùng tên như collection đích ở phía client.

    One collection for two subscriptions
    One collection for two subscriptions

    Một trong những lý do chúng ta muốn làm điều này là do tính Thừa kế Bảng Đơn

    Giả sử rằng chúng ta muốn tham khảo nhiều loại object từ bài viết của chúng ta, mỗi trong số chúng đều lưu những trường chung nhưng có một chút khác biệt về nội dung. Ví dụ, chúng ta có thể xây dựng một blog engine như kiểu Tumblr mà mỗi bài viết xử lý ID, tem thời gian, và tựa đề; nhưng thêm vào đó còn có thể có cả ảnh, video, đường dẫn hoặc chỉ văn bản.

    Chúng ta có thể lưu trữ tất cả object này trong một collection 'resources', sử dụng thuộc tính type để chỉ ra đó là kiểu object nào (video, image, link, vân vân).

    Và mặc dù chúng ta có một collection Resources phía server, chúng ta còn có thể biến đổi collection đơn đó thành đa collection phía client Videos, Images, vân vân… với chỉ một chút thay đổi:

      Meteor.publish('videos', function() {
        var sub = this;
    
        var videosCursor = Resources.find({type: 'video'});
        Mongo.Collection._publishCursor(videosCursor, sub, 'videos');
    
        // _publishCursor doesn't call this for us in case we do this more than once.
        sub.ready();
      });
    

    Chúng ta đang bảo _publishCursor publish (giống như là trả về) con trỏ video sẽ làm, nhưng thay vì publish tới collection resources phía client, chúng ta đang publish từ 'resources' sang 'videos'.

    Một ý tưởng khác nữa là dùng publish tới collection phía client nơi mà không có collection nào từ phía server! Ví dụ, bạn có thể túm dữ liệu từ bên dịch vụ thứ 3, và publish chúng vào collection bên phía client.

    Cảm ơn nhiều tới sự mềm dẻo của API publish, khả năng đã trở thành không có giới hạn.