Publications and Subscriptions

Randfenster 4.5

Zu ...% übersetzt

In diesem Kapitel wirst du:

  • Lernen, wie Veröffentlichungen und Abonnements funktionieren.
  • Lernen, was das Package Autopublish bewirkt.
  • Einige weitere Beispiele von Veröffentlichungsmustern kennenlernen.
  • Veröffentlichungen und Abonnements (publications/subscriptions) sind eines der grundlegenden und wichtigen Konzepte in Meteor. Wenn du dich erst kurze Zeit mit Meteor beschäftigst, dann kann es sein, dass das Konzept nicht leicht zu verstehen ist.

    Das hat zu einer Reihe von Missverständnissen geführt, etwa dass Meteor unsicher sei oder dass Meteor-Apps nicht in der Lage seien, mit großen Datenmengen umzugehen.

    Ein Großteil der Verwirrung geht auf das Konto der „Magie“, die Meteor für uns ausführt. Wenn diese auch letztendlich sehr nützlich ist, kann sie verschleiern, was wirklich hinter den Kulissen vor sich geht. (Was in der Natur von Magie liegt.) Lass uns also die magischen Schichten abtragen um zu verstehen, was da vor sich geht.

    In grauer Vorzeit

    Lasst uns zunächst auf die gute alte Zeit von 2011 zurückblicken, als es Meteor noch nicht gab. Nehmen wir an, du entwickelst eine einfache Rails-App. Wenn ein User auf deine Seite kommt, dann sendet der Client (z. B. der Browser) eine Anfrage (request) an deine App, die sich auf dem Server befindet.

    Das erste, was die App unternimmt, ist, herauszufinden, was der User sehen möchte. Das könnte Seite 12 von einer Menge von Suchergebnissen sein, die Profildaten von Mary, Bobs 20 neueste Tweets und so weiter. Stell dir einen Angestellten in einem Buchladen vor, der durch die Regalreihen geht, um das Buch, nach dem du gefragt hast, herauszusuchen.

    Sobald die richtigen Daten ermittelt sind, ist die zweite Aufgabe der App, diese in hübsches, lesbares HTML umzuformen (oder JSON, wenn ein API im Spiel ist).

    Im Bild des Buchladens würde das gewünschte Buch eingepackt und in eine Tüte gesteckt. Das entspricht dem „View“-Teil des bekannten Entwurfsmusters Model-View-Controller.

    Schließlich nimmt die App den HTML-Code und schickt ihn an den Browser. Die App hat damit ihre Arbeit getan und nun, wo sie ihre virtuellen Hände wieder frei hat, kann sie sich mit einem Bier zurücklehnen während sie auf die nächste Anfrage wartet.

    Wie Meteor es macht

    Schauen wir uns nun an, was Meteor im Vergleich dazu so besonders macht. Wie wir gesehen haben, besitzt Meteor im Gegensatz zur Rails-App, die nur auf dem Server existiert, auch einen Anteil auf dem Client (dem Browser). Das ist die grundlegende Innovation, die Meteor mitbringt.

    Eine Untermenge der Datenbank zum Client senden.
    Eine Untermenge der Datenbank zum Client senden.

    Das ist wie der Angestellte, der dir nicht nur das richtige Buch heraussucht, sondern dich auch nach Hause begleitet um es dir abends vorzulesen (zugegeben, das klingt etwas gruselig).

    Diese Architektur lässt Meteor viele erstaunliche Dinge tun, hauptsächlich das, was Meteor database everywhere nennt. Vereinfacht gesagt nimmt Meteor einen Teil der Datenbank und kopiert ihn auf den Client.

    Daraus folgt zweierlei: erstens überträgt eine Meteor-App anstatt HTML-Code die eigentlichen, unformatierten Daten zum Client und überlässt es diesem, wie damit umzugehen ist (data on the wire). Zweitens hast du unmittelbaren Zugriff auf diese Daten, ohne erst auf eine Server-Antwort zu warten (latency compensation).

    Veröffentlichen

    Die Datenbank einer App kann zehntausende von Dokumenten enthalten, von denen einige auch privater oder vertraulicher Natur sein mögen. Deshalb ist es offensichtlich, dass wir nicht einfach den gesamten Datenbankinhalt auf den Client spiegeln – aus Gründen der Sicherheit und der Stabilität.

    Wir brauchen daher eine Methode, wie wir Meteor mitteilen, welche Untermenge des Datenbestandes zum Client zu schicken ist. Das erreichen wir duch eine Veröffentlichung.

    Gehen wir noch einmal zu Microscope zurück. Hier sind alle Beiträge unserer App in der Datenbank gespeichert:

    Alle Beiträge in der Datenbank.
    Alle Beiträge in der Datenbank.

    Auch wenn diese Möglichkeit, wie wir zugeben, in Microscope nicht existiert, stellen wir uns einmal vor, dass einige unserer Beiträge wegen darin verwendeter Schimpfworte markiert worden sind. Auch wenn wir sie in der Datenbank behalten wollen, sollten die für die User nicht verfügbar sein, also nicht zum Client gesendet werden.

    Unsere erste Aufgabe wird sein, Meteor klarzumachen, was wir zum Client senden wollen. Wir sagen Meteor, nur nicht markierte Beiträge zu veröffentlichen.

    Markierte Beiträge ausschließen.
    Markierte Beiträge ausschließen.

    Hier ist der entsprechende Code, der auf dem Server sitzen würde:

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

    Damit ist sichergestellt, dass ein Client auf keinen Fall auf einen markierten Beitrag zugreifen kann. Genau so wird eine Meteor-App sicher gemacht: Stelle sicher, dass nur die Daten veröffentlicht werden, der der Client auch sehen soll.

    DDP

    Im Grunde ist das System, das hinter dem Veröffentlichen und Abonnieren (publication/subscription) steht, wie ein Trichter, durch den von einer serverseitigen Collection zu einer clientseitigen Collection transportiert werden.

    Das Protokoll, mit dem dieser Trichter arbeitet heißt DDP. Das steht für Distributed Data Protocol, auf deutsch etwa Protokoll für verteilte Daten. Um mehr darüber zu erfahren, schaue dir diesen Vortrag von Matt DeBergalis auf der Real-time-Konferenz an oder diesen Screencast von Chris Mather, die das Konzept von DDP näher beleuchten.

    Abonnieren

    Selbst wenn wir jeden nicht markierten Beitrag auf dem Client verfügbar machen wollten, können wir nicht einfach tausende Beiträge auf einmal versenden. Wir brauchen eine Methode, mittels derer der Client bestimmen kann welche Teilmenge der Daten in einem bestimmten Augenblick benötigt wird. Genau dafür gibt es das Abonnieren.

    Jegliche abonnierte (subscribed) Daten werden auf den Client gespiegelt. Das ist Minimongo zu verdanken, Meteors Clientseitige Implementierung der MongoDB.

    Wir schauen uns beispielsweise gerade die Profilseite von Bob Smith an und da wollen wir nur seine Beiträge sehen.

    Abonnieren von Bobs Beiträgen spiegelt diese auf den Client.
    Abonnieren von Bobs Beiträgen spiegelt diese auf den Client.

    Zunächst würden wir unsere Veröffentlichung (publication) um einen Parameter erweitern:

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

    Und anschließend würden wir diesen Parameter benutzen, wenn wir die Veröffentlichung im clientseitigen Code abonnieren:

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

    Auf diese Weise wird eine Meteor-App auf der Clientseite skalierbar: anstatt alle verfügbaren Daten zu abonnieren, wählen wir die Teile aus, die wir gerade brauchen. So wird vermieden, dass der Speicher des Browsers überlastet wird, egal wie groß die serverseitige Datenbank ist.

    Finden

    Jetzt ist es so, dass Bobs Beiträge über mehrere Kategoien verteilt sind (z. B.: „JavaScript“, „Ruby“ und „Python“). Angenommen, wir wollen jetzt zwar alle Beiträge von Bob im Speicher haben aber nur die der Kategorie „JavaScript“ anzeigen. Hier kommt das „Finden“ ins Spiel.

    Eine Teilmenge von Dokumenten vom Client aus selektieren.
    Eine Teilmenge von Dokumenten vom Client aus selektieren.

    Genau wie auf dem Server benutzen wir die Funktion Posts.find(), um eine Teilmenge unserer Daten zu selektieren.

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

    Jetzt haben wir einen Begriff davon, welche Rolle Veröffentlichungen und Abonnements haben. Zeit, tiefer zu graben und einige häufig benutzte Implementierungsmuster anzusehen.

    Autopublish

    Wenn du ein neues Meteor-Projekt anslegst (mittels meteor create), dann ist das Package autopublish automatisch mit installiert. Wozu ist es gut? Lass es uns einmal ansehen.

    Die Zielsetzung von autopublish ist, den Start in die Programmierung einer Meteor-App sehr einfach zu gestalten. Dazu werden einfach alle Daten vom Server auf den Client gespiegelt. autopublish übernimmt also das Veröffentlichen und Abonnieren für dich.

    Autopublish
    Autopublish

    Wie geht das? Angenommen, du hast eine Collection namens 'posts' auf dem Server. Dann wird autopublish automatisch jeden Eintrag den es in der Mongo-Collection 'posts' findet, an eine Collection 'posts' auf dem Client senden, sofern diese existiert.

    Wenn du also autopublish benutzt, dann brauchst du dir über das Veröffentlichen keine Gedanken machen. Daten sind überall vorhanden und die Dinge liegen sehr einfach. Natürlich gibt es die offensichtlichen Probleme mit einer vollständigen Kopie der Datenbank auf dem Rechner jedes einzelnen Benutzers.

    Deshalb ist autopublish nur während des Anfangs der Entwicklung sinnvoll, solange du dir noch keine Gedanken über ein Veröffentlichungskonzept gemacht hast.

    Vollständige Collections veröffentlichen

    Sobald du das Package autopublish aus deinem Projekt entfernt hast, stellst du schnell fest, dass alle Daten vom Client verschwunden sind. Eine einfache Methode, sie zurückzubekommen ist, den Mechanismus von autopublish zu kopieren und einfach die ganze Collection zu veröffentlichen. Zum Beispiel:

    Meteor.publish('allPosts', function(){
      return Posts.find();
    });
    
    Eine ganze Collection veröffentlichen
    Eine ganze Collection veröffentlichen

    Wir veröffentlichen zwar immer noch vollständige Collections aber immerhin haben wir die Kontrolle darüber, welche wir veröffentlichen und welche nicht. In diesem Fall veröffentlichen wir die Collection Posts aber nicht Comments.

    Teile einer Collections veröffentlichen

    Die nächste Ebene von Kontrolle ist es, nur Teile einer Collection zu veröffentlichen. Beispielsweise die Beiträge eines bestimmten Autors:

    Meteor.publish('somePosts', function(){
      return Posts.find({'author':'Tom'});
    });
    
    Teile einer Collection veröffentlichen
    Teile einer Collection veröffentlichen

    Hinter den Kulissen

    Wenn du die Meteor-Dokumentation zum Veröffentlichen gelesen hast, dann warst du vermutlich überwältigt von dem Gerede über die verwendung von added() und ready(), um Attribute von Datensätzen auf dem Client zu setzen und davon, das mit den Meteor-Apps die du bislang gesehen hast in Einklang zu bringen, bei denen du diese Methoden nicht angewandt findest.

    Das liegt daran, dass Meteor eine wichtige Komfort-Funktion bereitstellt: die Methode _publishCursor(). Die hast du auch noch nie gesehen? Vielleicht nicht direkt, aber wenn du in einer Veröffentlichungsfunktion einen Cursor zurückgibst (z.B. Posts.find({'author':'Tom'})), dann ist das genau das, was Meteor intern verwendet.

    Wenn Meteor erkennt, dass die Veröffentlichung somePosts einen Cursor zurückgegeben hat, dann ruft es die Methode _publishCursor(), um – richtig geraten – diesen Cursor automatisch zu veröffentlichen.

    Hier ist, was _publishCursor() tut:

    • Prüfen des Namens der serverseitigen Collection
    • Holen aller passenden Dokumente aus dem Cursor und senden dieser an die gleichnamige clientseitige Collection. (Mittels .added())
    • Wann immer ein Dokument dazukommt, entfernt wird oder verändert: Senden dieser Veränderungen an die clientseitige Collection. (Mittels .observe() am Cursor bzw. .added(), .changed() und removed())

    In dem Beispiel von oben stellen wir damit sicher, dass für den Benutzer nur die Beiträge, die ihn im Moment interessieren, nämlich die von Tom verfassten, in seinem clientseitigen Cache verfügbar sind.

    Einzelne Properties veröffentlichen

    Wir haben eben gesehen, wie wir nur einige der Beiträge veröffentlichen können. Wir können die Daten aber auch noch feiner zerteilen. Schauen wir uns an, wie wir lediglich bestimmte Properties veröffentlichen.

    Wie zuvor, benutzen wir find(), um einen Cursor zurückzugeben. Diesmal aber schließen wir bestimmte Felder aus:

    Meteor.publish('allPosts', function(){
      return Posts.find({}, {fields: {
        date: false
      }});
    });
    
    Einzelne Properties veröffentlichen
    Einzelne Properties veröffentlichen

    Selbstvertändlich können wir beide Techniken kombinieren. Wenn wir z.B. alle Beträge von Tom aber nicht deren Datum veröffentlichen wollen, können wir folgendermaßen schreiben:

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

    Zusammenfassung

    Wir haben gesehen, wie wir von autopublish, dem Veröffentlichen aller Properties aller Dokumente aus allen Collections zum Veröffenlichen mancher Properties mancher Dokumente aus manchen Collektions kommen.

    Das sind die Basisfunktionen dessen, was du mit Veröffentlichungen bei Meteor machen kannst und diese einfachen Techniken sollten für die allermeisten Anwendungsfälle ausreichen.

    Manchmal allerdings musst du darüber hinausgehen und Veröffentlichungen miteinander kombinieren, sie verbinden oder zusammenführen. Das werden wir in einem späteren Kapitel behandeln.