Design Dokumente

Design Dokumente sind eine spezielle Art von CouchDB Dokument, die Code für Anwendungen enthalten. Weil die Anwendungen in der Datenbank laufen, ist das API stark strukturiert. In den vorangegangenen Kapiteln haben wir bereits JavaScript Views und andere Funktionen gesehen. In diesem Teil beschäftigen wir uns mit dem API für Funktionen und sprechen darüber, wie Funktionen in einem Design Dokument mit der Anwendung zusammenhängen.

Dieser Teil (Teil II: „Entwickeln mit CouchDB” sowie die Kapitel 5: Design Dokumente bis Kapitel 9: Transforming Views with List Functions) bilden die Grundlage für Teil III: „Eine Beispiel Anwendung”, in dem wir das Gelernte anwenden, um eine kleine Blog Anwendung zu schreiben und um genauer zu verstehen, wie in CouchDB Anwendungen entwickelt werden. Die Anwendung heißt Sofa und wir werden ein paarmal darauf hinweisen. Falls noch nicht klar ist, worauf wir hinauswollen, so ist das kein Anlass zur Sorge – in Teil III: „Eine Beispiel Anwendung” gehen wir ausführlich darauf ein.

Modellierung von Dokumenten

Nach unserer Erfahrung gibt es zwei Arten von Dokumenten. Die einen würden von einem Textverarbeitungsprogramm oder einer Anwendung welche Benutzerprofile speichert angelegt werden. Diese Daten denormalisiert man soweit es geht und versucht dabei soviele Daten in ein Dokument zu packen, dass man es in einem Request lesen und anzeigen kann.

Eine andere Art von Dokument sind sogenannte „virtuelle” Dokumente, die man mithilfe von Views erstellt, welche die benötigten Daten aus verschiedenen anderen Dokumenten zusammenstellen. Man könnte damit unterschiedliche Attribute eines Benutzerkontos in verschiedenen Dokumenten speichern, allerdings ist das nicht zu empfehlen. Virtuelle Dokumente sind hilfreich wenn es darum geht, die Arbeit von verschiedenen Autoren in einem separaten Dokument zusammenzuführen. Ein gutes Beispiel wäre ein Blog Post und die dazugehörigen Kommentare. Ein Artikel mit dem Titel „CouchDB Joins” von Christopher Lenz beschreibt diesen Ansatz genauer.

Die Idee von virtuellen Dokumenten kann man auch noch zu einer weiteren Art von Dokumenten weiterspinnen – dem Event Log. Solche Dokumente kann man verwenden, wenn man beispielsweise den Benutzereingaben nicht vertraut oder man einen asynchronen Job starten möchte. So wird die Benutzeraktion als Event gespeichert und muss zunächst nur oberflächlich geprüft werden, was Zeit spart. Erst wenn das Dokument wieder geladen wird, findet die eigentliche komplexe Prüfung sämtlicher Abhängigkeiten statt.

Man kann Dokumente als Zustandsmaschinen betrachten, bei denen eine Kombination aus Benutzereingaben und Hintergrundverarbeitung den Zustand des Dokuments verwalten. Für jeden Zustand benutzt man eine andere View und erhält nur Dokumente, die sich gerade in diesem Zustand befinden. Ändert man den Zustand des Dokuments wandert es in eine andere View.

Mit diesem Ansatz sollte CouchDB gut zum Logging geeignet sein – besonders, wenn man den batch=ok Parameter für bessere Performance mit angibt. Anschließend kann man Reduce Views gut dafür einsetzen, die durchschnittliche Antwortzeit oder besonders aktive Benutzer zu finden.

Der Query Server

Ein Query Server in CouchDB ist der Teil, der die Funktionen der Design Dokumente ausführt. Der Standard Query Server von CouchDB ist in JavaScript geschrieben, doch es gibt auch andere Query Server für fast jede andere Programmiersprache die man sich vorstellen kann. Eine neue Sprache zu unterstützen beschränkt sich darauf ein paar JSON Kommandos von einem einfachen zeilenbasierten Programm aus zu bearbeiten.

In diesem Abschnitt gehen wir noch einmal kurz auf die bereits besprochenen Funktionen wie MapReduce Views und Validierungsfunktionen ein und erklären dann Listentransformationen. Dann zeigen wir kurz die Funktionen, die wir in CouchDB für die kommenden Versionen geplant haben, wie beispielsweise Replikationsfilter, Update Handler für Daten, die nicht im JSON Format sind und einen Rewrite Handler, um die URLs lesbarer zu gestalten. Da CouchDB ein Open Source Projekt ist, können wir nicht sagen, wann eine bestimmte Funktion verfügbar sein wird. Allerdings hoffen wir, dass das was wir hier beschreiben verfügbar sein wird, wenn sie dies lesen. Im Folgenden werden wir es deutlich machen, wenn wir über Funktionen sprechen, die noch nicht im CouchDB Trunk verfügbar sind.

Anwendungen sind Dokumente

CouchDB funktioniert am besten, wenn es eine 1:1 Beziehung zwischen Anwendungen und Design Dokumenten gibt.

Ein Design Dokument ist ein CouchDB Dokument dessen id mit _design/ beginnt. Die Sofa Anwendung ist beispielsweise in einem Design Dokument mit der ID _design/sofa gespeichert (siehe auch Abbildung 1: „Anatomie eines Design Dokuments”). Ansonsten verhalten sich Design Dokumente genau wie jedes andere Dokument in CouchDB – sie werden mit den anderen Dokumenten repliziert und benutzen das rev Attribut um Schreibkonflikte zu erkennen.

Design Dokumente sind ganz normale JSON Dokumente allerdings mit einer ID, die mit _design/ beginnt.

CouchDB sucht Views und Funktionen in diesen Dokumenten. Statische HTML Seiten werden als Anhänge des Design Dokuments gespeichert. Views und Validierungsfunktionen dagegen werden nicht als Anhänge gespeichert. Sie sind vielmehr Teil des JSON Bodys des Design Dokuments.

Abbildung 1: Anatomie eines Design Dokuments

Die MapReduce Querys werden im views Attribut gespeichert. So findet Futon die MapReduce Querys, zeigt sie an und erlaubt sie zu bearbeiten. View Indizes werden pro Design Dokument gespeichert – abhängig von einem Fingerprint, der über den Text-Inhalt des Design Dokuments gebildet wird. Das bedeutet, dass wenn man einen Anhang ändert, löscht oder hinzufügt oder irgendein anderes Attribut ändert, welches nichts mit den Views zu tun hat, werden die Indizes nicht neu erzeugt. Ändern sich jedoch eine Map oder Reduce Funktion löscht CouchDB den Index der View und legt ihn neu an.

CouchDB kann die Antworten auch in anderen Formaten als JSON erstellen. Die show und list Attribute eines Design Dokuments enthalten Funktionen um JSON in HTML, XML oder andere Ausgabeformate zu wandeln. So kann CouchDB zum Beispiel direkt Atom Feeds erzeugen ohne weitere Software einsetzen zu müssen. Die show und list Funktionen verhalten sich ein wenig wie „Aktionen” in traditionellen Web-Frameworks. Abhängig vom Request führen sie bestimmte Funktionen aus und erzeugen eine Antwort. Sie unterscheiden sich allerdings von traditionellen Aktionen darin, dass sie keine Seiteneffekte haben dürfen. Damit sind sie zwar hauptsächlich auf GET Requests beschränkt, können aber auch gut von HTTP Proxys wie Varnish gecachet werden.

Da die gesamte Anwendungslogik in einem Dokument gespeichert ist, kann man mithilfe der Replikation Upgrades der Software durchführen. Damit kann eine einzelne Datenbank auch mehrere Anwendungen haben. Die Benutzerschnittstelle für den Redakteur einer Zeitung muss ganz anders aussehen, als das was der Leser zu Gesicht bekommt – auch wenn die Daten im Großen und Ganzen die gleichen sind. Beide Anwendungen können in der gleichen Datenbank jedoch in verschiedenen Design Dokumenten gespeichert werden.

Eine CouchDB Datenbank kann viele Design Dokumente enthalten, die zum Beispiel folgende IDs haben könnten:

_design/calendar
_design/contacts
_design/blog
_design/admin

Die vollständigen URLs, um sich die CouchDB Design Dokumente per GET als JSON anzeigen zu lassen, sind:

http://localhost:5984/mydb/_design/calendar
http://127.0.0.1:5984/mydb/_design/contacts
http://127.0.0.1:5984/mydb/_design/blog
http://127.0.0.1:5984/mydb/_design/admin

Design Dokumente bilden hier eine Ausnahme zu regulären Dokumenten, da sie die einzigen Dokumente sind, auf die man mit einem regulären Slash (/) zugreifen kann. Der Grund dafür ist, dass niemand gerne %2F in der Adressleiste seines Browsers sieht. Bei alle anderen Dokumenten muss der Slash entsprechend kodiert (escaped) werden. So muss die ID eines Dokuments mit dem Wert movies/jaws in der URL wie folgt angegeben werden: http://127.0.0.1:5984/mydb/movies%2Fjaws.

In der ersten Stufe haben wir die Beispielanwendung ohne die show und list Funktionen entwickelt, da man anhand der AJAX Abfragen gegen das JSON API besser sieht, wie CouchDB als Datenbank funktioniert. Die APIs, die in der ersten Stufe benutzt werden, sind die gleichen APIs die man benutzt, um Log Daten zu analysieren, Bestandslisten zu führen oder persistente Warteschlangen zu verwalten.

In der zweiten Stufe ändern wir die Blog Anwendung so, dass sie auch dann noch funktioniert, wenn JavaScript im Browser abgeschaltet ist. Zunächst bleiben wir jedoch bei den AJAX Querys, da diese besser zeigen, wie das JSON/HTTP API von CouchDB funktioniert. Da JSON eine Untermenge von JavaScript ist, sind die Verluste bei der Konvertierung gering, während sich der Browser mithilfe des XMLHttpRequest (XHR) Objekts um die Details des HTTP Requests kümmert.

CouchDB verwendet die validate_doc_update Funktion um zu verhindern, dass ungültige oder nicht autorisierte Änderungen an Dokumenten vorgenommen werden. Wir nutzen diese Funktion in der Beispielanwendung um sicherzustellen, dass Blog Posts nur von Benutzern erstellt werden können, die sich zuvor angemeldet haben. Validierungsfunktionen können in CouchDB ebenfalls keine Seiteneffekte haben. Mit ihnen kann man nicht nur Änderungen von Endbenutzern überprüfen und verhindern, sondern auch replizierte Dokumente von entfernten Knoten. Auf die Validierung gehen wir in Teil III: „Eine Beispiel Anwendung” noch genauer ein.

Die Bilder, sowie die JavaScript, CSS und HTML Dateien, die Sofa benötigt, werden im Attribut _attachments gespeichert. Interessanterweise werden bei den Anhängen immer nur ein Teil angezeigt anstatt des vollständigen Inhalts. Anhänge kann man allen Dokumenten von CouchDB hinzufügen, nicht nur Design Dokumenten. Insofern haben Anwendungen zum Führen von Bestandslisten genug Flexibilität, um alles Notwendige zu speichern. Wenn eine Anwendung bestimmte statische Ressourcen benötigt, so sollten sie als Anhang zum Design Dokument gespeichert werden. So kann ein neuer Benutzer sehr einfach die Anwendung in eine neue leere Datenbank installieren.

Die anderen Attribute eines Design Dokuments, die Abbildung 1: „Anatomie eines Design Dokuments” zeigt, werden vom Upload Prozess von CouchApp verwendet (siehe auch Kapitel 10: Standalone Anwendungen). Das Attribut signatures erlaubt es bei einer Aktualisierung Anhänge zu erkennen, die sich nicht geändert haben. Die Überprüfung erfolgt durch Vergleich eines Hash-Wertes über den Inhalt der Datei mit der Datenbank. Im lib Attribut werden weiterer JavaScript Code und JSON Daten gespeichert, die später in den View, Show und Validierungsfunktionen benutzt werden können. Wir gehen auf CouchApp im nächsten Kapitel ein.

Ein einfaches Design Dokument

Im folgenden Abschnitt zeigen wir fortgeschrittene Techniken um mit Design Dokumenten zu arbeiten, doch bevor wir so weit gehen, schauen wir zunächst auf ein sehr einfaches Design Dokument. In diesem Dokument ist nur eine einzelne View definiert, dennoch sollte es ausreichen, um zu sehen, wie Design Dokumente ins Gesamtbild passen.

Zunächst legen wir eine Textdatei mit folgendem oder ähnlichem Inhalt an und speichern sie unter dem Namen mydesign.json:

{
  "_id" : "_design/example",
  "views" : {
    "foo" : {
      "map" : "function(doc){ emit(doc._id, doc._rev)}"
    }
  }
}

Anschließend nutzen wir curl um die Datei in CouchDB mit einem PUT Request zu speichern. Vorher legen wir noch eine Datenbank an:

curl -X PUT http://127.0.0.1:5984/basic
curl -X PUT http://127.0.0.1:5984/basic/_design/example -data-binary @mydesign.json

Nach dem zweiten Request sollte eine Antwort wie die Folgende zu sehen sein:

{"ok":true,"id":"_design/example","rev":"1-230141dfa7e07c3dbfef0789bf11773a"}

Nun kann man die View, die wir gerade angelegt haben, abfragen. Doch bevor wir das tun, sollten wir zunächst ein paar Dokumente der Datenbank hinzufügen, sodass die Datenbank auch etwas zum Anzeigen hat. Mit dem folgenden Kommando legt man ein leeres Dokument an. Führen wir es ein paarmal aus:

curl -X POST http://127.0.0.1:5984/basic -d '{}'

Nun können wir die View abfragen:

curl http://127.0.0.1:5984/basic/_design/example/_view/foo

Das sollte eine Liste aller in der Datenbank gespeicherten Dokumente liefern – mit Ausnahme des Design Dokuments. Herzlichen Glückwunsch! Sie haben gerade ihr erstes Design Dokument angelegt.

Ein Blick in die Zukunft

Während wir dieses Buch schreiben, werden neue Funktionen für Design Dokumente zu CouchDB hinzugefügt. Dazu gehören die _update und die _filter Funktionen, auf die wir hier nicht im Detail eingehen werden. Filter Funktionen werden in Kapitel 20: Change Notifications behandelt. Stellen sie sich dazu einen Webservice vor, der einen XML Blob unter einer URL, die sie festlegen, mittels POST übergibt, sobald ein bestimmtes Ereignis eintritt. Die Instant Payment Notification von PayPal ist ein solcher Service. Mit dem _update Handler ist es möglich, diese Daten direkt per POST in CouchDB zu speichern, wo CouchDB das XML analysiert und als JSON Dokument speichert. Das gleiche gilt für CSV, Multi-Part Formulare oder jedes andere Format.

Die Vision, an der wir arbeiten, ist die eines Application Servers – allerdings einer, der in einem wichtigen Aspekt anders ist, als die anderen: Anstatt dem Anwendungsentwickler sämtliche Freiheiten zu geben (iterieren über eine Liste von Dokumenten IDs, Abfragen auf Basis der IDs durchführen und auf Basis der Ergebnisse weitere abhängige Abfragen durchführen), definieren wir „sichere Transformationen”, so wie view, show, list und update. Unter „sicher” verstehen wir, dass ihr Performance Verhalten bekannt ist und gut in die CouchDB Architektur passen.

Das Ziel ist einen Weg aufzuweisen, um Standalone Anwendungen zu bauen, die gleichzeitig gut von Suchmaschinen indiziert und von Screen Readern genutzt werden können. Deshalb auch die Fokussierung auf normales HTML. Auf JavaScript im Client kann man sich normalerweise verlassen – ausser wenn man es nicht kann. Durch HTML Ressourcen kann man mit CouchDB gut nach aussen sichtbare Webanwendungen bauen.

Bereits in Sichtweite ist ein Rewrite Handler und ein Datenbank Event Handler, da beide gut in die Anwendungsstruktur passen. Mit einem Rewrite Handler kann eine Anwendung ihren URL Namensraum selber verwalten, was die Integration in bestehende Systeme vereinfacht. Mit einem Event Handler lassen sich asynchrone Prozesse anstossen, wenn sich die Datenbank ändert. So könnte eine Aktualisierung eines Dokuments eine Validierung mehrerer Dokumente oder das Abarbeiten einer Message Queue auslösen.