Das Core API

In diesem Kaptiel nehmen wir CouchDB genau unter die Lupe. Wir gehen richtig in die Details und zeigen knifflige Sachen. Wir erklären die Best Practices und helfen die üblichen Stolperfallen zu umgehen.

Zu Beginn gehen wir noch einmal die Grundoperationen durch, die wir bereits im letzten Kapitel vorgestellt haben und schauen anschließend unter die Haube. Wir zeigen auch, was Futon hinter der Benutzerschnittstelle machen muss, um die vielen Funktionen zu haben, die wir zuvor gesehen haben.

Dieses Kapitel ist zugleich eine Einführung in das Core API von CouchDB und eine Referenz. Wenn Sie sich nicht mehr erinnern können, wie ein bestimmter Request funktioniert oder warum bestimmte Parameter benötigt werden, können Sie jederzeit hierhin zurückkehren und nachschlagen. Von allen Kapiteln nutzen wir dieses wahrscheinlich am häufigsten.

Während wir das API genauer erklären, müssen wir manchmal etwas weiter ausholen, um die Motivation hinter einem Request zu erklären. Dabei können wir gleich erklären, warum CouchDB so funktioniert wie es funktioniert.

Das API kann man in folgende Bereiche aufteilen, die wir nacheinander besprechen werden:

Server

Der erste Request ist simpel. Man benutzt ihn um zu sehen ob CouchDB überhaupt läuft. Setzt eine externe Bibliothek eine bestimmte Version voraus, kann sie mit diesem Request prüfen, ob diese Bedingung erfüllt ist. Wir nutzen wieder curl um mit CouchDB zu sprechen:

curl http://127.0.0.1:5984/

Voller Erwartung antwortet CouchDB mit:

{"couchdb":"Welcome","version":"0.10.1"}

Zurück kommt ein JSON String, der einen Willkommensgruß und die Version von CouchDB enthält.

Damit kann man zwar nicht viel anfangen, allerdings zeigt es, wie sich CouchDB verhält. Man schickt einen HTTP Request und bekommt einen JSON String als HTTP Response.

Datenbanken

Machen wir etwas Sinnvolleres: Legen wir eine Datenbank an. Wenn man es genau nimmt, ist CouchDB ein Datenbank Management System (DBMS), weil es mehrere Datenbanken enthalten kann. Eine Datenbank ist ein Behälter, der „zusammenhängende Daten“ enthält. Wir werden später sehen was das genau bedeutet. Meist verschwimmen die Begriffe etwas und „die Datenbank“ sind dann zugleich das DBMS und die Datenbank darin. Wir nehmen es auch nicht so genau, denn aus dem Kontext geht meist hervor, ob CouchDB als Ganzes oder eine Datenbank darin gemeint ist.

Legen wir die Datenbank nun an – zum Beispiel um unsere Lieblingsalben zu speichern. Sinnigerweise nennen wir die Datenbank albums. Datenbanken werden in CouchDB mit einem PUT Request angelegt. Deshalb nehmen wir bei curl die -X Option. Anderfalls würde curl einen GET Request schicken.

curl -X PUT http://127.0.0.1:5984/albums

CouchDB antwortet mit:

{"ok":true}

Das war's. Sie haben eine Datenbank angelegt und CouchDB sagt, das alles funktioniert hat. Was passiert, wenn man versucht eine Datenbank anzulegen, die bereits existiert sehen wir, wenn wir versuchen, die gleiche Datenbank nochmal anzulegen:

curl -X PUT http://127.0.0.1:5984/albums

CouchDB antwortet mit:

{"error":"file_exists","reason":"The database could not be created, the file already exists."}

CouchDB antwortet mit einem Fehler, was sehr praktisch ist. Zugleich lernen wir wieder ein wenig mehr darüber, wie CouchDB arbeitet. CouchDB speichert jede Datenbank in einer eigenen Datei. Ganz einfach. Daraus ergeben sich einige Konsequenzen, auf die wir jedoch erst später eingehen. Schauen wir zunächst auf das zugrundeliegende Speichersystem in Anhang F - Die Macht der B-Trees.

Legen wir noch eine Datenbank an – diesmal mit der -v Option (für „verbose“) von curl. Mit dieser Option zeigt curl nicht nur die wesentlichen Teile – den HTTP Response Body – sondern alle Bestandteile des Requests an.

curl -vX PUT http://127.0.0.1:5984/albums-backup

curl zeigt:

* About to connect() to 127.0.0.1 port 5984 (#0)
*   Trying 127.0.0.1... connected
* Connected to 127.0.0.1 (127.0.0.1) port 5984 (#0)
> PUT /albums-backup HTTP/1.1
> User-Agent: curl/7.16.3 (powerpc-apple-darwin9.0) libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
> Host: 127.0.0.1:5984
> Accept: */*
>
< HTTP/1.1 201 Created
< Server: CouchDB/0.9.0 (Erlang OTP/R12B)
< Date: Sun, 05 Jul 2009 22:48:28 GMT
< Content-Type: text/plain;charset=utf-8
< Content-Length: 12
< Cache-Control: must-revalidate
<
{"ok":true}
* Connection #0 to host 127.0.0.1 left intact
* Closing connection #0

Wupps, das ist doch einiges mehr als ohne -v. Gehen wir der Reihe nach durch die einzelnen Zeilen, um zu verstehen, was genau passiert ist und was wichtig ist. Wenn man diese Ausgabe ein paarmal gesehen hat, weiß man worauf man achten muss und sieht die wichtigen Teile sofort.

* About to connect() to 127.0.0.1 port 5984 (#0)

Damit sagt curl uns, dass es eine TCP Verbindung zu dem CouchDB Server aufbauen wird, den wir in dem Request URL angegeben haben. Das ist meist nicht besonders wichtig, ausser man sucht gerade einen Fehler im Netzwerk.

*   Trying 127.0.0.1... connected
* Connected to 127.0.0.1 (127.0.0.1) port 5984 (#0)

curl sagt uns mit diesen Zeilen, dass es sich erfolgreich mit CouchDB verbunden hat. Wieder nicht besonders wichtig, ausser man kämpft mit Netzwerkfehlern.

Die folgenden Zeilen beginnen jeweils mit den Zeichen > und <. Steht am Anfang ein > bedeutet das, dass diese Zeile exakt so an CouchDB geschickt wurde (ohne das >). Ein < am Anfang bedeutet, dass diese Zeile genau so von CouchDB zurück an curl geschickt wurde.

> PUT /albums-backup HTTP/1.1

Damit wird der HTTP Request eingeleitet. Die Methode ist PUT, die URI ist /albums-backup und die HTTP Version ist HTTP/1.1. Es gibt auch ein HTTP/1.0 welches in manchen Fällen etwas einfacher ist, doch im allgemeinen sollte man HTTP/1.1 verwenden.

Anschließend folgen eine Menge von Request Headern, die zusätzliche Informationen über den Request enthalten.

> User-Agent: curl/7.16.3 (powerpc-apple-darwin9.0) libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3

Der User-Agent Header teilt CouchDB mit, welche Software den Request gerade durchführt. Für uns ist das nichts Neues: es ist curl. Dieser Header wird oft in Web Anwendungen benutzt, um Fehler mit bestimmten HTTP Clients (z.B. Browsern) zu umgehen. Der Server kann dann eine auf den Client zugeschnittene Antwort schicken. Zudem kann man an dem Header erkennen, auf welcher Plattform der Benutzer ist, was für statistische Zwecke ganz hilfreich sein kann. Für CouchDB dagegen ist der User-Agent Header irrelevant.

> Host: 127.0.0.1:5984

Der Host Header ist Pflicht in HTTP 1.1. Er sagt dem Server, welcher Hostname in der URL stand.

> Accept: */*

Der Accept Header sagt CouchDB, dass curl jeden Medientyp als Antwort akzeptiert. Wir werden gleich noch sehen, wie man das nutzen kann.

>

Eine leere Zeile zeigt das Ende der Liste der Request Header an und markiert den Beginn der Daten, die wir an den Server schicken. In dem Fall schicken wir keine Daten, insofern ist der Rest der Ausgabe die HTTP Response.

< HTTP/1.1 201 Created

Die erste Zeile der Antwort von CouchDB enthält die HTTP Version (damit teilt uns der Server seine höchste HTTP Version mit, die er versteht), einen HTTP Status Code und eine Status Meldung. Unterschiedliche Requests liefern unterschiedliche Status Codes zurück. Es gibt eine ganze Menge an vordefinierten Status Codes, welche dem Client (in unserem Fall curl) sagen, welchen Effekt der Request auf den Server hatte. Ist jedoch ein Fehler aufgetreten, so enthält der Status Code den Typ des Fehlers. RFC 2616 (die HTTP Spezifikation) beschreibt genau, welches Verhalten welchen Status Code auslösen sollte. CouchDB hält sich genau an den RFC.

Der Status Code 201 Created teilt dem Client mit, dass die Ressource, gegen die der Request gemacht wurde (/albums-backup), erfolgreich angelegt wurde. Das ist das keine große Überraschung, doch wir haben vorher bereits gesehen, das CouchDB bei dem gleichen Request auch einen Fehler melden kann, wenn die Datenbank bereits existiert. Dabei könnte auch ein anderer Status Code zurückgeliefert worden sein. Zum Beispiel sind alle Status Codes größer oder gleich 400 Fehlercodes. Eine einfache Fehlerprüfung kann sich das zunutze mache, indem man auf Status Code >= 400 prüft.

< Server: CouchDB/0.10.1 (Erlang OTP/R13B)

Der Server Header eignet sich gut zur Diagnose. Er enthält die CouchDB Version und die Version des zugrundeliegenden Erlang Systems. Meist kann man diesen Header ignorieren, doch es ist gut zu wissen, dass er da ist, falls man ihn braucht.

< Date: Sun, 05 Jul 2009 22:48:28 GMT

In dem Date Header teilt uns CouchDB die Uhrzeit auf dem Server mit. Da die Zeit auf dem Client und Server nicht unbedingt synchronisiert sind, ist dieser Header eine reine Information. Unter keinen Umständen sollte man sich in einer Anwendung auf einen Wert dieses Headers verlassen.

< Content-Type: text/plain;charset=utf-8

Der Content-Type Header zeigt an, welche Art von Daten der HTTP Response Body enthält und wie diese codiert sind. Die Art der Daten steht im MIME Type und die Codierung im charset Parameter. Wir wissen bereits, das CouchDB JSON Strings zurückliefert. Der passende Content-Type Header wäre also application/json. Warum schickt CouchDB dann text/plain? An dieser Stelle ist Pragmatismus wichtiger als Purismus. Schickt man application/json als Content-Type Header, so wird jeder Browser versuchen den JSON String als Datei zu speichern anstatt ihn einfach anzuzeigen. Da es jedoch sehr praktisch ist CouchDB von einem Webbrowser aus zu testen, schickt CouchDB als Content Type text/plain, so dass alle Browser den JSON String einfach anzeigen.

Es gibt Erweiterungen für Webbrowser, mit denen der Browser JSON korrekt erkennt und entsprechend behandelt. Allerdings werden diese standardmäßig nicht installiert.

Erinnern Sie sich noch an den Accept Header im HTTP Request und wie er zu */* gesetzt wurde, um damit zu zeigen, dass der Client jeden MIME Type akzeptiert? Schickt ein Client im Request Accept: application/json, so weiß CouchDB, dass der Client korrekt mit JSON Strings und dem Content-Type: application/json Header umgehen kann und wird diesen verwenden anstelle von text/plain.

< Content-Length: 12

Der Content-Length Header sagt lediglich aus wievielen Bytes der Response Body besteht.

< Cache-Control: must-revalidate

Dieser Cache-Control Header signalisiert, dass unser Client und jeder Proxy Server zwischen dem Client und CouchDB die Antwort nicht cachen darf.

<

Diese leere Zeile zeigt uns, dass keine weiteren Response Header folgen und dass der Rest der Response Body ist.

{"ok":true}

Das haben wir schon mal gesehen.

* Connection #0 to host 127.0.0.1 left intact
* Closing connection #0

Mit den letzen beiden Zeilen zeigt curl an, dass es die TCP Verbindung noch einen Moment offen gehalten hat, um sie dann nach Erhalt des vollständigen Response Bodys zu schließen.

Wir werden im ganzen Buch immer mal wieder Requests mit der -v Option zeigen, aber bestimmte Header, die wir hier gezeigt haben, weglassen und nur die wichtigen für den jeweiligen Request anzeigen.

Datenbanken anzulegen ist schön und gut, aber wie löschen wir eine? Ganz einfach – Man ändert einfach die HTTP Methode:

> curl -vX DELETE http://127.0.0.1:5984/albums-backup

Damit löscht man eine CouchDB Datenbank. Der Request wird die Datei, in der die Datenbank gespeichert ist, sofort löschen. Es gibt keine „Sind Sie sicher?“ Frage oder eine Papierkorb-Logik wenn Sie eine Datenbank löschen. Das liegt in Ihrer Verantwortung und Ihre Daten werden umgehend – und wenn Sie kein Backup haben – ohne eine Chance sie wieder zurück zu holen gelöscht.

In diesem Abschnitt sind wir tief in HTTP eingestiegen und haben damit die Grundlage geschaffen um den Rest des Core APIs von CouchDB zu erklären. Nächste Haltestelle: Dokumente.

Dokumente

Dokumente sind die zentrale Datenstruktur von CouchDB und die Idee dahinter ist die von realen Dokumenten auf Papier, wie eine Rechnung, ein Rezept oder eine Visitenkarte. Wir wissen bereits, dass CouchDB Dokumente im JSON Format speichert. Schauen wir wie die Speicherung auf der untersten Ebene funktioniert.

In CouchDB hat jedes Dokument eine ID, die innerhalb der jeweiligen Datenbank eindeutig ist. Sie können jeden beliebigen String als ID nehmen, wir empfehlen jedoch UUIDs (auch GUIDs) – Universally (bzw. Globally) Unique Identifiers. UUIDs sind Zufallszahlen, die so eine geringe Kollisionswahrscheinlichkeit haben, dass man Tausende UUIDs pro Minute für Millionen von Jahren erzeugen kann, ohne jemals eine doppelt anzulegen. Damit kann man sicherzustellen, dass zwei Menschen, die unabhängig voneinander arbeiten, nicht verschiedene Dokumente mit der gleichen ID anlegen können. Doch warum sollte Sie es überhaupt kümmern was andere Leute tun? Zum einen könnten Sie selber diese andere Person sein – zu einem späteren Zeitpunkt oder an einem anderen Computer. Zum anderen kann man mithilfe der Replikation Dokumente mit anderen teilen und mit UUIDs stellt man sicher, dass alles funktioniert. Wir gehen später noch einmal genauer darauf ein. Zuerst legen wir ein paar Dokumente an.

curl -X PUT http://127.0.0.1:5984/albums/6e1295ed6c29495e54cc05947f18c8af -d '{"title":"There is Nothing Left to Lose","artist":"Foo Fighters"}'

CouchDB antwortet mit:

{"ok":true,"id":"6e1295ed6c29495e54cc05947f18c8af","rev":"1-2902191555"}

Das curl Kommando sieht komplex aus, weshalb wir es Schritt für Schritt durchgehen. Durch -X PUT wird curl angewiesen, einen PUT Request zu machen. Anschließend folgt die URL, welche den Hostnamen oder die IP Adresse und den Port von CouchDB enthält. Die URI /albums/6e1295ed6c29495e54cc05947f18c8af beschreibt die Position des Dokuments innerhalb der albums Datenbank. Die wilde Folge von Zahlen und Buchstaben ist eine UUID, welche die ID des Dokuments ist. Zum Schluß übegeben wir mit dem -d Parameter den Body des PUT Requests. Der Body ist ein String, der eine einfache JSON Struktur inklusive der Attribute title und artist mit ihren jeweiligen Werten enthält.

Wenn Sie gerade keine UUID zur Hand haben kann CouchDB eine für Sie erzeugen. Das ist genau das was wir gerade gemacht haben ohne explizit darauf hinzuweisen. Um eine UUID von CouchDB erzeugen zu lassen schickt man einen GET Request an /_uuids:

curl -X GET http://127.0.0.1:5984/_uuids

CouchDB antwortet mit:

{"uuids":["6e1295ed6c29495e54cc05947f18c8af"]}

Voilá, eine UUID. Brauchen Sie mehr als eine, so können Sie zum Beispiel ?count=10 mit angeben, um 10 UUIDs zu erzeugen.

Um sicher zu gehen das CouchDB das Dokument auch wirklich gespeichert hat, versuchen wir es mit einem GET Request wieder zu laden:

curl -X GET http://127.0.0.1:5984/albums/6e1295ed6c29495e54cc05947f18c8af

Wir hoffen, dass Sie ein Muster erkennen. Alles in CouchDB hat eine Adresse, eine sogenannte URI und man benutzt verschiedene HTTP Methoden um auf diesen URIs Operationen auszuführen.

CouchDB antwortet mit:

{"_id":"6e1295ed6c29495e54cc05947f18c8af","_rev":"1-2902191555","title":"There is Nothing Left to Lose","artist":"Foo Fighters"}

Das sieht wie das Dokument aus, das wir zuvor gespeichert haben, allerdings mit zwei zusätzlichen Feldern, die CouchDB zu unserer JSON Struktur hinzugefügt hat. Das erste Feld ist _id, welches die UUID des Dokuments enthält. Das CouchDB Dokumente ihre ID als eigenes Attribut enthalten ist sehr praktisch.

Das zweite Feld ist _rev. Es bedeutet Revision oder auf Deutsch „Version”.

Versionen

Wenn man in CouchDB ein Dokument ändern möchte, so schickt man keinen Request um ein bestimmtes Feld in einem Dokument zu finden und dessen Wert zu ändern. Stattdessen lädt man das komplette Dokument, ändert die JSON Struktur (oder das Objekt, jenachdem in welcher Umgebung man gerade arbeitet) und speichert das Dokument als neue Version wieder in CouchDB. Jede Version hat einen eigenen neuen Wert für das _rev Attribut.

Um ein Dokument zu aktualisieren oder zu löschen muss man das _rev Attribut mit übergeben. Damit stellt CouchDB sicher, dass sich das Dokument in der Zwischenzeit nicht geändert hat. Stimmen die Versionsnummern nicht überein, wird CouchDB die Änderung oder das Löschen des Dokuments ablehnen. Wenn CouchDB die Änderung des Dokuments akzeptiert, dann weist es ihm eine neue Versionsnummer zu. Kurz gesagt, wer zuerst speichert gewinnt. Schauen wir was passiert, wenn wir kein _rev Attribut übergeben (was das Gleiche ist wie ein veralteter Wert):

curl -X PUT http://127.0.0.1:5984/albums/6e1295ed6c29495e54cc05947f18c8af -d '{"title":"There is Nothing Left to Lose","artist":"Foo Fighters","year":"1997"}'

CouchDB antwortet mit:

{"error":"conflict","reason":"Document update conflict."}

Wenn dieser Fehler erscheint, dann muss die aktuelle Versionsnummer mit in die JSON Struktur:

curl -X PUT http://127.0.0.1:5984/albums/6e1295ed6c29495e54cc05947f18c8af -d '{"_rev":"1-2902191555","title":"There is Nothing Left to Lose", "artist":"Foo Fighters","year":"1997"}'

Jetzt wird auch klar, warum CouchDB das _rev Attribut beim Anlegen des Dokuments zurückgegeben hat. CouchDB antwortet mit:

{"ok":true,"id":"6e1295ed6c29495e54cc05947f18c8af","rev":"2-2739352689"}

CouchDB hat die Änderung akzeptiert und zugleich eine neue Versionsnummer erstellt. Die Versionsnummer ist der MD5 Hashwert der Transport Repräsentation(bessere Übersetzung) des Dokuments und einem N- Präfix, der angibt, wie oft das Dokument bereits aktualisiert wurde. Das wird uns bei der Replikation noch helfen. Mehr Infos gibt es in Kapitel 17: Konflikt Management.

Es gibt verschiedene Gründe, warum CouchDB dieses Versionierungsschema nutzt, dass man auch Multi-Version Concurrency Control (MVCC) nennt. Sie greifen alle ineinander und jetzt ist ein guter Zeitpunkt, einige davon etwas genauer zu erklären.

Einer der wichtigsten Aspekte des HTTP Protokolls, das CouchDB benutzt, ist, dass es zustandslos (stateless) ist. Was bedeutet das? Wenn man mit CouchDB spricht, so verschickt man Requests. Dazu muss man eine Netzwerkverbindung öffnen, Daten in eine oder beide Richtungen übertragen und die Verbindung wieder schließen. Jeder Request ist in sich abgeschlossen und trifft keine Annahmen darüber, in welchem Zustand sich der Server gerade befindet. Sämtliche Daten, die zur Bearbeitung des Requests benötigt werden, sind in dem Request enthalten und der Server vergisst den Request sofort nachdem er geschlossen wurde.

Andere Protokolle dagegen erlauben es eine Verbindung über einen längeren Zeitraum offen zu halten, um immer mal wieder Daten auszutauschen und nicht ständig eine Verbindung auf- und abzubauen. Dabei merken sich Client und Server, welche Daten bereits über diese Verbindung verschicht wurden – sie merken sich den Zustand der Verbindung (stateful). Wenn der Client den Zustand kennt, kann er sich anders verhalten, als wenn er das nicht weiß. Er kann zum Beispiel immer nur die Unterschiede zwischen Dokumenten verschicken oder den Modus auf dem Server ändern (z.B.„Beginne das Bearbeiten von Dokument“, „Bearbeite Dokument“, „Beende das Bearbeiten von Dokument“). Das bedeutet jedoch für den Server sehr viel Mehrarbeit, da er sich für jede offene Verbindung den Zustand merken muss.

Zusätzlich dazu ist die Verwaltung der offenen Verbindungen zu vielen verschiedenen Clients eine signifikante Last für den Server. HTTP Verbindungen sind dagegen meist von kurzer Dauer und erlauben CouchDB so mehr parallele Requests von vielen verschiedenen Clients zu beantworten.

Ein anderer Grund für die Benutzung von MVCC ist, dass diese Modell vom Konzept her einfacher ist und somit auch leichter zu programmieren ist. CouchDB benötigt weniger Code um das umzusetzen und weniger Code ist immer gut, da das Verhältnis von Fehlern zu Codezeilen konstant ist.

Suggestion: Andererseits verlagert MVCC die Behandlung von konkurrierenden, parallelen Zugriffen auf die gleichen Daten von der Datenbank in die Anwendung. Damit wird CouchDB einfacher, aber Ihre Anwendung muss damit umgehen können. Das ist kein großes Problem, allerdings ein etwas anderer Ansatz als bei relationalen Datenbanken, die sich um die interne Konsistenz der Daten kümmern. Der große Vorteil ist, dass Ihre Anwendung besser skaliert, weil sie nicht mehr auf einen globalen Zustand angewiesen ist und man bei mehr Last einfach einen weiteren Server dazustellen kann.

Die Versionierung von Dokumenten erleichtert auch die Replikation und die Speicherung der Dokumente in der Datenbank, doch dazu später mehr.

Der Begriff Version sollte ihnen bekannt vorkommen – wenn Sie bisher ohne Versionsverwaltung entwickeln, legen Sie dieses Buch sofort beiseite und lernen mit einem der gängigen Versionskontrollsysteme umzugehen. Neue Versionen von Dokumenten anzulegen ist sehr ähnlich zu Versionskontrollsystemen, doch es gibt einen entscheidenden Unterschied: CouchDB garantiert nicht, dass alte Versionen immer verfügbar sind.

Dokumente im Detail

Schauen wir uns den Request zum Anlegen eines Dokuments mit curl und der -v Option noch einmal genauer an. Dabei können wir auch gleich noch ein paar weitere Dokumente anlegen, die wir später benötigen.

Wir fügen ein paar mehr unserer Lieblingsalben in die Datenbank ein. Dazu holen wir uns eine neue UUID über die /_uuids URI. Falls Sie nicht mehr wissen, wie das geht, blättern Sie einfach ein paar Seiten zurück.

curl -vX PUT http://127.0.0.1:5984/albums/70b50bfa0a4b3aed1f8aff9e92dc16a0 -d '{"title":"Blackened Sky","artist":"Biffy Clyro","year":2002}'

Falls Sie mehr Details zu Ihren Lieblingsalben kennen, fügen Sie einfach weitere Attribute hinzu. Machen Sie sich auch keine Gedanken darüber, dass Sie im Moment noch nicht alle Informationen über die Alben haben. Die schema-freien Dokumente von CouchDB können enthalten was auch immer Sie möchten. Schließlich sollen Sie sich entspannen und keine Gedanken über Ihre Daten machen.

Mit der -v Option sieht die Antwort von CouchDB wie folgt aus (unwichtige Details haben wir dabei ausgeblendet):

> PUT /albums/70b50bfa0a4b3aed1f8aff9e92dc16a0 HTTP/1.1
>
< HTTP/1.1 201 Created
< Location: http://127.0.0.1:5984/albums/70b50bfa0a4b3aed1f8aff9e92dc16a0
< Etag: "1-2248288203"
<
{"ok":true,"id":"70b50bfa0a4b3aed1f8aff9e92dc16a0","rev":"1-2248288203"}

CouchDB antwortet mit dem HTTP Status Code 201 Created, genauso als wenn wir eine Datenbank anlegen. Der Location Header enthält die komplette URL zu unserem Dokument – und dann ist da noch ein neuer Etag Header. Im HTTP Kauderwelsch ist ein Etag eine bestimmte Version einer Ressource. In diesem Fall ist es eine bestimmte Version (die erste) unseres Dokuments. Das kommt einem irgendwie bekannt vor, oder? Ein Etag ist vom Konzept her das Gleiche wie die Versionsnummer für CouchDB Dokumente und es ist daher auch nicht überraschend, dass CouchDB den Inhalt des _rev Attributs für den Etag Header benutzt. Etags sind sinnvoll für Caching Systeme und wir beschäftigen uns näher mit ihnen in Kapitel 8: Show Funktionen.

Anhänge

CouchDB Dokumente können Anhänge haben genau wie eine E-Mail Anhänge haben kann. Ein Anhang wird durch einen Namen und einen MIME Type (oder Content-Type) sowie die Größe des Anhangs in Bytes beschrieben. Anhänge können beliebige Daten enthalten. Am einfachsten stellt man sich Anhänge als Dateien vor, die zu dem Dokument gehören. Das können Texte oder Bilder sein, Word Dokumente, Musik, Filme, etc. Legen wir mal einen an.

Anhänge bekommen ihre eigene URL wenn man sie hochlädt. Angenommen wir möchten das Album Cover zu dem Dokument mit der ID 6e1295ed6c29495e54cc05947f18c8af („There is Nothing Left to Lose”) hinzufügen und das Cover Bild ist in der Datei artwork.jpg im aktuellen Verzeichnis:

> curl -vX PUT http://127.0.0.1:5984/albums/6e1295ed6c29495e54cc05947f18c8af/ artwork.jpg?rev=2-2739352689 --data-binary @artwork.jpg -H "Content-Type: image/jpg"

Mit der -d@ Option sagen wir curl den Inhalt einer Datei als HTTP Request Body zu verschicken. Mit der -H Option sagen wir CouchDB, dass wir ein JPEG hochladen. CouchDB merkt sich das und wird entsprechende Header in die Antwort schreiben, wenn der Anhang wieder gelesen wird. Im Falle dieses Bildes wird der Browser das Bild anzeigen anstatt anzubieten, die Datei zu speichern. Das wird später noch nützlich sein. Achten Sie darauf, dass wir die aktuelle Versionsnummer zum Request hinzugefügt haben, genauso als wenn wir das Dokument selber aktualiseren würden. Letztendlich ändert das Hinzufügen eines Anhangs das Dokument.

Wenn Sie mit dem Browser zu folgender URL gehen, sollte das Bild angezeigt werden: http://127.0.0.1:5984/albums/6e1295ed6c29495e54cc05947f18c8af/artwork.jpg.

Wenn man das Dokument wieder liest, enthält es ein neues Attribut:

curl http://127.0.0.1:5984/albums/6e1295ed6c29495e54cc05947f18c8af

CouchDB antwortet mit:

{"_id":"6e1295ed6c29495e54cc05947f18c8af","_rev":"3-131533518","title": "There is Nothing Left to Lose","artist":"Foo Fighters","year":"1997","_attachments":{"artwork.jpg":{"stub":true,"content_type":"image/jpg","length":52450}}}

Das _attachments Attribut ist eine Liste von Key/Value Paaren, deren Werte JSON Objekte mit den Metadaten der Anhänge sind. stub=true sagt uns, dass der Eintrag nur aus Metadaten besteht. Fügen wir dem HTTP Request ?attachments=true hinzu, so bekommen wir einen Base64 kodierten String mit den Daten des Anhangs.

Wir werden später noch weitere Requests für Dokumente sehen, wenn wir uns weitere Funktionen von CouchDB näher ansehen. Wie beispielsweise die Replikation, die unser nächstes Thema ist.

Replikation

Die Replikation in CouchDB ist ein Mechanismus, um Datenbanken zu synchronisieren. Ähnlich wie mit rsync zwei Verzeichnisse lokal oder über ein Netzwerk synchronisiert werden können, synchronisiert die Replikation in CouchDB zwei Datenbanken entweder lokal oder über ein Netzwerk.

Mit einem einfachen POST Request teilt man CouchDB die Quelle (source) und das Ziel (target) der Replikation mit. CouchDB findet anschließend heraus, welche Dokumente und neuen Versionen von Dokumenten in der Quelldatenbank existieren, aber nicht in der Zieldatenbank und wird beginnen, die fehlenden Dokumente und Versionen zu kopieren.

Wir gehen auf die Replikation in Kapitel 16: Replikation noch genauer ein. In diesem Kapitel zeigen wir lediglich, wie man sie benutzt.

Zunächst erzeugt man eine Zieldatenbank. Beachten sie, dass CouchDB nicht automatisch eine Zieldatenbank für uns anlegt und einen Fehler meldet, wenn das Ziel nicht existiert. (Das gleiche gilt auch für die Quelle, aber diesen Fehler zu machen ist nicht ganz so einfach):

curl -X PUT http://127.0.0.1:5984/albums-replica

Ab jetzt können wir die Datenbank albums-replica als Ziel einer Replikation benutzen:

curl -vX POST http://127.0.0.1:5984/_replicate -d '{"source":"albums","target":"albums-replica"}'

Seit Version 0.11 bietet CouchDB die Option "create_target":true, die man dem JSON in dem POST Request hinzufügen kann, mit der die Zieldatenbank angelegt wird, falls sie nicht schon exisitert.

CouchDB antwortet mit (diesmal haben wir die Ausgabe formatiert, sodass man sie besser lesen kann):

{
  "history": [
    {
      "start_last_seq": 0,
      "missing_found": 2,
      "docs_read": 2,
      "end_last_seq": 5,
      "missing_checked": 2,
      "docs_written": 2,
      "doc_write_failures": 0,
      "end_time": "Sat, 11 Jul 2009 17:36:21 GMT",
      "start_time": "Sat, 11 Jul 2009 17:36:20 GMT"
    }
  ],
  "source_last_seq": 5,
  "session_id": "924e75e914392343de89c99d29d06671",
  "ok": true
}

CouchDB verwaltet eine Session Historie von Replikationen. Die Antwort auf einen Replication Request enthält die Historie dieser Replikationssession. Zu beachten ist, dass der Request für die Replikation erst beantwortet wird, wenn die Replikation abgeschlossen ist. Bis dahin bleibt die Verbindung offen. Wenn Ihre Datenbank viele Dokumente hat, wird es eine Weile dauern, bis alle kopiert wurden und Sie erhalten keine Antwort, bis dieser Vorgang vollständig abgeschlossen ist. Des weiteren ist es wichtig zu wissen, dass die Datenbank nur bis zu dem Punkt repliziert wird, an dem sie zu Beginn der Replikation war. Alle Veränderungen, die nach Beginn der Replikation erfolgen, werden nicht mit repliziert.

Wir überspringen die Details nocheinmal, denn "ok": true am Ende sagt uns, das die Replikation erfolgreich war. Wenn man anschließend auf die albums-replica Datenbank schaut, sollten alle Dokumente, die in der albums Datenbank vorhanden sind ebenfalls vorhanden sein. Nett, oder?

Was wir gerade gemacht haben nennt man in CouchDB eine lokale Replikation. Sie haben eine lokale Kopie einer Datenbank angelegt. Das ist hilfreich für Backups oder um Snapshots von bestimmten Zuständen der Datenbank für später aufzuheben. Es kann auch während der Entwicklung von Anwendungen nützlich sein, um gegebenenfalls einen Rollback auf eine stabile Version von Code und Daten durchzuführen.

Es gibt noch weitere Arten der Replikation, die in anderen Situationen sinnvoll sind. Die source und target Parameter in unserem Replikationsrequest sind Links (genau wie in HTML) und bis jetzt haben wir nur relative Links verwendet, die relativ zu dem Server sind, auf dem wir gerade arbeiten (deshalb auch lokale Replikation). Man kann aber auch eine entfernte Datenbank als Ziel angeben:

curl -vX POST http://127.0.0.1:5984/_replicate -d '{"source":"albums","target":"http://127.0.0.1:5984/albums-replica"}'

Verwendet man eine lokale Datenbank als source und eine entfernte Datenbank als target, dann nennt man das Push Replikation, weil die Änderungen auf den entfernten Server geschoben werden.

Da wir im Moment keinen zweiten CouchDB Server zur Verfügung haben, nutzen wir für die Beispiele die absolute Adresse unseres Servers doch es sollte klar sein, wie man das für Netzwerkserver verwendet.

Das ist sehr praktisch um lokale Änderungen mit entfernten Servern zu teilen – und sei es auch nur mit dem des Kumpels von nebenan.

Eine weitere Form der Replikation ist eine entfernte source und ein lokales target. In dem Fall führt man eine Pull Replikation durch. Damit lassen sich gut die Änderungen eines entfernten Server, der von anderen gepflegt wird, auf den eigenen Server kopieren:

curl -vX POST http://127.0.0.1:5984/_replicate -d '{"source":"http://127.0.0.1:5984/albums-replica","target":"albums"}'

Letztendlich kann man auch noch eine Remote Replikation starten, die hauptsächlich für Management Aufgaben eingesetzt wird:

curl -vX POST http://127.0.0.1:5984/_replicate -d '{"source":"http://127.0.0.1:5984/albums","target":"http://127.0.0.1:5984/albums-replica"}'

CouchDB und REST

CouchDB ist stolz darauf, ein RESTful API zu haben, doch diese Replikations Requests sehen für REST Profis nicht sehr REST-mäßig aus. Warum ist das so? Obwohl das Core API für Datenbanken, Dokumente und Anhänge RESTful ist, so gilt das nicht für alle Bereiche des CouchDB APIs. Das API für die Replikation ist ein Beispiel und weitere werden im Rest des Buches noch folgen.

Warum existieren RESTful und nicht RESTful APIs nebeneinander? Waren die Entwickler zu faul REST wirklich konsequent umzusetzen?

REST ist ein Architektur Stil, der zu bestimmten Architekturen führt, wie zum Beispiel dem CouchDB API für Dokumente. Doch es ist keine eierlegende Wollmilchsau. Das Auslösen eines Events ergibt unter REST wenig Sinn, denn es ist viel eher ein traditioneller Remote Procedure Call – und ist nichts Schlimmes dran.

Wir glauben das es wichtig ist, das richtige Werkzeug für den Job einzusetzen und REST ist kein Werkzeug für jeden Job. Zur Unterstützung möchten wir an dieser Stelle Leonard Richardson und Sam Ruby zitieren, die RESTful Web Services (O’Reilly) geschrieben haben und unsere Ansichten teilen.

Fassen wir zusammen

Wir haben immer noch nicht das vollständige CouchDB API vorgestellt, sind jedoch einen Schritt weiter gekommen und tief in die Details eingestiegen. Die Lücken werden wir im Laufe des Buches nacheinander noch füllen. Im Moment sollten Sie soweit gerüstet sein, dass Sie anfangen können, mit CouchDB Anwendungen zu schreiben.