-
Notifications
You must be signed in to change notification settings - Fork 79
/
13s-advanced-publications.md.erb
190 lines (129 loc) · 9.35 KB
/
13s-advanced-publications.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
---
title: 高级发布机制
slug: advanced-publications
date: 0013/01/02
number: 13.5
points: 10
sidebar: true
photoUrl: http://www.flickr.com/photos/ikewinski/8390558986/
photoAuthor: Mike Lewinski
contents: 学习更多处理发布机制的高级方法。|了解发布/订阅模型可以得到怎样的弹性机制。
paragraphs: 36
---
目前你应该对发布和订阅交互模式有一个不错的掌握了。因此,我们废话少说,来看几个更高级的情景。
### 多次发布一个集合
在[我们第一个关于发布的附录中](/chapter/publications-and-subscriptions/),我们看到了一些更普遍的发布和订阅模式,同时我们学习了 `_publishCursor` 函数,如何让它们非常容易地实现在我们的站点上。
首先,让我们回忆 `_publishCursor` 到底为我们做了什么:它将整理所有的文档以匹配一个给定的游标(cursor),并将它们推送至*同名的*客户端集合中。注意这与 _publication_ 的名字是不关联的。
这意味着我们可以用_不止一个 publicaton_ 去连接任何集合的客户端与服务端版本。
我们已经在[分页章节](/chapter/pagination/)用过这个模式,当我们在当前显示的帖子之外,再发布一个所有帖子的分页后的子集。
另一个相似的用例是发布一大组文档的*预览*,和单个文档的全部信息:
<%= diagram "doublecollection", "两次发布一个集合", "pull-center" %>
~~~js
Meteor.publish('allPosts', function() {
return Posts.find({}, {fields: {title: true, author: true}});
});
Meteor.publish('postDetail', function(postId) {
return Posts.find(postId);
});
~~~
现在客户端订阅这两个发布,这 `'posts'` 集合来自于两个源渠道:来自第一个订阅的标题和作者姓名列表,和来自第二个订阅的单个帖子全部信息。
你也许意识到了 `postDetail` 发布的帖子也被 `allPosts` 发布了(尽管只有它的部分属性)。但是,Meteor 会合并字段及确认没有重复的帖子,来处理数据重叠的问题。
这是很棒的,因为现在当我们呈现帖子摘要列表时,我们正在处理的数据对象正好拥有我们需要显示的足够数据。但是,当我们呈现单个帖子时,我们有一切需要展示的数据。当然,在这种情况下,我们需要让客户端不要去期待所有帖子的所有字段都能都显示出来————这是一个常见的问题!
注意你并没有改变文档属性的任何限制。你可以很好地在这两个发布中发布同样的属性,但是先后排序不同。
~~~js
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});
});
~~~
<%= caption "server/publications.js" %>
### 多次订阅一个发布
我们已经看了如何多次发布同一个集合。事实证明你可以通过另一个模式来完成非常相近的结果:建立一个单一发布,却多次*订阅*它。
在 Microscope 中,我们多次重复订阅 `posts` 发布,但 Iron Router 为我们设置并拆开每次的订阅。然而,没有理由我们不能*同时进行*多次订阅。
举个例子,我们想要将最新的和最好的帖子同时载入内存:
<%= diagram "subscribetwice", "对一个发布订阅两次", "pull-center" %>
我们设定一个单一发布:
~~~js
Meteor.publish('posts', function(options) {
return Posts.find({}, options);
});
~~~
并且我们多次订阅这个发布。事实上或多或少我们在 Microscope 里这样做了:
~~~js
Meteor.subscribe('posts', {submitted: -1, limit: 10});
Meteor.subscribe('posts', {baseScore: -1, submitted: -1, limit: 10});
~~~
接下来到底发生什么了?每个浏览器开启了*两个*不同的订阅,每个订阅连接到*同个*服务端的发布。
每个订阅提供了不同的发布参数,但从根本上,每次一个(不同)文档子集从 `posts` 集合提取出来,并通过连接机制发送到客户端集合。
你甚至可以用*同样的参数*订阅两次相同的发布。这个对很多处场景来说很难说有用,但这种弹性机制总有一天会有用的。
### 单一订阅中的多个集合
不像传统关系型数据库像 MySQL 使用 *joins*,NoSQL 数据库类似 Mongo 都是关于*去规范化*和*嵌入*。让我们看看它们是怎样在 Meteor 环境下工作的。
让我们看一个具体的例子。我们已经对我们的帖子添加了评论,到目前为止,我们一直很愉快地只发布用户看的单个帖子的评论。
但是,假设我们希望在首页中显示*全部*帖子的回复(记着这些帖子会在分页时被改变)。这个用例展示了一个很好的理由把评论嵌入帖子中,事实上这是促使我们来非规范化评论*数量*。
当然我们可以总是嵌入评论到帖子中,并完全摒除 `Comments` 集合。但如同我们前面在*去规范化*章节看到的,我们将在分离的集合的操作中也会失去一些额外的好处。
但是事实证明有一个涉及订阅的技巧,在保持分离集合的同时再嵌入我们的评论。
让我们假定除了首页帖子列表之外,我们希望再订阅每个帖子的两个最新评论。
使用独立的评论发布会很难完成这个要求,尤其在帖子列表受到某些限制时(比如说,最近的10个)。我们必须写一个发布,看起来像下面的代码:
<%= diagram "multiplecollections", "一个订阅中的两个集合", "pull-center" %>
~~~js
Meteor.publish('topComments', function(topPostIds) {
return Comments.find({postId: topPostIds});
});
~~~
从性能角度来看这是个问题,因为这个发布将需要每次在 `topPostIds` 改变时消除及重新建立。
有一个途径来解决这个问题。我们可以应用这个事实:就是我们不仅可以在每个*集合*上拥有多次*发布*,而且我们也可以在每个*发布*上拥有多个*集合*。
~~~js
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(); });
});
~~~
注意我们在这个发布中没有返回任何东西,因为我们自己手动给 `sub` 发送信息(通过 `.added()` 等方式)。所以我们不必通过返回一个游标来请求 `_publishCursor` 给我们来做这个动作。
现在,每次我们发布一个帖子时,我们也自动发布其2个最新评论。而且所有都在一个订阅调用中!
虽然 Meteor 还未直接实现这个方法,但是你也可以参考在 Atomsphere 里的 `publish-with-relations` 包,它的目标就是让这个模式更容易使用。
### 连接不同的集合
这样的订阅弹性机制还能给我们更多新知识么?当然,如果我们不使用 `_publishCursor`,我们不必跟着此项约束,就是在服务端的源集合需要与客户端的目标集合有同样的名称。
<%= diagram "linkedcollections", "一个集合对两个订阅", "pull-center" %>
为什么我们想这么做的一个原因就是*单表继承*。
假设我们需要从我们的帖子中引用多种类型的对象,每一个对象存储在相同字段中但又显然是不同内容。例如,我们建立一个类似 Tumblr 的博客引擎,每个帖子具有常见的 ID、时间戳,以及标题;但是额外也有如图像、视频、链接,或者只是文字。
我们可以将这些对象存储在一个单独的 `'resources'` 集合中,使用 `type` 属性来标记他们是什么类型的对象(`video`、`image`、`link` 等等)。
同时,虽然我们有一个服务端的单一的 `Resources` 集合,我们也能够将单一集合转换到多个的 `Videos`、`Images',等等集合中。
客户端的集合如下的代码:
~~~js
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();
});
~~~
我们告诉 `_publishCursor` (就像返回)游标会做的一样发布我们的视频,但不是将 `resources` 集合发布到客户端,而是我们从 `resources` 发布到 `videos`。
另一个类似的主意是:发布到客户端的集合,却根本*没有服务端集合*!举例,你也许从一个第三方服务中抓取数据,并发布它们到一个客户端集合。
由于发布 API 的灵活性,可能性是无限的。