-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy path07s-latency-compensation.md.erb
187 lines (134 loc) · 9.94 KB
/
07s-latency-compensation.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
---
title: La Compensation de Latence
slug: latency-compensation
date: 0007/01/02
number: 7.5
points: 5
sidebar: true
photoUrl: http://www.flickr.com/photos/ikewinski/9473352049/
photoAuthor: Mike Lewinski
contents: Comprendre la compensation de latence.|Ralentissez votre application et voyez ce qu'il se passe.|Apprendre comment les méthodes Meteor interagissent entre elles.
paragraphs: 28
---
Dans le dernier chapitre, nous avons présenté un nouveau concept du monde Meteor : les **Méthodes**.
<%= diagram "latency1", "Sans compensation de latence", "pull-right" %>
Une Méthode Meteor est un moyen d'exécuter une série de commandes sur le serveur d'une façon structurée. Dans notre exemple, nous avons utilisé une Méthode car nous voulions nous assurer que les nouveaux articles seraient tagués avec le nom et l'id de leur auteur ainsi que l'heure du serveur.
Cependant, si Meteor exécute des Methodes dans sa plus simple façon, nous aurions un problème. Considérez la séquence suivante d’événements (note : les horodatages sont des valeurs aléatoires choisies seulement pour l'exemple) :
- *+0ms:* L'utilisateur clique sur un bouton et le navigateur renvoie un appel de Méthode.
- *+200ms:* Le serveur fait des changements dans la base de données Mongo
- *+500ms:* Le client reçoit ces changements, met à jour l'interface utilisateur.
Si Meteor opérait de cette façon, il y aurait alors un petit décalage entre la réalisation de ces actions et l'affichage des résultats (ce décalage étant plus ou moins visible selon votre proximité avec le serveur). Nous ne pouvons nous le permettre dans une application web moderne !
### Compensation de latence
<%= diagram "latency2", "Avec compensation de latence", "pull-right" %>
Pour éviter ce problème, Meteor introduit un concept appelé **Compensation de latence**. Quand nous avons définit notre Méthode `post`, nous l'avons placée à l'intérieur d'un fichier dans le répertoire `collections/`. Ceci signifie qu'elle est disponible pour le serveur *et le client* -- et elle s'exécutera sur les deux en même temps !
Quand nous faisons un appel de Méthode, le client envoie l'appel au serveur, mais *simule* également simultanément l'action de la Méthode sur ses collections côté client. Notre workflow devient :
- *+0ms:* L'utilisateur clique sur un bouton et le navigateur renvoie un appel de Méthode.
- *+0ms:* Le client simule l'action de l'appel de Méthode sur les collections côté client et met à jour l'interface utilisateur pour refléter ceci.
- *+200ms:* Le serveur fait les changements dans la base de données Mongo.
- *+500ms:* Le client reçoit ces changements et annule ses changements simulés, en les remplaçant par les changements du serveur (qui sont généralement les mêmes). L'interface utilisateur change pour refléter ceci.
Le résultat pour l'utilisateur est de voir les changements instantanément. Quand la réponse du serveur revient un peu plus tard, il peut y avoir (ou non) des changements visibles à mesure que les documents canoniques du serveur arrivent. Une chose à retenir est donc que nous devons nous assurer que nous simulons les documents aussi proches de la réalité que possible.
### Observer une compensation de latence
Nous pouvons faire un petit changement à l'appel de la méthode `post` pour voir cela en action. Pour ce faire, nous utiliserons la fonction bien pratique `Meteor._sleepForMs()` pour retarder l'appel de la méthode de 5 secondes, mais (c'est un point crucial) *seulement sur le serveur*.
Nous utiliserons `isServer` pour demander à Meteor si la Méthode est actuellement invoquée sur le client (comme une "ébauche") ou sur le serveur. Une [ébauche](http://docs.meteor.com/#methods_header) est la simulation d'une Méthode que Meteor exécute sur le client en parallèle, pendant que la "vraie" Méthode est en cours d'exécution sur le serveur.
Nous allons donc demander à Meteor si le code est en cours d'exécution sur le serveur. Si c'est le cas, nous allons retarder l'avancement des choses de 5 secondes et ajouter la chaîne de caractère `(server)` à la fin du titre de notre article. Sinon, nous ajouterons la chaîne de caractère `(client)` :
~~~js
Posts = new Mongo.Collection('posts');
Meteor.methods({
postInsert: function(postAttributes) {
check(this.userId, String);
check(postAttributes, {
title: String,
url: String
});
if (Meteor.isServer) {
postAttributes.title += postAttributes.title + "(server)";
// attente de 5 secondes
Meteor._sleepForMs(5000);
} else {
postAttributes.title += "(client)";
}
var postWithSameLink = Posts.findOne({url: postAttributes.url});
if (postWithSameLink) {
return {
postExists: true,
_id: postWithSameLink._id
}
}
var user = Meteor.user();
var post = _.extend(postAttributes, {
userId: user._id,
author: user.username,
submitted: new Date()
});
var postId = Posts.insert(post);
return {
_id: postId
};
}
});
~~~
<%= caption "collections/posts.js" %>
<%= highlight "11~17" %>
Si nous nous arrêtions ici, la démonstration ne serait pas concluante. Dans l'état des choses, on dirait que le formulaire de soumission d'article se met en pause pendant 5 secondes avant de rediriger vers la liste des posts principaux, et pas grand chose d'autre se passe.
Pour comprendre cela, revenons au manager de l'événement de soumission de post :
~~~js
Template.postSubmit.events({
'submit form': function(e) {
e.preventDefault();
var post = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
};
Meteor.call('postInsert', post, function(error, result) {
// affiche l'erreur à l'utilisateur et s'interrompt
if (error)
return alert(error.reason);
// affiche ce résultat mais 'route' tout de même
if (result.postExists)
alert('This link has already been posted');
Router.go('postPage', {_id: result._id});
});
}
});
~~~
<%= caption "client/templates/posts/post_submit.js" %>
Nous avons placé notre appel de routeur `Router.go()` à l'intérieur du callback de l'appel de la méthode. Ce qui signifie que le formulaire attend que cette méthode réussisse avant de rediriger.
C'est normalement la bonne manière de faire les choses. Après tout, vous ne pouvez pas rediriger l'utilisateur avant de savoir si la soumission de son post est valide ou pas, parce que ce serait extrêmement déroutant d'être redirigé une première fois et ensuite d'être redirigé une nouvelle fois, en arrière, vers la page de soumission de post pour corriger vos données, tout cela en quelques secondes.
Mais pour le bien de cet exemple, nous voulons voir les résultats de nos actions immédiatement. Ainsi, nous allons changer l'appel du routeur pour rediriger vers la route `postsList` (nous ne pouvons pas diriger vers le post car nous ne connaissons pas son `_id` en dehors de la méthode), pour ensuite l'extraire du callback, et voir ce qui se passe :
~~~js
Template.postSubmit.events({
'submit form': function(event) {
event.preventDefault();
var post = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
};
Meteor.call('postInsert', post, function(error, result) {
// affiche l'erreur à l'utilisateur et s'interrompt
if (error)
return alert(error.reason);
// affiche se résultat mais 'route' tout de même
if (result.postExists)
alert('This link has already been posted');
});
Router.go('postsList');
}
});
~~~
<%= caption "client/templates/posts/post_submit.js" %>
<%= highlight "20" %>
<%= scommit "7-5-1", "Démonstration de l'ordre d'apparition des articles en utilisant un sleep." %>
Si nous créons un article maintenant, nous verrons clairement la compensation de latence en action. Premièrement, un article est inséré avec `(client)` dans le titre (le premier article dans la liste, relié à Github) :
<%= screenshot "s5-1", "Notre article comme initialement stocké dans la collection côté client" %>
Ensuite, cinq secondes plus tard, il est proprement remplacé avec le vrai document qui a été inséré par le serveur :
<%= screenshot "s5-2", "Notre article une fois que le client reçoit la mise à jour de la collection côté serveur" %>
### Méthodes des collections sur le client
Vous pourriez penser que les Méthodes sont compliquées après cela, mais en fait elles peuvent être plutôt simples. En fait, nous avons déjà vu trois méthodes très simples : les Méthodes de mutation de collection, `insert`, `update` et `remove`.
Quand vous définissez une collection serveur appelée `'posts'`, vous êtes implicitement en train de définir trois Méthodes : `posts/insert`, `posts/update` et `posts/delete`. En d'autres mots, quand vous appelez `Posts.insert()` sur votre collection client, vous appelez une Méthode de compensation de latence qui fait deux choses :
1. Des vérifications pour voir si vous faites la mutation en appelant des callbacks `allow` et `deny` (cependant il n'est pas nécessaire que cela arrive dans la simulation).
2. La modification vers le stockage de données sous-jacent.
### Des méthodes qui appellent des Méthodes
Si vous suivez bien, vous venez peut-être juste de réaliser que notre Méthode `post` appelle une autre Méthode (`posts/insert`) quand nous insérons notre article. Comment ça marche ?
Quand la simulation (version côté client de la Méthode) est en cours d'exécution, nous effectuons la simulation d'un `insert` (nous insérons donc dans notre collection cliente), mais nous *ne* faisons *pas* le vrai, `insert` côté serveur, nous attendons que la version *côté serveur* de `post` le fasse.
Par conséquent, quand la Méthode `post` côté serveur appelle `insert` il n'y a pas besoin de s'inquiéter de la simulation, et l'insertion continue sans encombre.
N'oubliez pas comme précédemment de supprimer les changements effectués au cours de ce chapitre avant de passer au suivant.