Pubblicazioni e Sottoscrizioni

Approfondimento 4.5

Percentuale Tradotta

In questo capitolo imparerai:

  • Come funzionano pubblicazioni e sottoscrizioni.
  • Cosa fa il pacchetto Autopublish
  • Alcuni schemi di pubblicazione.
  • Pubblicazioni e sottoscrizioni sono uno dei fondamentali e più importanti concetti di Meteor, ma possono risultare complicati da capire all'inizio.

    Questo ha portato a molte incomprensioni, come la convinzione che Meteor sia insicuro o che le applicazioni in Meteor non possono gestire grandi quantità di dati.

    Buona parte dei motivi per cui le persone trovano questi concetti un po’ confusi inizialmente è la “magia” che Meteor fa per noi. Sebbene questa magia sia molto utile, può oscurare ciò che sta succedendo realmente dietro le scene (come la magia tende a fare). Proviamo a togliere gli strati di magia per capire ciò che sta accadendo.

    I vecchi tempi

    Guardiamo prima a ciò che accadeva nel 2011 quando Meteor non era ancora disponibile. Diciamo che state costruendo una semplice applicazione in Rails. Quando un utente raggiunge il vostro sito, il cliente (ad esempio il browser) manda una richiesta all'applicazione che risiede sul server.

    Il primo lavoro dell'applicazione è quello di capire che dati l'utente vuole vedere. Potrebbe essere pagina 12 dei risultati di ricerca, le informazioni del profilo di Mary, i 20 ultimi tweet di Bob e così via. Potete figurarla come un commesso di una libreria che cerca negli scaffali il libro che avete richiesto.

    Quando i dati corretti sono stati selezionati, il secondo lavoro dell'applicazione è quello di tradurre i dati in formato HTML leggibile da una persona (o JSON nel caso di un API).

    Nella metafora della libreria, sarebbe il passaggio di impacchettare il libro che avete appena comprato e metterlo in una bella borsa. Questa è la parte “View” del famoso schema Model-View-Controller.

    Infine, l'applicazione prende il codice HTML e lo restituisce al browser. Il lavoro è finito e ora che tutto è stato consegnato l'applicazione può rilassarsi con una birra e aspettare per la prossima richiesta.

    Come funziona Meteor

    Rivediamo cosa rende Meteor così speciale. Come abbiamo visto, l'innovazione di Meteor sta nel fatto che se un'applicazione Rails vive solo sul server, un'applicazione Meteor comprende anche una componente che risiede nel client (il browser).

    Pushing a subset of the database to the client.
    Pushing a subset of the database to the client.

    Questo è come un commesso che non solo trova il giusto libro per voi, ma vi segue fino a casa per leggerlo di notte (che ammettiamo suona un po’ spaventoso).

    Questa architettura fa in modo che Meteor faccia molte cose interessanti, prima fra tutte quello che Meteor chiama database ovunque. In parole semplici, Meteor prende una parte del database e lo copia nel client.

    Ci sono due grandi implicazioni: primo, invece di mandare il codice HTML al client, un'applicazione Meteor invia i dati grezzi e lascia che sia il client ad occuparsene (dati sul filo). In secondo luogo, si è in grado di accedere ai dati istantaneamente senza dover aspettare un intero giro di scambio con il server (compensazione di latenza).

    Pubblicare

    Il database di un'applicazione può contenere decine di migliaia di documenti, alcuni dei quali possono essere dati privati o sensibili. Per questo motivo non dobbiamo ovviamente replicare tutto il database nel client, per ragioni di sicurezza e scalabilità.

    Abbiamo bisogno di un modo per dire a Meteor quale sottoinsieme di dati può essere inviato al client e lo faremo attraverso una pubblicazione.

    Torniamo a Microscope. Qui abbiamo tutti i post che si trovano nel database dell'applicazione:

    Tutti i posts contenuti nel database.
    Tutti i posts contenuti nel database.

    Immaginiamo che alcuni dei nostri post siano stati segnalati per linguaggio inappropriato, anche se questa opzione non esiste al momento in Microscope. Anche se vogliamo tenere questi post nel database, non devono essere resi disponibili agli utenti (ad esempio inviati al client).

    Il nostro primo compito è di dire a Meteor quali dati vogliamo mandare al client. Diciamo a Meteor che vogliamo pubblicare solo i post non segnalati:

    Esclusione dei posts segnalati.
    Esclusione dei posts segnalati.

    Questo è il codice corrispondente, che risiede sul server:

    // on the server
    Meteor.publish('posts', function() {
      return Posts.find({flagged: false});
    });
    

    Questo assicura che non ci sia nessun modo possibile che un client possa accedere ai post segnalati. In questo modo si rende sicura l'applicazione Meteor: dovete assicurarvi che state pubblicando solo i dati a cui volete che il client acceda.

    DDP

    Fondamentalmente potete pensare a pubblicazione/sottoscrizione come ad un tubo che trasferisce i dati da una collezione sul server (sorgente) ad una collezione lato client (obiettivo).

    Il protocollo che gestisce questo comunicazione è chiamato DDP (che sta per Distributed Data Protocol). Per saperne di più sul DPP potete guardare questo intervento alla Real-time Conference di Matt DeBergalis (uno dei fondatori di Meteor), o questo screencast di Chris Mather che vi mostra qualche dettaglio in più su questo concetto.

    Sottoscrizioni

    Anche se vogliamo che gli utenti vedano tutti i post non segnalati, non possiamo inviare migliaia di post alla volta. Abbiamo bisogno di un modo con cui i client possano specificare di quale sottoinsieme di dati hanno bisogno e lo facciamo attraverso le sottoscrizioni.

    Ogni dato a cui si fa una sottoscrizione viene replicato sul client grazie a Minimongo, l'implementazione lato client, contenuto in Meteor, di MongoDB.

    Ad esempio, stiamo navigando sul profilo di Bob Smith e vogliamo mostrare solo i suoi post.

    Sottoscrivendosi ai posts di Bob, questi verranno replicati sul client.
    Sottoscrivendosi ai posts di Bob, questi verranno replicati sul client.

    Come prima cosa sistemiamo le nostre pubblicazioni in modo che accettino un parametro:

    // on the server
    Meteor.publish('posts', function(author) {
      return Posts.find({flagged: false, author: author});
    });
    

    Definiamo questo parametro quando facciamo sottoscrizione a quella pubblicazione nel codice lato client della nostra applicazione:

    // on the client
    Meteor.subscribe('posts', 'bob-smith');
    

    Per rendere scalabile un'applicazione Meteor lato client, invece di sottoscrivere ogni dato disponibile, prendiamo e scegliamo le parti che sono necessarie. In questo modo evitiamo il sovraccarico della memoria del browser indipendentemente da quando è grande il database lato server.

    Come trovare i dati

    I post di Bob sono divisi in più categorie (per esempio: “JavaScript”, “Ruby” e “Phyton”). Potremmo voler ancora caricare tutti i post di Bob in memoria, ma vogliamo mostrare solo quelli della categoria “JavaScript”. Vediamo come trovarli.

    Selezione di un sottoinsieme di documenti lato client.
    Selezione di un sottoinsieme di documenti lato client.

    Come abbiamo fatto sul server, useremo la funzione Posts.find() per selezionare un sottoinsieme dei nostri dati:

    // on the client
    Template.posts.helpers({
        posts: function(){
            return Posts.find({author: 'bob-smith', category: 'JavaScript'});
        }
    });
    

    Ora che abbiamo più informazioni sul ruolo di pubblicazioni e sottoscrizioni, andiamo più a fondo e osserviamo alcuni schemi comuni di implementazione.

    Autopubblicazione

    Se create un progetto Meteor da zero (ad esempio usando meteor create), verrà automaticamente abilitato il pacchetto autopublish. Come prima cosa vediamo esattemente cosa fa.

    L'obiettivo di autopublish è di semplificare molto l'iniziare a scrivere codice in un'applicazione Meteor, replicando automaticamente tutti i dati dal server al client, prendendosi carico di pubblicazioni e sottoscrizioni al posto nostro.

    Autopublish
    Autopublish

    Come funziona? Supponiamo di avere una collezione chiamata 'posts' sul server: autopublish invia automaticamente ogni post che trova nella collezione lato server in Mongo in una collezione chiamata 'posts' sul client (assumendo che ne esista una).

    Se state usando autopublish, non dovete preoccuparvi delle pubblicazioni. I dati sono dappertutto e le cose sono semplici. Ci sono ovvie problematiche legate al fatto di avere una copia completa del database dell'applicazione nella cache della macchina di ogni utente.

    Per questo motivo autopublish è utile solo quando state iniziando e non avete ancora pensato alle pubblicazioni.

    Pubblicare intere collezioni

    Dopo aver rimosso autopublish vi accorgerete presto che tutti i vostri dati sono spariti dal client. Un modo semplice per riverderli è di duplicare ciò che fa autopublish e pubblicare una collezione intera. Ad esempio:

    Meteor.publish('allPosts', function(){
      return Posts.find();
    });
    
    Pubblicare una collezione intera
    Pubblicare una collezione intera

    Stiamo ancora pubblicando le intere collezioni ma abbiamo ora controllo su quali vogliamo mostrare. In questo caso stiamo pubblicando Posts ma non Comments.

    Pubblicare collezioni parziali

    Il prossimo livello di controllo è di pubblicare solo parte di una collection. Ad esempio solo i post che appartengono ad un certo autore:

    Meteor.publish('somePosts', function(){
      return Posts.find({'author':'Tom'});
    });
    
    Pubblicare una collezione parziale.
    Pubblicare una collezione parziale.

    Dietro le quinte

    Se avete letto la documentazione di Meteor sulle pubblicazioni, vi sarete forse preoccupati sentendo parlare di come usare added() e ready() per settare gli attributi dei record sul client e avrete faticato a capire come fare guardando le applicazioni di Meteor che non usano mai quei metodi.

    Il motivo è che Meteor provvede una convezione molto importante: il metodo _publishCursor(). Non l'avete mai visto prima? Forse non direttamente ma se ritornate un cursore (ad esempio Posts.find({'author':'Tom'})) in una funzione di pubblicazione, è esattamente ciò che Meteor sta usando.

    Quando Meteor vede che la pubblicazione somePosts ha ritornato un cursore, chiama _publishCursor() per – avete indovinato – pubblicare quel cursore automaticamente.

    Questo è ciò che fa _publishCursor():

    • Controlla il nome della collezione lato server.
    • Prende tutti i documenti risultanti e li invia ad una collezione lato client con lo stesso nome (Usa added() per farlo).
    • Ogni volta che un documento viene aggiunto, rimosso o cambiato, invia questi cambiamenti alla collezione lato client (Usa .observe() sul cursore e .added(), .changed() e removed() per farlo).

    In questo esempio possiamo essere sicuri di inviare all'utente solo i post a cui sono interessati (quelli scritti da Tom) disponibili nella cache lato client.

    Pubblicare proprietà parziali

    Abbiamo visto come pubblicare alcuni dei nostri post ma possiamo andare ancora più a fondo! Vediamo come pubblicare solo specifiche proprietà.

    Come prima, usiamo find() per ritornare un cursore, ma questa volta escludiamo alcuni campi:

    Meteor.publish('allPosts', function(){
      return Posts.find({}, {fields: {
        date: false
      }});
    });
    
    Pubblicare proprietà parziali.
    Pubblicare proprietà parziali.

    Possiamo combinare entrambe le tecniche. Ad esempio, se vogliamo ritornare tutti i post di Tom ma non la loro data di pubblicazione, scriviamo:

    Meteor.publish('allPosts', function(){
      return Posts.find({'author':'Tom'}, {fields: {
        date: false
      }});
    });
    

    Riassumendo

    Abbiamo visto come pubblicare ogni proprietà di ogni documento di ogni collezione (con autopublish) e come pubblicare solo alcune proprietà di alcuni documenti di alcune collezioni.

    Questo copre le basi di quello che si può fare con le pubblicazioni in Meteor, e queste semplici tecniche bastano a coprire la maggioranza dei casi.

    Qualche volta avrete bisogno di andare oltre combinando, collegando oppure unendo le pubblicazioni. Vedremo come fare in un capitolo successivo!