Comprendre Meteor Publish / Subscribe


84

J'ai mis en place une application simple qui affiche une liste de Projects. J'ai supprimé le autopublishpackage pour ne pas tout envoyer au client.

 <template name="projectsIndex">    
   {{#each projects}}      
     {{name}}
   {{/each}}
 </template>

Quand autopublishétait activé, cela afficherait tous les projets:

if Meteor.isClient
  Template.projectsIndex.projects = Projects.find()

Une fois supprimé, je dois également faire:

 if Meteor.isServer
   Meteor.publish "projects", ->
     Projects.find()
 if Meteor.isClient
   Meteor.subscribe "projects"
   Template.projectsIndex.projects = Projects.find()

Alors, est-il exact de dire que la find()méthode côté client ne recherche que les enregistrements qui ont été publiés du côté serveur? Cela m'a fait trébucher parce que j'avais l'impression que je ne devrais appeler find()qu'une seule fois.

Réponses:


286

Les collections, les publications et les abonnements sont un domaine délicat de Meteor, que la documentation pourrait discuter plus en détail, afin d'éviter une confusion fréquente , qui est parfois amplifiée par une terminologie confuse .

Voici Sacha Greif (co-auteur de DiscoverMeteor ) expliquant les publications et les abonnements en une seule diapositive:

abonnements

Pour bien comprendre pourquoi vous devez appeler find()plusieurs fois, vous devez comprendre comment les collections, les publications et les abonnements fonctionnent dans Meteor:

  1. Vous définissez des collections dans MongoDB. Aucun Meteor n'est encore impliqué. Ces collections contiennent des enregistrements de base de données (également appelés «documents» par Mongo et Meteor , mais un «document» est plus général qu'un enregistrement de base de données; par exemple, une spécification de mise à jour ou un sélecteur de requête sont également des documents - des objets JavaScript contenant des field: valuepaires).

  2. Ensuite, vous définissez des collections sur le serveur Meteor avec

    MyCollection = new Mongo.Collection('collection-name-in-mongo')
    

    Ces collections contiennent toutes les données des collections MongoDB, et vous pouvez les exécuter MyCollection.find({...}), ce qui renverra un curseur (un ensemble d'enregistrements, avec des méthodes pour les parcourir et les renvoyer).

  3. Ce curseur est (la plupart du temps) utilisé pour publier (envoyer) un ensemble d'enregistrements (appelé «ensemble d'enregistrements» ). Vous pouvez éventuellement publier uniquement certains champs de ces enregistrements. Ce sont des jeux d'enregistrements (et non des collections) auxquels les clients s'abonnent . La publication se fait par une fonction de publication , qui est appelée à chaque fois qu'un nouveau client s'abonne, et qui peut prendre des paramètres pour gérer les enregistrements à renvoyer (par exemple un identifiant d'utilisateur, pour ne renvoyer que les documents de cet utilisateur).

  4. Sur le client , vous disposez de collections Minimongo qui reflètent partiellement certains des enregistrements du serveur. "Partiellement" car ils peuvent ne contenir que certains des champs, et "certains des enregistrements" car vous souhaitez généralement envoyer au client uniquement les enregistrements dont il a besoin, pour accélérer le chargement de la page, et uniquement ceux dont il a besoin et qu'il a l'autorisation de accès.

    Minimongo est essentiellement une implémentation non persistante en mémoire de Mongo en JavaScript pur. Il sert de cache local qui stocke uniquement le sous-ensemble de la base de données avec laquelle ce client travaille. Les requêtes sur le client (find) sont servies directement à partir de ce cache, sans parler au serveur.

    Ces collections Minimongo sont initialement vides. Ils sont remplis par

    Meteor.subscribe('record-set-name')
    

    appels. Notez que le paramètre pour s'abonner n'est pas un nom de collection; c'est le nom d'un jeu d'enregistrements que le serveur a utilisé dans l' publishappel. L' subscribe()appel abonne le client à un ensemble d'enregistrements - un sous-ensemble d'enregistrements de la collection de serveurs (par exemple les 100 derniers articles de blog), avec tout ou un sous-ensemble des champs dans chaque enregistrement (par exemple uniquement titleet date). Comment Minimongo sait-il dans quelle collection placer les enregistrements entrants? Le nom de la collection sera l' collectionargument utilisé dans les Publish gestionnaire added, changed, et removedcallbacks, ou si ceux -ci sont portés disparus ( ce qui est le cas la plupart du temps), ce sera le nom de la collection MongoDB sur le serveur.

Modifier les enregistrements

C'est là que Meteor rend les choses très pratiques: lorsque vous modifiez un enregistrement (document) dans la collection Minimongo sur le client, Meteor mettra instantanément à jour tous les modèles qui en dépendent et renverra également les modifications au serveur, qui à son tour stockera les modifications dans MongoDB et les enverra aux clients appropriés qui se sont abonnés à un jeu d'enregistrements comprenant ce document. C'est ce qu'on appelle la compensation de latence et c'est l'un des sept principes fondamentaux de Meteor .

Abonnements multiples

Vous pouvez avoir un tas d'abonnements qui récupèrent différents enregistrements, mais ils finiront tous dans la même collection sur le client s'ils provenaient de la même collection sur le serveur, en fonction de leur _id. Cela n'est pas expliqué clairement, mais sous-entendu par la documentation Meteor:

Lorsque vous vous abonnez à un jeu d'enregistrements, il indique au serveur d'envoyer des enregistrements au client. Le client stocke ces fiches dans les collections Minimongo locales, avec le même nom que l' collectionargument utilisé dans la Publish gestionnaire added, changedet removedcallbacks. Meteor mettra en file d'attente les attributs entrants jusqu'à ce que vous déclariez la Mongo.Collection sur le client avec le nom de collection correspondant.

Ce qui est pas expliqué est ce qui se passe lorsque vous n'utilisez explicitement , et , ou publier des gestionnaires du tout - ce qui est la plupart du temps. Dans ce cas le plus courant, l'argument collection est (sans surprise) tiré du nom de la collection MongoDB que vous avez déclarée sur le serveur à l'étape 1. Mais cela signifie que vous pouvez avoir différentes publications et abonnements avec des noms différents, et tous les les enregistrements finiront dans la même collection sur le client. Jusqu'au niveau des champs de niveau supérieur , Meteor prend soin d'effectuer une union définie entre les documents, de sorte que les abonnements puissent se chevaucher - publier des fonctions qui expédient différents champs de niveau supérieur au client travaillent côte à côte et sur le client, le document dans le la collection sera laaddedchangedremovedunion des deux ensembles de champs .

Exemple: plusieurs abonnements remplissant la même collection sur le client

Vous avez une collection BlogPosts, que vous déclarez de la même manière sur le serveur et le client, même si elle fait des choses différentes:

BlogPosts = new Mongo.Collection('posts');

Sur le client, BlogPostspeut obtenir des enregistrements de:

  1. un abonnement aux 10 derniers articles de blog

    // server
    Meteor.publish('posts-recent', function publishFunction() {
      return BlogPosts.find({}, {sort: {date: -1}, limit: 10});
    }
    // client
    Meteor.subscribe('posts-recent');
    
  2. un abonnement aux publications de l'utilisateur actuel

    // server
    Meteor.publish('posts-current-user', function publishFunction() {
      return BlogPosts.find({author: this.userId}, {sort: {date: -1}, limit: 10});
      // this.userId is provided by Meteor - http://docs.meteor.com/#publish_userId
    }
    Meteor.publish('posts-by-user', function publishFunction(who) {
      return BlogPosts.find({authorId: who._id}, {sort: {date: -1}, limit: 10});
    }
    
    // client
    Meteor.subscribe('posts-current-user');
    Meteor.subscribe('posts-by-user', someUser);
    
  3. un abonnement aux articles les plus populaires

  4. etc.

Tous ces documents proviennent de la postscollection dans MongoDB, via la BlogPostscollection sur le serveur, et se retrouvent dans la BlogPostscollection sur le client.

Nous pouvons maintenant comprendre pourquoi vous devez appeler find()plusieurs fois - la deuxième fois sur le client, car les documents de tous les abonnements se retrouveront dans la même collection et vous ne devez récupérer que ceux qui vous intéressent. Par exemple, pour obtenir les messages les plus récents sur le client, il vous suffit de mettre en miroir la requête du serveur:

var recentPosts = BlogPosts.find({}, {sort: {date: -1}, limit: 10});

Cela renverra un curseur sur tous les documents / enregistrements que le client a reçus jusqu'à présent, à la fois les principaux messages et les messages de l'utilisateur. ( merci Geoffrey ).


10
C'est bien. Il convient peut-être de mentionner ce qui se passe si vous faites BlogPosts.find({})sur le client après vous être abonné aux deux publications - c'est-à-dire qu'il renverra un curseur de tous les documents / enregistrements actuellement sur le client, à la fois les principaux messages et les messages de l'utilisateur. J'ai vu d'autres questions sur SO où le questionneur était confus par cela.
Geoffrey Booth

3
C'est bien. Merci. De plus, la collection Meteor.users () devient un peu déroutante car elle est publiée automatiquement côté client. Peut-on ajouter un peu à la réponse ci-dessus pour indiquer la collection users ()?
Jimmy MG Lim

3
Même si beaucoup plus que ce qui avait été demandé à l'origine, je pense que @DVG devrait marquer cet excellent article comme la réponse acceptée. Merci Dan.
physiocoder

1
Merci @DanDascalescu, Excellente explication qui m'a beaucoup éclairé, la seule chose qui, après avoir lu votre explication, après avoir lu votre explication, BlogPostsn'est pas une collection, c'est l'objet retourné qui a des méthodes comme "insérer", "mettre à jour "..etc, et la vraie collection se trouve également postsdans le client et le serveur.
UXE

4
Est-il possible d'appeler uniquement le jeu d'enregistrements auquel vous vous êtes abonné? Comme dans, est-il possible d'obtenir directement le jeu d'enregistrements dans mon javascript, au lieu d'interroger localement la base de données Minimongo?
Jimmy Knoot le

27

Oui, le find () côté client ne renvoie que les documents qui se trouvent sur le client dans Minimongo. À partir de la documentation :

Sur le client, une instance Minimongo est créée. Minimongo est essentiellement une implémentation non persistante en mémoire de Mongo en JavaScript pur. Il sert de cache local qui stocke uniquement le sous-ensemble de la base de données avec laquelle ce client travaille. Les requêtes sur le client (find) sont servies directement à partir de ce cache, sans parler au serveur.

Comme vous le dites, publish () spécifie quels documents le client aura.


1

La règle empirique de base est ici publishet subscribedles noms de variables doivent être les mêmes côté client et côté serveur.

Les noms des collections sur Mongo DB et côté client doivent être identiques.

Supposons que j'utilise publier et m'abonner pour ma collection nommée employeesalors le code ressemblerait à


du côté serveur

Ici, l'utilisation du varmot-clé est facultative (utilisez ce mot-clé pour rendre la collection locale à ce fichier).

CollectionNameOnServerSide = new Mongo.Collection('employees');   

Meteor.publish('employeesPubSub', function() { 
    return CollectionNameOnServerSide.find({});     
});

fichier .js côté client

CollectionNameOnClientSide = new Mongo.Collection('employees');
var employeesData = Meteor.subscribe('employeesPubSub');

Template.templateName.helpers({
  'subcribedDataNotAvailable' : function(){
        return !employeesData.ready();
    },
   'employeeNumbers' : () =>{
       CollectionNameOnClientSide.find({'empId':1});
  }
});

fichier .html côté client

Ici, nous pouvons utiliser la subcribedDataNotAvailableméthode d'assistance pour savoir si les données sont prêtes côté client, si les données sont prêtes, puis imprimer les numéros d'employés à l' employeeNumbersaide de la méthode d'assistance.

<TEMPLATE name="templateName">
{{#if subcribedDataNotAvailable}}
   <h1> data loading ... </h1>
 {{else}}
  {{#each employeeNumbers }}
     {{this}}
  {{/each}}
 {{/if}}
<TEMPLATE>

0
// on the server
Meteor.publish('posts', function() {

    return Posts.find();

});

// on the client
Meteor.subscribe('posts');
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.