-
Notifications
You must be signed in to change notification settings - Fork 6
/
13s-advanced-publications.md.erb
187 lines (125 loc) · 21.3 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
---
title: Publications ขั้นสูง
slug: advanced-publications
date: 0013/01/02
number: 13.5
sidebar: true
contents: เรียนรู้รูปแบบวิธีจัดการเผยแพร่ข้อมูลแบบก้าวหน้า|เข้าใจความยืดหยุ่นของการเผยแพร่และบอกรับข้อมูล
paragraphs: 36
---
ถึงตอนนี้คุณน่าจะมีความเข้าใจการทำงานระหว่างการเผยแพร่และการบอกรับข้อมูลเป็นอย่างดีแล้ว ดังนั้นเราจะไม่มาฝึกฝนอะไรอีก แต่จะมาทำความเข้าใจวิธีการที่ก้าวหน้ามากขึ้นซักสองสามกรณี
### เผยแพร่คอลเลกชั่นหลายครั้ง
ใน [บทแทรกที่เกี่ยวกับการเผยแพร่ข้อมูล](/chapter/publications-and-subscriptions/) เราได้เห็นรูปแบบทั่วไปของการเผยแพร่และบอกรับข้อมูลกันมาแล้ว และเราก็รู้ว่าฟังก์ชัน `_publishCursor` ช่วยให้เราใช้งานมันได้ง่ายแค่ไหนกับไซต์ของเรา
ก่อนอื่นเรามาทบทวนว่า `_publishCursor` ทำอะไรให้เรา สิ่งที่มันทำก็คือ หาเอกสารทั้งหมดที่ตรงกับเคอร์เซอร์ที่ขอมา และส่งพวกมันกลับมาให้คอลเลกชั่นที่ฝั่งไคลเอนต์ *ด้วยชื่อเดียวกัน* สังเกตุว่า ชื่อของ _การเผยแพร่_ ไม่ได้ถูกนำมาใช้
นั่นก็หมายความว่า เราสามารถมีได้ _มากกว่าหนึ่งการเผยแพร่_ ที่เชื่อมระหว่างไคลเอนต์ กับ คอลเลกชั่นเวอร์ชันต่างๆของเซฺิร์ฟเวอร์
เรารู้จักรูปแบบนี้กันมาแล้วใน [บทการแบ่งหน้า](/chapter/pagination/) เมื่อเราเผยแพร่หน้าย่อยๆ ของข่าวทั้งหมดเพิ่มเติมจากข่าวที่กำลังแสดงอยู่
อีกกรณีที่มีการใช้งานคล้ายๆกัน คือ การเผยแพร่ *ภาพรวม* ของชุดเอกสารขนาดใหญ่ ควบคู่กับรายละเอียดทั้งหมดของเอกสารตัวเดียว
<%= diagram "doublecollection", "Publishing a collection twice", "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", "Subscribing twice to one publication", "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` และส่งกลับมาที่คอลเลกชั่นฝั่งไคลเอนต์
นอกจากนี้ คุณยังสามารถบอกรับไปที่การเผยแพร่ตัวเดียวทั้งสองครั้งด้วย *อาร์กิวเมนท์ชุดเดียวกันก็ได้ !* ซึ่งวิธีใช้แบบนั้นอาจจะหาวิธีทำให้เกิดประโยชน์ได้ยาก แต่ด้วยความยืดหยุ่นที่มี เราอาจมีโอกาสนำไปใช้ประโยชน์ได้ในวันหน้า !
### คอลเลกชั่นหลายตัว บอกรับครั้งเดียว
สิ่งที่ไม่เหมือนกับฐานข้อมูลแบบ relational เช่น MySQL ที่ใช้การ *joins* เป็นหลัก ก็คือ ฐานข้อมูลแบบ NoSQL เช่น Mongo จะเกี่ยวข้องกับ *denormalizing* และ *embedding* เป็นสำคัญ เราจะมาดูกันว่า มันทำงานได้อย่างไรใน Meteor
ลองดูตัวอย่างที่ชัดเจนกันก่อน การที่เราเพิ่มข้อคิดเห็นเข้าไปที่ข่าว และเราก็พอใจกับการเผยแพร่เฉพาะข้อคิดเห็นของข่าวที่ผู้ใช้กำลังดูอยู่เท่านั้น
แต่ถ้าหากว่า เราต้องการจะแสดงข้อคิดเห็นของข่าว *ทั้งหมด* ในหน้าแรกขึ้นมาล่ะ (จำไว้ด้วยว่าข่าวที่แสดงจะเปลี่ยนไปเมื่อเราโหลดข่าวที่เหลือเพิ่มเข้ามาอีก ด้วยวิธีแบ่งหน้า) กรณีนี้ทำให้เรามีเหตุผลที่ดีที่จะฝังข้อคิดเห็นเข้าไปในข่าว และจริงๆแล้วมันคือสาเหตุหนึ่งที่เราทำ denormalize กับ *จำนวน* ข้อคิิดเห็นด้วย
อันที่จริงเราก็สามารถฝังข้อคิดเห็นเข้าไปในข่าว และกำจัดคอลเลกชั่น `Comments` ออกไปพร้อมๆกันได้อยู่แล้ว แต่ก็เหมือนกับที่เราเห็นในบท *การ denormalize* ที่การทำอย่างนั้น จะทำให้เราสูญเสียความสามารถพิเศษที่จะใช้จัดการกับคอลเลกชั่นที่แยกออกจากกัน
แต่ปรากฏว่า มันยังมีวิธีที่เกี่ยวข้องกับการบอกรับข้อมูล ที่ทำให้สามารถฝังข้อคิดเห็นของเรา และแยกคอลเลกชั่นออกจากกันได้ด้วย
สมมุติว่า ในหน้าแรกที่เราแสดงรายการข่าวนั้น เราต้องการให้แต่ละข่าว บอกรับข้อมูลข้อคิดเห็นสองตัวแรก ไปพร้อมๆกัน
มันค่อนข้างยากที่จะทำแบบนี้ได้ด้วยการใช้ข้อมูลจากการเผยแพร่ที่ไม่เกี่ยวข้องกัน โดยเฉพาะอย่างยิ่งเมื่อจำนวนข่าวถูกจำกัดไว้ด้วยเงื่อนไข (เช่น 10 ข่าวล่าสุด) ซึ่งทำให้เราต้องเขียนโค้ดการเผยแพร่คล้ายๆแบบนี้
<%= diagram "multiplecollections", "Two collections in one subscription", "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()` และเพื่อนๆ) ดังนั้นเราก็ไม่จำเป็นต้องใช้ `_publishingCursor` ทำงานให้เราโดยคืนค่าเคอร์เซอร์กลับมา
ตอนนี้ทุกๆครั้งที่เราเผยแพร่ข่าว เราก็เผยแพร่ข้อคิดเห็นสองตัวแรกติดไปกับมันด้วย โดยใช้การบอกรับแค่ครั้งเดียว !
ถึงแม้ว่า Meteor จะไม่ได้ทำให้วิธีนี้ใช้งานได้โดยตรง แต่คุณก็สามารถดูได้ที่แพ็คเกจ `publish-with-relations`บนเว็บ Atmosphere ได้ ซึ่งแพ็คเกจนี้จะช่วยให้เราใช้งานในรูปแบบนี้ได้ง่ายขึ้น
### เชื่อมโยงหลายคอลเลกชั่น
ด้วยความยืดหยุ่นของการบอกรับข้อมูลที่เราเพิ่งค้นพบนั้น คำตอบคือ ถ้าเราไม่ใช้ `_publishCursor` เราก็ไม่จำเป็นต้องทำตามข้อจำกัดที่ว่า คอลเลกชั่นต้นทางบนเซิร์ฟเวอร์จำเป็นต้องมีชื่อเดียวกับคอลเลกชั่นปลายทางที่ไคลเอนต์
<%= diagram "linkedcollections", "One collection for two subscriptions", "pull-center" %>
เหตุผลหนึ่งที่ทำให้คุณต้องทำสิ่งนี้คือ *การสืบทอดจากตารางเดียว (SIngle Table Inheritance)*
สมมุติว่าเราต้องการอ้างถึงอ็อบเจกต์ชนิดต่างๆของข่าวเรา แต่ละตัวถูกเก็บในฟิลด์ที่ใช้ร่วมกัน โดยมีข้อมูลแตกต่างกันเล็กน้อย อย่างเช่น เราอาจสร้างระบบทำบล็อกคล้ายๆ 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 ของการเผยแพร่ ที่ทำให้ความเป็นไปได้ไม่มีที่สิ้นสุด