In diesem Kapitel werden wir die einzelnen Funktionen von CouchDB kurz vorstellen. Dazu gehört auch Futon, die integrierte Administrationsanwendung. Wir werden ein erstes Dokument anlegen und die Views von CouchDB genauer untersuchen. Zunächst jedoch muß CouchDB installiert und konfiguriert werden. Die passende Anleitung für das jeweilige Betriebssystem sind in Anhang D - Installation von den Sourcen.
CouchDB hat ein kompaktes HTTP API (Application Programming Interface), das man gut mit dem Kommandozeilentool curl
bedienen kann. Neben curl gibt es noch viele andere Möglichkeiten mit CouchDB zu arbeiten, wie wir später noch sehen werden. Das Interessante an curl
ist, dass es volle Kontrolle über die reinen HTTP Requests erlaubt, denn man sieht genau was „unter der Haube“ passiert.
Prüfen wir, ob CouchDB noch läuft:
curl http://127.0.0.1:5984/
Damit wird ein einfacher HTTP GET
Request ohne Parameter zu CouchDB geschickt.
Die Antwort sollte ungefähr so aussehen:
{"couchdb":"Welcome","version":"0.10.1"}
Nicht besonders aufregend. CouchDB sagt „Hallo“ und teilt uns seine Versionsnummer mit.
Anschließend holen wir die Liste der verfügbaren Datenbanken:
curl -X GET http://127.0.0.1:5984/_all_dbs
Der Request unterscheidet sich vom vorherigen nur durch _all_dbs
am Ende.
Die Antwort sollte ungefähr so aussehen:
[]
Da wir noch keine Datenbank angelegt haben, antwortet CouchDB mit einer leeren Liste.
Ohne zusätzliche Parameter sendet curl
immer GET
Requests. Um POST
Requests zu verschicken, muss man curl
mit -X POST
aufrufen. Wir verwenden in der Regel die -X
Option auch bei GET
Requests, um die Terminal Historie besser nutzen zu können. Beim nächsten POST
Request muß dann nur die Methode geändert werden.
Der tatsächliche HTTP Request ist etwas komplexer als er hier dargestellt wird, weil curl
einige Details der Übersichtlichkeit halber weglässt. Um alle Details von Request und Response zu sehen, ruft man curl
mit der -v
Option auf, z.B. curl -vX GET
. Damit werden der Host, mit dem sich curl
verbindet sowie sämtliche Header angezeigt. Ideal zum Debuggen!
Legen wir nun eine Datenbank an:
curl -X PUT http://127.0.0.1:5984/baseball
CouchDB antwortet mit:
{"ok":true}
Anschließend taucht die neue Datenbank in der Liste der Datenbanken auf:
curl -X GET http://127.0.0.1:5984/_all_dbs
CouchDB antwortet mit:
["baseball"]
Jetzt ist ein guter Zeitpunkt auf JSON, die JavaScript Object Notation hinzuweisen. JSON ist die Sprache der Daten in CouchDB. Es ist ein schlankes Format auf Basis der JavaScript Notation. Da JSON direkt von JavaScript verstanden wird, ist der Web Browser ein idealer Client für CouchDB.
JSON kann man leicht erklären: Eckige Klammern ([]
) sind sortierte Listen und geschweifte Klammern ({}
) sind Key/Value Hashmaps. Die Schlüssel müssen Strings in doppelten Anführungszeichen ("
) sein. Die Werte können Strings, Zahlen, Bool'sche Werte, Listen oder andere Key/Value Hashmaps sein. Das war's eigentlich schon. Eine ausführlichere Beschreibung von JSON ist im Anhang E — JSON Einführung.
Legen wir noch eine Datenbank an:
curl -X PUT http://127.0.0.1:5984/baseball
CouchDB antwortet mit:
{"error":"file_exists","reason":"The database could not be created, the file already exists."}
Da es schon eine Datenbank mit diesem Namen gibt, antwortet CouchDB mit einer Fehlermeldung. Versuchen wir es mit einen anderen Namen:
curl -X PUT http://127.0.0.1:5984/plankton
CouchDB antwortet mit:
{"ok":true}
Die Liste der verfügbaren Datenbanken sollte nun beide Datenbanken anzeigen:
curl -X GET http://127.0.0.1:5984/_all_dbs
CouchDB antwortet mit:
["baseball", "plankton"]
Die zweite Datenbank brauchen wir nicht, also löschen wir sie:
curl -X DELETE http://127.0.0.1:5984/plankton
CouchDB antwortet mit:
{"ok":true}
Die Liste der Datenbanken ist nun wieder wie vorher:
curl -X GET http://127.0.0.1:5984/_all_dbs
CouchDB antwortet mit:
["baseball"]
Ziemlich einfach, oder? Wir überspringen an dieser Stelle die Arbeit mit Dokumenten und curl
, da der nächste Abschnitt einen anderen und wahrscheinlich einfacheren Weg zeigt, um mit CouchDB Dokumente zu verwalten. Wir sollten uns jedoch merken, dass jede Anwendung mit genau den gleichen Mitteln arbeitet wie in den vorangegangenen Beispielen: Ein HTTP Request mit GET
, PUT
, POST
oder DELETE
und ein URI.
Nachdem wir die nackte HTTP Schnittstelle von CouchDB gesehen haben, gehen wir einen Schritt weiter zu Futon, der integrierten Administrationsanwendung. Futon bietet vollen Zugang zu sämtlichen Funktionen von CouchDB und macht es einfach mit den komplexeren Features von CouchDB zu experementieren. Mit Futon kann man Datenbanken anlegen und löschen, Dokumente anzeigen und bearbeiten, MapReduce Views erzeugen und ausführen als auch die Replikation zwischen Datenbanken anstoßen.
Futon ist unter folgender URL zu erreichen:
http://127.0.0.1:5984/_utils/
Ab Version 0.9 oder später sollte die Oberfläche ungefähr wie in Abbildung 1: Der Futon Home Screen aussehen. Spätere Kapitel gehen dann mehr darauf ein, CouchDB mit Server Sprachen wir Ruby und Python zu nutzen. Deshalb ist das jetzt eine gute Gelegenheit zu zeigen, wie man eine dynamische Webanwendung ausschließlich mit CouchDB und seinem integrierten Webserver ausliefert. Das ist vielleicht auch etwas für ihre Anwendungen.
Das Erste, was man mit einer neuen CouchDB Installation machen sollte, ist die Test Suite auszuführen, um sicherzustellen, dass alles korrekt funktioniert. Liefern die Tests keine Fehler, kann ausgeschlossen werden, dass spätere Probleme an einem fehlerhaften Setup liegen. Zugleich ist ein Fehler in der Test Suite ein Warnzeichen, die Installation noch einmal genau zu überprüfen bevor man einen eventuell defekten Datenbankserver produktiv einsetzt. Das spart viele Kopfschmerzen, wenn die Dinge nicht so laufen, wie sie sollten.
Bei einigen Netzwerkkonfigurationen schlagen die Tests für die Replikation fehl, wenn man Futon über localhost
aufruft. Man kann das umgehen, indem man stattdessen die IP Adresse benutzt (http://127.0.0.1:5984/_utils/
).
Um die Test Suite auszuführen, klickt man rechts in der Futon Seitenleiste auf „Test Suite“ und anschließend oben auf „Run all“ um die Tests zu starten. Abbildung 2: Futon Test Suite in Aktion zeigt Futon während die Tests ausgeführt werden
Da die Test Suite vom Browser aus gestartet wird, testet sie nicht nur die Funktion von CouchDB, sondern auch die Kommunikation zwischen Browser und CouchDB. So erkennt man schlecht konfigurierte Proxies oder andere HTTP Middleware.
Falls die Test Suite eine ungewöhnlich hohe Anzahl von Fehlern produziert, sollten sie im Abschnitt Troubleshooting in Anhang D Installation von Source nachschlagen, wie man die Installation repariert.
Ist die Test Suite erfolgreich durchgelaufen, ist sichergestellt, das CouchDB korrekt arbeitet.
Eine Datenbank in Futon zu erstellen ist simpel. Auf der Übersichtsseite klickt man zunächst auf „Create Database“. Für die erste Datenbank wählen wir den Namen hello-world
. Mit einem Klick auf den „Create“ Button wird die Datenbank angelegt.
Anschließend zeigt Futon eine Liste aller Dokumente, die bei einer neuen Datenbank jedoch leer ist (Abbildung 3: Eine leere Datenbank in Futon). Mit einem Klick auf „New Document“ legen wir ein erstes Dokument an. Futon hat das _id
Feld bereits mit einer UUID vorbelegt. Lässt man es frei, wird CouchDB eine UUID für das Dokument wählen. Zum Speichern klickt man auf „Save Document“.
In den Beispielen ist es in Ordnung, wenn CouchDB die UUIDs für die Dokumente bestimmt. Für eigene Anwendungen empfehlen wir jedoch grundsätzlich die UUIDs selber zu wählen. Wenn man sich auf darauf verlässt, dass der Server die UUIDs wählt, kann es vorkommen, dass man Dokumente doppelt anlegt. Das geschieht wenn man einen zweiten POST Request abschickt weil der erste aus irgendeinem Grund fehlgeschlagen ist. In dieser Situation kann es passieren, das man zwei Dokumente erzeugt hat, weil CouchDB das Dokument angelegt haben kann ohne das die Bestätigung beim Absender eintrifft. So geht das erste Dokument verloren. Wählt man die UUIDs selber, kann man Dokumente nicht doppelt anlegen.
Futon zeigt anschließend das neue Dokument an, welches nur aus den beiden Attributen _id
und _rev
besteht. Um ein weiteres Feld hinzuzufügen, klickt man auf „Add Field“ und gibt dem Feld einen Namen. Durch einen Doppelklick auf den Wert kann man ihn ändern.
CouchDB lehnt ungültiges JSON als Wert ab. Versucht man es, erhält man einen Fehler. CouchDB Werte müssen ein gültiger JSON Ausdruck sein. Streng genommen sollte Futon den Wert world
als ungültig ablehnen, weil in JSON Strings in einfachen oder doppelten Anführungszeichen stehen müssen. Futon erkennt jedoch, dass wir einen String eingeben möchten und fügt die Anführungszeichen selber hinzu. Experementieren sie ein wenig mit anderen JSON Ausdrücken wie [1, 2, "c"]
oder {"foo":"bar"}
. Achten sie vor dem Speichern auf den Wert des _rev
Attributes und klicken dann auf „Save Document“. Das Ergebnis sollte so ähnlich aussehen, wie in Abbildung 4: Ein „Hello World“ Dokument in Futon.
Wenn sie gut aufgepasst haben, ist ihnen aufgefallen, dass sich der Wert des _rev
Attributs geändert hat. Wir werden auf die Bedeutung später noch genauer eingehen. Im Moment genügt es zu verstehen, dass das _rev
Attribut ein Sicherheitsmechanismus beim Speichern von Dokumenten ist. So lange ihre Anwendung die gleiche Version des Dokuments speichern möchte, die CouchDB als die aktuelle Version betrachtet, können sie das Dokument speichern.
Futon erlaubt auch das Dokument in JSON Notation auszugeben, was jenach Art der Daten kompakter und einfacher zu lesen sein kann. Um die JSON Repräsentation unseres „Hello World“ Dokuments zu sehen, klicken sie auf den „Source“ Reiter. Das Ergebnis sollte ungefähr so aussehen wie in Abbildung 5: Der JSON Source eines „Hello World“ Dokuments in Futon.
Relationale Datenbanken erlauben, dass man jederzeit Abfragen gegen die Datenbank ausführen kann, solange die Daten orderntlich strukturiert sind. Im Gegensatz dazu verwendet CouchDB vordefinierte map und reduce Funktionen in einer Form, die man unter dem Begriff MapReduce kennt. Diese Funktionen erlauben eine große Flexibilität, denn sie können mit Variationen in der Struktur der Dokumente umgehen. Sie erlauben es auch, die Indizes für verschiedene Dokumente unabhängig voneinander und parallel zu berechnen. Die Kombination einer map und einer reduce Funktion nennt man in CouchDB eine View.
MapReduce verwendet man anders als Abfragen in relationalen Datenbanken. Dort bestimmt eine Abfrage, welche Zeilen einer Tabelle Teil des Result Sets sind und welches der effizienteste Weg ist, dieses Result Set zu berechnen. Abfragen mit einer reduce Funktion dagegen arbeiten mit einfachen Bereichsabfragen auf Indizes, die zuvor mit map Funktionen erstellt wurden.
Die map Funktionen werden genau einmal für jedes Dokument aufgerufen und das Dokument ist das einzige Argument. Die Funktion kann das Dokument ignorieren oder eine oder mehrere Zeilen als Key/Value Paare zurückliefern (emit).
map Funktionen müssen in sich abgeschlossen sein und dürfen nur auf Daten innerhalb des Dokuments zugreifen. Diese Unabhängigkeit von anderen Dokumenten und globalen Variablen ermöglicht es CouchDB Views inkrementell und parallel zu erstellen.
Die Ergebnisse der Views von CouchDB werden als Zeilen gespeichert, die nach dem Schlüssel sortiert sind. Das ermöglicht einen sehr effizienten Zugriff, selbst wenn bei Tausenden oder Millionen von Zeilen. Schreibt man eine map Funktion in CouchDB, so ist das Ziel einen Index zu erzeugen, der alle relevanten Daten unter einem Schlüssel enthält.
Bevor wir ein Beispiel mit einer MapReduce View erstellen können, benötigen wir einige Daten. Dazu erzeugen wir drei Dokumente mit Preisen von Früchten aus verschiedenen Geschäften. Nutzen wir Futon um die Dokumente für die Preise von Äpfeln, Orangen und Bananen anzulegen. Die JSON Struktur sollte dabei wie folgt aussehen. Das Setzen der _id und _rev Felder überlassen wir dabei CouchDB.
{ "_id" : "bc2a41170621c326ec68382f846d5764", "_rev" : "2612672603", "item" : "apple", "prices" : { "Fresh Mart" : 1.59, "Price Max" : 5.99, "Apples Express" : 0.79 } }
Dieses Dokument sollte in Futon wie in Abbildung 6: Ein Beispiel Dokument in Futon mit Preisen für Äpfel aussehen.
Anschließend die Preise für Orangen:
{ "_id" : "bc2a41170621c326ec68382f846d5764", "_rev" : "2612672603", "item" : "orange", "prices" : { "Fresh Mart" : 1.99, "Price Max" : 3.19, "Citrus Circus" : 1.09 } }
Zum Schluss die Preise für Bananen:
{ "_id" : "bc2a41170621c326ec68382f846d5764", "_rev" : "2612672603", "item" : "banana", "prices" : { "Fresh Mart" : 1.99, "Price Max" : 0.79, "Banana Montana" : 4.22 } }
Stellen wir uns nun vor, wir müssten ein Buffet für einen Kunden organisieren, der sehr auf den Preis achtet. Um den niedrigsten Preis zu finden erstellen wir eine erste View, welche die Früchte nach Preis sortiert. Dazu klicken wir zunächst oben auf „hello-world“, um wieder auf die Übersicht der Datenbank zu gelangen. Anschließend wählen wir aus der „View“ Drop-Downliste „Temporary View…“ aus, um eine neue View anzulegen. Das Ergebnis sollte ungefähr so aussehen wie in Abbildung 7: Eine temporäre View in Futon.
Die dazugehörige map Funktion sieht wie folgt aus:
function(doc) { var store, price, value; if (doc.item && doc.prices) { for (store in doc.prices) { price = doc.prices[store]; value = [doc.item, store]; emit(price, value); } } }
Diese JavaScript Funktion führt CouchDB mit jedem der Dokumente aus, während es die View berechnet. Die reduce Funktion bleibt im Moment noch leer.
Wenn sie auf „Run“ klicken sollte das Ergebnis wie in Abbildung 8: Das Ergebnis einer View in Futon aussehen. Die verschiedenen Früchte sind dabei nach Preis sortiert. Allerdings kann die Sortierung noch verbessert werden. Besser wäre es die Ergebnisse zuerst nach Typ und dann nach Preis zu sortieren. Dazu muß der Schlüssel jedoch mehr als einen Wert enthalten. Da in CouchDB jedes JSON Objekt ein Schlüssel sein kann, schreibt man die Map Funktion so um, dass sie ein Array [item, price]
zurückliefert, sodass CouchDB die Daten entsprechend sortiert.
Ändern wir die map Funktion wie folgt:
function(doc) { var store, price, key; if (doc.item && doc.prices) { for (store in doc.prices) { price = doc.prices[store]; key = [doc.item, price]; emit(key, store); } } }
CouchDB verkraftet einige isolierte Fehler in einer map Funktion. Werden jedoch zuviele Fehler produziert — beispielsweise durch falsches JavaScript oder durch ein fehlendes Attribut — beendet CouchDB die Indizierung, um nicht noch mehr Rechenzeit zu verschwenden. Daher sollte die map Funktion prüfen, ob das Dokument alle benötigten Felder besitzt. In diesem Fall wird die map Funktion das erste „Hello World“ Dokument überspringen, weil es weder ein Attribut „item“ noch ein Attribut „price“ hat. Das Ergebnis sollte wie in Abbildung 9: Das Ergebnis einer View nach der Gruppierung von Typ und Preis aussehen.
Hat das Dokument einen Typ und einen oder mehrere Preise, iteriert die map Funktion über die Preise und gibt für jeden einzelnen ein Key/Value Paar aus (emit). Der Schlüssel ist ein Array aus Typ und Preis und ist die Basis für den sortierten Index. In diesem Fall ist der Wert nur der Name des Geschäfts in dem die Früchte zu dem angegebenen Preis gekauft werden können.
Die Zeilen von Views werden immer nach ihrem Schlüssel sortiert. In diesem Beispiel zuerst nach Typ und dann nach Preis. Diese Sortierung ist das Kernstück um sinnvolle Indizes mit CouchDB zu erstellen.
MapReduce kann eine Herausforderung darstellen, besonders wenn man jahrelange Erfahrung mit relationalen Datenbanken hat. Das Wichtigste an map Funktionen ist, dass sie es erlauben die Daten nach einem beliebigen Schlüssel zu sortieren kann und CouchDB darauf ausgerichtet ist, auf Schlüsselbereiche schnell und effizient zuzugreifen.
Mit Futon kann man die Replikation von zwei lokalen Datenbanken, zwischen einer lokalen und einer Remote-Datenbank und sogar zwischen zwei Remote-Datenbanken anstoßen. Das folgende Beispiel repliziert eine lokale Datenbank in eine andere lokale Datenbank. Das ist zugleich ein einfacher Weg, ein Backup einer Datenbank zu erstellen.
Zuerst wird eine leere Datenbank benötigt, welche das Ziel der Replikation sein wird. Dazu erzeugen wir in der Übersicht eine neue Datenbank hello-replication
. Anschließend wählt man rechts den „Replicator“, wählt hello-world
als Quelle und hello-replication
als Ziel. Ein Klick auf „Replicate“ startet die Replikation und das Ergebnis sollte wie in Abbildung 10: Datenbank Replikation in Futon aussehen.
Die Replikation von größeren Datenbanken kann natürlich viel länger dauern. Da wir die Replikation über den Browser angestoßen haben, darf das Fenster nicht geschlossen werden, bis die Replikation abgeschlossen ist. Alternativ kann die Replikation auch mit curl
oder einem anderen HTTP Client gestartet werden, der lang laufenden Anfragen verarbeiten kann. Schließt der Client die Verbindung bevor die Replikation abgeschlossen ist, muss sie erneut gestartet werden. Allerdings fängt CouchDB nicht von vorne an sondern macht da weiter, wo es vorher aufgehört hat.
Nachdem wir die meisten der Features von Futon gesehen haben, sind wir für die kommenden Kapitel gerüstet, in denen wir die Daten genauer untersuchen und eine Beispiel Anwendung bauen werden. Futon ist vollständig in JavaScript geschrieben und zeigt das es möglich ist, nur mit dem HTTP API von CouchDB und dem integrierten Web Server eine vollständige Web Anwendung zu entwickeln.
Doch als Nächstes werden wir das HTTP API von CouchDB genauer unter die Lupe nehmen. Ab auf die Couch zum Entspannen.