Das CouchDB-Dokument: Freiheit statt Spalten

Der leichte Einstieg
Kommentare

Ich gehe davon aus, dass Sie den Umgang mit herkömmlichen SQL-Datenbanken gewohnt sind. Komplexe Datenstrukturen werden in mehrere Tabellen gegliedert, die untereinander mit Fremdschlüsseln referenziert sind. In jeder Tabelle definieren Sie die Spalten der Tabelle und legen sich auf das Datenformat der Spalte fest. Sie erzeugen eine Schablone und pressen Ihre Daten dort hinein.

Im Fall einer einfachen Filmverwaltung mit Feldern wie Name, Jahr, Genre und einem Feld für alle Schauspieler muss nicht lange überlegt und geplant werden, die einfache Datenstruktur passt erst einmal:

Tabelle 3.1: Film

In diesem Fall kann nach einzelnen Schauspielern nur über eine Volltextsuche innerhalb des Felds „Schauspielerliste“ gesucht werden, dafür sind wir flexibel, was die Anzahl der Schauspieler angeht.

Wenn wir eine maximale Anzahl von Schauspielern festlegen, bietet sich auch eine Anzahl von Schauspielerfeldern an:

Tabelle 3.2: Film

Der Nachteil ist klar: Egal ob gefüllt oder nicht, diese Felder sind in jedem Datensatz vorhanden und müssen abgefragt werden. Weiterhin haben wir Probleme, wenn auch ein vierter Schauspieler mit angegeben werden soll.

Also wird eine separate Tabelle für die Schauspieler benötigt, um flexibel zu sein:

Tabelle 3.3: Film

Tabelle 3.4: Schauspieler

Aus einem Objekt der realen Welt (wenn man Hollywood in diesem Fall mit „real“ gleichsetzen möchte) muss das Anwendungsprogramm die Informationen teilen und getrennt in der Datenbank abspeichern.

Natürlich kann (und sollte) man die Aufteilung der Tabellen noch weiterführen und die Schauspieler in einer eigenen Tabelle speichern sowie über eine Verknüpfungstabelle die Schauspieler mit den Filmen verbinden:

Tabelle 3.5: Film

Tabelle 3.6: Film_Schauspieler_xref

Tabelle 3.7: Schauspieler

Was hat dieser kleine Exkurs in die Tabellendefinition von relationalen Datenbanken mit unserem CouchDB-Dokument zu tun? Er soll Ihnen bewusst machen, dass Sie bei mehr als einer Tabelle für einen logischen Datensatz eine gewisse „Intelligenz“ in die Anwendungsprogrammierung legen müssen, um die Daten vorab zu formatieren.

Dadurch verteilen sich die Aufgaben auf das Anwendungsprogramm und die Datenbank.
Ändert sich das Datenschema (oh, wir wollen ja noch die Filmlänge hinzunehmen) so müssen sowohl die Datenbankdefinition als auch das Anwendungsprogramm angepasst werden. Sie mögen an dieser Stelle sagen „Ok, aber das ist doch ganz normal!“. Was wäre aber, wenn ich Ihnen anbieten kann, in Ihrer Arbeit auf den Teil des Datenbankdesigns zu verzichten?

Einer der Kernpunkte der CouchDB ist die Schemalosigkeit der Dokumente. Hier muss im Vorfeld in der Datenbank keine Definition der Datenstruktur hinterlegt werden, allein das Anwendungsprogramm liefert die Datenstruktur. Das kann gerade in der Entwicklungsphase eines Datenbanksystems von Vorteil sein, wenn die Anforderungen sich ständig ändern.

In CouchDB müssen Sie keine Tabellen und Fremdschlüssel definieren, alle strukturellen Informationen bereiten Sie vor dem Speichern auf und schreiben die Daten so, wie Sie einen zusammenhängenden Datensatz beschreiben würden. In unserem Fall also:

"name" : "American Graffiti",
"genre" :"Musikfilm",
"schauspieler" : ["Richard Dreyfuss","Ronny Howard","Paul Le Mat"]

Ein CouchDB-Datendokument besteht also im Prinzip aus einem einzigen langen Text, in dem die Daten in einer strukturierten Form gespeichert sind.

Dabei ist die Struktur eines Dokuments nicht vorgeschrieben (mit einigen Ausnahmen, wie Sie noch sehen werden), der nächste Film könnte also aus nur zwei Schauspielern bestehen und einen Bonus beinhalten:

"name" : "Der Mann und die Frau",
"genre" : "Tragik",
"schauspieler": ["Adam","Eva"],
"bonus": ["Making of"]

Beide Datensätze liegen nun in der gleichen Datenbank und haben doch unterschiedliche Felder.

Natürlich braucht auch die CouchDB ein festgelegtes Format zur Datenbeschreibung. Hier fiel die Wahl auf das JSON-Format.

Aufmacherbild: Sofa/ Two people carried a sofa von Shutterstock / Urheberrecht: Jane0606

[ header = JSON ]

JSON

Als einfaches Datenformat zum Austausch von strukturierten Daten hat sich JSON (JavaScript Object Notation) bereits seit einiger Zeit etabliert. Einen Boom erlebte dieses Format seit der Nutzung von AJAX zum Datenaustausch zwischen Server und Client, da JSON leichtgewichtiger und einfacher zu lesen ist als sein Hauptkonkurrent XML.

Wie der Name schon andeutet, ist JSON aus der JavaScript-Welt entstanden. Jeder JSON-Datensatz könnte in JavaScript mit eval() direkt in ein Objekt umgewandelt werden. An dieser Stelle kommt natürlich der Zeigefinger: eval() könnte auch Programmcode ausführen, sodass selbst innerhalb von JavaScript ein JSON-Parser benutzt werden sollte, wenn die Daten aus fremden, potenziell unsicheren Quellen kommen können.

Es gibt in JSON sechs Datentypen:

Tabelle 3.8: JSON-Datentypen

Ein JSON-Objekt besteht aus einem oder mehreren Namen/Werte-Paare, bei dem die Werte aus den oben genannten sechs Typen bestehen können. Dabei können die Daten verschachtelt werden. So kann ein Objekt aus mehreren Arrays bestehen, die ihrerseits Objekte und Arrays beinhalten.

Nehmen wir noch einmal unsere Filmdatei als Beispiel, dann könnte das große Drama folgendermaßen definiert werden:

{ "name" : "Der Mann und die Frau",
"genre" : "tragik",
"schauspieler" : ["Adam","Eva"],
"bonus" : ["making of", "interviews"],
"laufzeit" : 180
}

Oder auch in JavaScript formuliert:

var filmObj=eval('{"name":"Der Mann und die Frau","genre":"tragik",
"schauspieler":["Adam","Eva"],"bonus":["making of","interviews"],
"laufzeit":180}');

Der Zugriff auf die Schauspieler erfolgt in JavaScript über die Punktnotation:

filmObj.name

oder auch

filmObj.schauspieler[1]

Weitere Beispiele für gültige JSON-Ausdrücke:

{
"mietfahrzeuge": [
{ "hersteller":"Audi",
"modell":"A3",
"kilometerstand":57123,
"extras": [ "Klimaanlage", "Automatik" ],
"verliehen":false
},
{ "hersteller":"BMW",
"modell":"3er",
"kilometerstand":25974,
"extras": ["Klimaanlage","Ledersitze" ],
"verliehen":true
}
]
}

Daraus ergibt sich:
mietfahrzeuge[0].extras[1] = „Automatik“
mietfahrzeuge[1].verliehen = true

{
"buch": {
"titel":"CouchDB mit PHP",
"author":"Oliver Kurowski",
"kapitel": [
{ "nummer":1,
"titel":"Allgemeines",
"unterkapitel": [
{ "nummer":1,
"titel":"Einfuehrung in NoSQL" },
{ "nummer":2,
"titel":"Die Geschichte von CouchDB" }
]
}
]
}
}

buch.kapitel[0].nr = 1
buch.kapitel[0].unterkapitel[1].titel = Die Geschichte von CouchDB
In PHP gibt es seit der Version 5.2 die Funktionen json_encode und json_decode, mit der ein JSON-Ausdruck in einen PHP-Datentyp gewandelt werden kann und umgekehrt.

So kann das letzte Beispiel auf folgende Art in PHP umgesetzt werden:

<?php 
class BuchVO {
  var $titel;
  var $author;
  var $kapitel;
  public function __construct($titel,$author,$kapitel) {
    $this->titel=$titel;
    $this->author=$author;
$this->kapitel=$kapitel;
  }
}
class KapitelVO {
  var $nummer;
  var $titel;
  var $unterkapitel;
  public function __construct($nummer,$titel,$unterkapitel=null) {
    $this->nummer=$nummer;
    $this->titel=$titel;
    $this->unterkapitel=$unterkapitel;  
  }
}
class UnterkapitelVO {
  var $nummer;
  var $titel;
  public function __construct($nummer,$titel) {
    $this->nummer=$nummer;
    $this->titel=$titel;
  }
}
$unterkapitel_1A=array( 
                        new UnterkapitelVO(1,"Einfuehrung in NoSQL"),
                        new UnterkapitelVO(2,"Die Geschichte von CouchDB"));
$kapitel_1=new KapitelVO(1,"Allgemeines",$unterkapitel_1A);
$meinBuch=new BuchVO("CouchDB mit PHP",
                   "Oliver Kurowski",array($kapitel_1));
$buchJSON=json_encode($meinBuch);
echo $buchJSON;

PHP erlaubt die einfache Zuordnung von Attributen zu einem Objekt durch Setzen eines Werts, sodass auch mit der Klasse stdClass() gearbeitet werden könnte:

<?php 
$unterkapitel_1=new stdClass();
$unterkapitel_1->nummer=1;
$unterkapitel_1->titel="Einfuehrung in NoSQL";

$unterkapitel_2=new stdClass();
$unterkapitel_2->nummer=2;
$unterkapitel_2->titel="Die Geschichte von CouchDB";

$kapitel_1=new stdClass();
$kapitel_1->nummer=1;
$kapitel_1->titel="Allgemeines";
$kapitel_1->unterkapitel=array($unterkapitel_1,$unterkapitel_2);

$meinBuch=new stdClass();
$meinBuch->titel="CouchDB mit PHP";
$meinBuch->author="Oliver Kurowski";
$meinBuch->kapitel=array($kapitel_1);

$buchJSON=json_encode($meinBuch);
echo $buchJSON;
?>

Im umgekehrten Fall, also wenn Sie einen String haben, der ein JSON-Datenformat repräsentiert, müssen Sie bei der Benutzung von json_decode() darauf achten, dass der String utf8-formatiert ist, da sonst bei Sonderzeichen (auch Umlauten) kein Objekt zurückgeliefert wird. Ganz sicher gehen Sie mit

json_decode (utf8_encode($buchS));

[ header = Aufbau eines CouchDB-Datendokuments ]

Aufbau eines CouchDB-Datendokuments

Jetzt, wo sie wissen, wie sie Ihre Daten mithilfe von JSON formatieren können, brauchen Sie noch einige Informationen speziell zu CouchDB. Wie ich bereits sagte, ist ein CouchDB-Datendokument nichts weiter als ein großes Textfeld, in dem wir frei wählbar einen JSON-String speichern können.

Grundsätzlich kann man sagen, dass jedes CouchDB-Dokument ein Objekt ist, in dem die Attribute gespeichert werden. Während wir ein normales Array in JSON definieren können ([1,2,3]), können wir es nicht einfach als CouchDB-Dokument speichern. Dieses Array muss Teil eines Name/Wert-Paars innerhalb des CouchDB-Objekts sein:

{
"irgendwas":[1,2,3]
}

Wie Sie noch wissen, sind wir in der Definition der Daten frei, es gibt kein Schema, wie die Daten auszusehen haben. Allerdings gibt es einige Einschränkungen. Feldnamen dürfen nicht mit einem Unterstrich „_“ beginnen, da diese Namen für CouchDB-interne Zwecke benutzt werden. Damit CouchDB mit dem Dokument etwas anfangen kann, gibt es zwei Attribute, die jedes Dokument beinhaltet: _id und _rev.

Die Felder _id und _rev

Das Feld _id ist die eindeutige ID dieses Dokuments. Der Wert ist immer ein String und darf in jeder Datenbank nur einmal vorkommen. CouchDB bietet einen UUID-Algorithmus zum Erzeugen von 32-Zeichen IDs an. Geben Sie beim Speichern eines Dokuments eine ID an, so wird die von Ihnen angegebene ID übernommen. Lassen Sie das Feld leer, übernimmt CouchDB das Erzeugen dieser ID automatisch, sofern Sie ein Dokument mit POST speichern, und fügt das Feld in Ihr Dokument ein.

Als gültige ID können Sie jeden String nehmen, sollten allerdings beachten, dass Sonderzeichen als URL-Encoded benutzt werden. Als weitere Einschränkung ist der Unterstrich _ am Anfang des Dokuments zu beachten. Dieser weist auf besondere Dokumenttypen (z. B. „_design/“) hin.

Anhand dieser ID können Sie jedes Dokument in Ihrer Datenbank ansprechen:

http://localhost:5984/film_db/american-graffiti
http://localhost:5984/film_db/123345
http://localhost:5984/film_db/c0cf34859a315bcefe54aecb51000322

Das Feld _rev bezeichnet die Revisionsnummer und identifiziert die aktuelle Version dieses Dokuments. Wir erinnern uns: CouchDB ändert ein Dokument, indem es das aktuellere Dokument an das letzte Dokument anhängt und die Versionsnummer hoch zählt. Sie können die Revisionsnummer nicht verändern, müssen aber beim Verändern eines Dokuments (auch das Löschen ist erst einmal nur eine Veränderung), die aktuelle Versionsnummer mit angeben. Wie wir später noch sehen werden, ist die Revisionsnummer ein wichtiger Punkt in der Konfliktverwaltung beim gleichzeitigen Speichern eines Dokuments von mehr als einem Benutzer.

Beim Anlegen eines Dokuments brauchen Sie sich über dieses Feld keine Gedanken zu machen, CouchDB fügt es automatisch ein, und bei jeder erfolgreichen Änderung des Dokuments wird die neue Versionsnummer in dem geänderten Dokument eingetragen. Es gibt noch eine Reihe weiterer spezieller Felder, auf die wir in einem späteren Kapitel eingehen werden.

Von MySQL und anderen relationalen Datenbanken sind Sie es gewohnt, gleiche Datenstrukturen in Tabellen zusammenzufassen und dort zu speichern. Bei CouchDB gibt es innerhalb einer Datenbank (wobei mehrere Datenbanken auf einer CouchDB-Instanz angelegt werden können) keine weiteren Unterscheidungen von Dokumenten, alle liegen in der gleichen Datenbank. Ein Unterscheidungsmerkmal von Dokumententypen sind die IDs der Dokumente.

Die Namen der CouchDB-Dokumente die später zum Abfragen von Daten bestimmt sind („Designdokumente“), beginnen mit dem „_“. Die IDs der Datendokumente hingegen sind frei wählbar, und können z. B. durch unterschiedliche ID-Arten („konto_1234“,“person_1123) optisch und in Abfragen unterschieden werden . Es hat sich eingebürgert, bei unterschiedlichen Objektarten in einer Datenbank (z. B. Personen und Konten) ein Datenfeld „type“ einzufügen, anhand dessen in den Abfragen unterschieden werden kann.

Ein Datendokument besteht also mindestens aus einem JSON-Objekt mit den Feldern _id, _rev und den von Ihnen frei gewählten Feldnamen und deren Werten.

Hier zwei Beispiele für gültige Dokumente in CouchDB:

{
"_id": "film-american-graffiti",
"_rev":"1-f451e71cea3cd9a1a8273d7c62b8f44b",
"name":"American Graffiti",
"genre":"Musikfilm",
"schauspieler": ["Richard Dreyfuss","Ronny Howard","Paul Le Mat"],
"laenge":90
}
{
"_id": "c0cf34859a315bcefe54aecb51000322",
"_rev": "2-5f9ef89b3cecc517b8a0d4bf49cca9c2",
"type": "news",
"head": "Internet versehentlich gelu00f6scht",
"body": "Eine Hausfrau aus Wanne/Eickel legte versehentlich das
gesamte Internet lahm",
"comments": [
{
"name":"Horst-Peter",
"text": "So ein Quatsch",
"date": "2011-08-04"
},
{
"name":"Gaby R.",
"text":"ich glaub das nicht",
"date":"2011-08-05"
}
],
"pictures": ["bild01.jpg","bild02.jpg","bild03.jpg"]
}
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -