Entendiendo Meteor Publish / Subscribe


84

Tengo una aplicación sencilla configurada que muestra una lista de Projects. He eliminado el autopublishpaquete para no enviar todo al cliente.

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

Cuando autopublishse enciende, esto muestra todos los proyectos:

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

Una vez eliminado, también tengo que hacer:

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

Entonces, ¿es correcto decir que el find()método del lado del cliente solo busca registros que se han publicado desde el lado del servidor? Me ha estado haciendo tropezar porque sentí que solo debería llamar find()una vez.

Respuestas:


286

Las colecciones, publicaciones y suscripciones son un área complicada de Meteor, que la documentación podría discutir con más detalle, para evitar confusiones frecuentes , que a veces se amplifican con terminología confusa .

Aquí está Sacha Greif (coautor de DiscoverMeteor ) explicando las publicaciones y suscripciones en una diapositiva:

suscripciones

Para comprender correctamente por qué necesita llamar find()más de una vez, debe comprender cómo funcionan las colecciones, publicaciones y suscripciones en Meteor:

  1. Define colecciones en MongoDB. Ningún meteorito involucrado todavía. Estas colecciones contienen los registros de base de datos (también llamados "documentos" por tanto Mongo y Meteor , pero un "documento" es más general que un registro de base de datos; por ejemplo, una especificación de actualizaciones o un selector de consultas son documentos demasiado - JavaScript objetos que contienen field: valuepares).

  2. Luego, define colecciones en el servidor Meteor con

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

    Estas colecciones contienen todos los datos de las colecciones de MongoDB, y puede ejecutarlas MyCollection.find({...}), lo que devolverá un cursor (un conjunto de registros, con métodos para recorrerlos en iteración y devolverlos).

  3. Este cursor se utiliza (la mayoría de las veces) para publicar (enviar) un conjunto de registros (denominado "conjunto de registros" ). Opcionalmente, puede publicar solo algunos campos de esos registros. Son conjuntos de registros ( no colecciones) a los que se suscriben los clientes . La publicación se realiza mediante una función de publicación , que se llama cada vez que se suscribe un nuevo cliente, y que puede tomar parámetros para administrar qué registros devolver (por ejemplo, una identificación de usuario, para devolver solo los documentos de ese usuario).

  4. En el cliente , tiene colecciones de Minimongo que reflejan parcialmente algunos de los registros del servidor. "Parcialmente" porque pueden contener solo algunos de los campos y "algunos de los registros" porque normalmente desea enviar al cliente solo los registros que necesita, para acelerar la carga de la página, y solo aquellos que necesita y tiene permiso para acceso.

    Minimongo es esencialmente una implementación no persistente en memoria de Mongo en JavaScript puro. Sirve como caché local que almacena solo el subconjunto de la base de datos con la que está trabajando este cliente. Las consultas en el cliente (buscar) se sirven directamente desde esta caché, sin hablar con el servidor.

    Estas colecciones de Minimongo están inicialmente vacías. Están llenos de

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

    llamadas. Tenga en cuenta que el parámetro para suscribirse no es un nombre de colección; es el nombre de un conjunto de registros que utilizó el servidor en la publishllamada. La subscribe()llamada suscribe al cliente a un conjunto de registros : un subconjunto de registros de la colección del servidor (por ejemplo, las 100 publicaciones de blog más recientes), con todos o un subconjunto de los campos en cada registro (por ejemplo, solo titley date). ¿Cómo sabe Minimongo en qué colección colocar los registros entrantes? El nombre de la colección será el collectionargumento que se utiliza en los manejadores de publicar added, changedy removeddevoluciones de llamada, o si los desaparecidos (que es el caso de la mayor parte del tiempo), que será el nombre de la colección MongoDB en el servidor.

Modificar registros

Aquí es donde Meteor hace las cosas muy convenientes: cuando modifica un registro (documento) en la colección de Minimongo en el cliente, Meteor actualizará instantáneamente todas las plantillas que dependen de él y también enviará los cambios al servidor, que a su vez almacenará los cambios en MongoDB y los enviará a los clientes correspondientes que se hayan suscrito a un conjunto de registros que incluya ese documento. Esto se llama compensación de latencia y es uno de los siete principios básicos de Meteor .

Varias suscripciones

Puede tener un montón de suscripciones que obtengan diferentes registros, pero todas terminarán en la misma colección en el cliente si provienen de la misma colección en el servidor, según su _id. Esto no se explica claramente, pero está implícito en los documentos de Meteor:

Cuando se suscribe a un conjunto de registros, le dice al servidor que envíe registros al cliente. El cliente almacena estos registros en colecciones locales Minimongo, con el mismo nombre que el collectionargumento utilizado en el manejador de publicar added, changedy removeddevoluciones de llamada. Meteor pondrá en cola los atributos entrantes hasta que declare Mongo.Collection en el cliente con el nombre de la colección correspondiente.

Lo que no se explica es lo que pasa cuando no se utiliza de forma explícita added, changedy removed, o publicar los manipuladores en absoluto - que es la mayor parte del tiempo. En este caso más común, el argumento de la colección se toma (como era de esperar) del nombre de la colección de MongoDB que declaró en el servidor en el paso 1. Pero lo que esto significa es que puede tener diferentes publicaciones y suscripciones con diferentes nombres, y todas las los registros terminarán en la misma colección del cliente. Hasta el nivel de los campos de nivel superior , Meteor se encarga de realizar una unión establecida entre documentos, de modo que las suscripciones puedan superponerse: funciones de publicación que envían diferentes campos de nivel superior al trabajo del cliente en paralelo y en el cliente, el documento en el la colección será launión de los dos conjuntos de campos .

Ejemplo: múltiples suscripciones llenando la misma colección en el cliente

Tienes una colección BlogPosts, que declaras de la misma manera tanto en el servidor como en el cliente, aunque hace cosas diferentes:

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

En el cliente, BlogPostspuede obtener registros de:

  1. una suscripción a las 10 publicaciones de blog más recientes

    // server
    Meteor.publish('posts-recent', function publishFunction() {
      return BlogPosts.find({}, {sort: {date: -1}, limit: 10});
    }
    // client
    Meteor.subscribe('posts-recent');
    
  2. una suscripción a las publicaciones del usuario actual

    // 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. una suscripción a las publicaciones más populares

  4. etc.

Todos estos documentos provienen de la postscolección en MongoDB, a través de la BlogPostscolección en el servidor, y terminan en la BlogPostscolección en el cliente.

Ahora podemos entender por qué necesita llamar find()más de una vez: la segunda vez está en el cliente, porque los documentos de todas las suscripciones terminarán en la misma colección y solo debe buscar aquellos que le interesan. Por ejemplo, para obtener las publicaciones más recientes en el cliente, simplemente refleje la consulta desde el servidor:

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

Esto devolverá un cursor a todos los documentos / registros que el cliente ha recibido hasta ahora, tanto las publicaciones principales como las publicaciones del usuario. ( gracias Geoffrey ).


10
Esto es genial. Quizás vale la pena mencionar lo que sucede si lo hace BlogPosts.find({})en el cliente después de suscribirse a ambas publicaciones, es decir, devolverá un cursor de todos los documentos / registros que se encuentran actualmente en el cliente, tanto las publicaciones principales como las publicaciones del usuario. He visto otras preguntas sobre SO en las que el interrogador estaba confundido por esto.
Geoffrey Booth

3
Esto es genial. Gracias. Además, la colección Meteor.users () se vuelve un poco confusa ya que se publica automáticamente en el lado del cliente. ¿Se puede agregar un poco a la respuesta anterior para indicar la colección de usuarios ()?
Jimmy MG Lim

3
Incluso si mucho más de lo que se pidió originalmente, creo que @DVG debería marcar este gran artículo como la respuesta aceptada. Gracias Dan.
Physiocoder

1
Gracias @DanDascalescu, Gran explicación que me despejó mucho, lo único que cuando sigo los documentos meteorológicos sobre "colecciones" después de leer su explicación, creo BlogPostsque no es una colección, es el objeto devuelto que tiene métodos como "insertar", "actualizar "..etc, y la colección real está postsen el cliente y también en el servidor.
UXE

4
¿Es posible solicitar solo el conjunto de registros al que está suscrito? Como en, ¿es posible obtener directamente el conjunto de registros en mi javascript, en lugar de consultar la base de datos de Minimongo localmente?
Jimmy Knoot

27

Sí, find () del lado del cliente solo devuelve documentos que están en el cliente en Minimongo. De los documentos :

En el cliente, se crea una instancia de Minimongo. Minimongo es esencialmente una implementación no persistente en memoria de Mongo en JavaScript puro. Sirve como caché local que almacena solo el subconjunto de la base de datos con la que está trabajando este cliente. Las consultas en el cliente (buscar) se sirven directamente desde esta caché, sin hablar con el servidor.

Como dices, publish () especifica qué documentos tendrá el cliente.


1

La regla básica aquí es publish y subscribednombres de las variables debe ser el mismo en el cliente y el servidor.

Los nombres de las colecciones en Mongo DB y en el lado del cliente deben ser los mismos.

Supongamos que estoy usando publicar y suscribirme para mi colección nombrada, employeesentonces el código se vería así


lado del servidor

Aquí el uso de la varpalabra clave es opcional (use esta palabra clave para hacer que la colección sea local para este archivo).

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

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

archivo .js del lado del cliente

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

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

archivo .html del lado del cliente

Aquí podemos usar el subcribedDataNotAvailablemétodo de ayuda para saber si los datos están listos en el lado del cliente, si los datos están listos, entonces imprima los números de los empleados usando el employeeNumbersmétodo de ayuda.

<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');
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.