Entwurf und Implementierung eines Clojure

Werbung
Entwurf und Implementierung
eines Clojure-Treibers für ArangoDB
Peter Fessel
Matrikel-Nr.: 772676
Medieninformatik Bachelor
Beuth Hochschule für Technik Berlin
Betreuer: Prof. Dr. Stefan Edlich
Gutachter: Prof. Dr. Löser
0. Abstract
The following thesis deals with the design and implementation of
a driver for the multimodel NoSQL database ArangoDB. The
driver is written in the programming language Clojure and makes
use of ArangoDB's HTTP-Interface to send requests to the
database.
The goal for the driver is a simple and effective API design and a
lightweight implementation, that adds as little overhead as
possible to the database requests.
Topics in this thesis include the functional JVM language Clojure,
an examination of the field of NoSQL databases and the HTTP
protocol and its origins in the REST architectural style.
Peter Fessel, Berlin, April 2014
E-Mail: peter.fessel[at]rwth-aachen.de
www.peterfessel.com
www.github.com/lepetere
Inhalt
0. Abstract.......................................................................................................................... 2
1. Einleitung.......................................................................................................................5
2. Fachliche u. technische Grundlagen.............................................................................. 8
2.1 Clojure.....................................................................................................................8
2.1.1 Eigenschaften von Clojure und Lisp............................................................... 8
2.1.2 Funktionale Programmierung und Clojure......................................................9
2.2 NoSQL.................................................................................................................. 12
2.2.1 Polyglot Persistence...................................................................................... 12
2.2.2 Definition...................................................................................................... 13
2.2.3 Dokumentbasierte Datenbanken................................................................... 15
2.2.4 Key/Value Datenbanken................................................................................16
2.2.5 Graphdatenbanken.........................................................................................17
2.2.6 Wide Column Stores..................................................................................... 18
2.2.7 Multimodel-Datenbanken............................................................................. 18
2.2.8 Zusammenfassung.........................................................................................19
2.3 REST und HTTP...................................................................................................20
2.3.1 REST Einführung..........................................................................................20
2.3.2 Ressourcen und Repräsentationen.................................................................21
2.3.3 Die Bestandteile von REST.......................................................................... 21
2.3.4 REST, HTTP und die HTTP-Methoden........................................................ 23
2.3.5 HTTP-Header................................................................................................ 25
2.3.6 REST und NoSQL.........................................................................................26
2.4 ArangoDB............................................................................................................. 27
2.4.1 Eigenschaften und Designziele..................................................................... 27
2.4.2 Speichereffizienz und Performance.............................................................. 28
2.4.3 Concurrency, Transaktionen, Skalierbarkeit und Replikation.......................29
2.4.4 ArangoDBs HTTP/REST-Interface...............................................................31
2.4.5 Datenbanken, Collections, Dokumente und Graphen in ArangoDB.............31
2.4.6 Querying........................................................................................................34
2.4.7 Indizierung.................................................................................................... 35
2.4.8 Die Bestandteile von ArangoDB................................................................... 35
3. Aufgabenstellung......................................................................................................... 38
4. Entwurf und Implementierung.....................................................................................39
4.1 Werkzeuge.............................................................................................................40
4.1.1 Versionsverwaltung....................................................................................... 40
4.1.2 Clojure Projektmanagement..........................................................................40
4.2 verwendete Libraries.............................................................................................41
4.2.1 clj-http........................................................................................................... 41
4.2.1 Cheshire.........................................................................................................41
4.3 Versionierung........................................................................................................ 42
4.4 Vergleich von APIs anderer Clojure Datenbanktreiber.........................................43
4.4.1 Monger für MongoDB.................................................................................. 43
4.4.2 Clutch für CouchDB..................................................................................... 47
4.4.3 Elastisch für Elasticsearch.............................................................................48
4.4.4 clj-orient für OrientDB..................................................................................49
4.4.5 Carmine für Redis......................................................................................... 51
4.4.6 Neocons für Neo4J........................................................................................ 52
4.4.7 Zusammenfassung.........................................................................................53
4.5 grundsätzliche Überlegungen................................................................................54
4.5.1 ähnliche Methoden........................................................................................ 54
4.5.2 Angabe von Verbindungsdaten......................................................................54
4.5.3 Überprüfung der Eingabedaten..................................................................... 55
4.5.4 Methodenbenennung..................................................................................... 56
4.5.5 Gliederung der Funktionalitäten in eigene Funktionsräume vs. Gliederung
der ArangoDB HTTP-API......................................................................................57
4.6 Clarango API.........................................................................................................58
4.6.1 Clarango Core............................................................................................... 58
4.6.2 Document API............................................................................................... 59
4.6.3 Collection API............................................................................................... 60
4.6.4 Datenbank API.............................................................................................. 60
4.6.5 Query API......................................................................................................60
4.6.6 Graph API......................................................................................................61
4.6.7 Flexible Funktionssignaturen........................................................................ 61
4.7 Implementierungsdetails....................................................................................... 63
4.7.1 Error-Handling.............................................................................................. 63
4.7.2 Rückgabewerte.............................................................................................. 63
4.7.3 „klassische“ Datenbank-Methoden vs. „clojuresque“ Methoden.................65
4.7.4 Batch Requests.............................................................................................. 67
4.7.5 Allgemein verwendbare unterliegende Methoden.........................................67
4.8 Clarango System-Architektur............................................................................... 69
4.8.1 Clarango Namespaces................................................................................... 69
4.8.2 Diagramm......................................................................................................71
4.9 Exemplarische Untersuchung: Aufbau und Aufruf einer Clarango Methode ......72
5. Testing / Qualitätssicherung........................................................................................ 78
6. Anwendungsdemo....................................................................................................... 80
7. Fazit und Ausblick....................................................................................................... 83
8. Abbildungsverzeichnis.................................................................................................88
9. Quellenverzeichnis...................................................................................................... 89
9.1 Buchquellen.......................................................................................................... 89
9.2 Internetquellen...................................................................................................... 90
10. Anhang.......................................................................................................................96
10.1 Ausgaben des Anwendungsbeispiels aus Kapitel 6............................................96
10.2 Vollständige Clarango API Dokumentation...................................................... 104
10.2.1 Core API....................................................................................................104
10.2.2 Document API........................................................................................... 105
10.2.3 Collection API........................................................................................... 109
10.2.4 Datenbank API.......................................................................................... 113
10.2.5 Query API..................................................................................................114
10.2.6 Graph API..................................................................................................116
10.2.7 collection-ops API..................................................................................... 122
10.3 ArangoDB API Checkliste................................................................................ 124
4
1. Einleitung
Mit dem Aufkommen des Web 2.0 und seinen sich schnell verändernden dynamischen
Web-Anwendungen sowie großen und untereinander vernetzten Datenmengen ist eine
neue Datenbank-Generation entstanden. Diese wird unter dem Label „NoSQL“
zusammengefasst. Nach Jahren der einseitigen Nutzung von relationalen Datenbanken in
Softwareprojekten steht diese Bewegung für eine freie Auswahl verschiedener
Datenbankmodelle.
Ein Vertreter dieser neuen Gruppe von Datenbanken ist ArangoDB 1. ArangoDB wird seit
2011 von dem Unternehmen triAGENS aus Köln entwickelt und ist als Open Source
Software frei verfügbar. Dort wo sich die meisten anderen Vertreter der NoSQL-Fraktion
auf ein bestimmtes Datenmodell wie Dokumente oder Graphen festgelegt haben, deckt
ArangoDB gleich drei verschiedene Datenmodelle ab: Dokumente, Graphen und
Key/Value. Der Gedanke dahinter ist, dass sich die Datenbank fexibel an eine WebAnwendung während ihrer Entwicklung anpassen kann. Wenn sich in der
Entwicklungsphase neue Anforderungen ergeben, so ist es nicht notwendig, gleich das
ganze Datenbanksystem zu wechseln oder ein zusätzliches System zum Technologiestack
hinzuzufügen. Stattdessen vereint ArangoDB viele Funktionen unter einem Dach, sodass
bei Bedarf zusätzliche oder andere Funktionen genutzt werden können. Um diese
vielseitigen Anwendungsmöglichkeiten zu erreichen, werden leichte Abstriche bei der
Performance und bei der Skalierbarkeit gemacht.
Mit diesem breiten Ansatz hat sich das Team von ArangoDB zum Ziel gesetzt, „das MySQL
in NoSQL“ zu werden2. ArangoDB soll also zur quasi-Standard-Datenbank unter den
NoSQL-Datenbanken werden, so wie es MySQL faktisch im Bereich der relationalen
Datenbanken ist. Da die Datenbank sich in stetiger Weiterentwicklung befindet, ist es
wahrscheinlich, dass sie in Zukunft zu einer breiteren Verwendung gelangt.
Die Nähe von ArangoDB zum Web wird deutlich durch die Verwendung einer
REST/HTTP-Schnittstelle zur Kommunikation mit seinen Clients. Ebendiese Schnittstelle
soll zur Entwicklung des Clojure Treibers in dieser Bachelorarbeit genutzt werden. Der
Treiber dient dazu, die Sprache Clojure mit der Datenbank ArangoDB kommunizieren zu
lassen, sodass Daten ausgetauscht werden können.
1 http://www.arangodb.org/
2 „we want to become the MySql in nosql – without MySql’s annoyances of course ;-)“
[wwwArangoBlog1]
5
Bei Clojure handelt es sich um eine vornehmlich funktionale Programmiersprache. Der
Ansatz der funktionalen Programmierung versucht mit der Komplexität von
Softwareanwendungen umzugehen, indem Zustände und veränderliche Variablen aus der
Programmierung verbannt werden. Da es sich bei REST/HTTP ebenfalls um ein
zustandsloses Konzept handelt, bietet sich eine Kombination dieser beiden Ansätze an. Als
die Idee zu dieser Arbeit entstand, gab es noch keinen ArangoDB Treiber für Clojure3. Das
Ziel dieser Arbeit ist daher die Entwicklung eines solchen Treibers.
Der Treiber soll in Zusammenarbeit mit dem Betreuer Prof. Dr. Stefan Edlich entstehen.
Prof. Dr. Edlich wird sich hierbei vornehmlich auf das Erstellen einer Test-Infrastruktur
konzentrieren, während der Verfasser dieser Arbeit den Entwurf, und soweit es geht, auch
die Implementierung des eigentlichen Treibers übernimmt. Der Treiber wird als Open
Source Projekt unter dem Namen „Clarango“ realisiert. „Clarango“ setzt sich aus den
Anfangsbuchstaben der Namen Clojure und ArangoDB zusammen.
Mit Hilfe einer HTTP-Library ist es theoretisch möglich, direkt HTTP Anfragen aus einer
Anwendung an die Datenbank zu senden. In der Praxis ist dies jedoch keine gute Lösung,
da es die Komplexität der Anwendung unnötig erhöht. Die Verwendung eines Clojure
Treibers bietet den Vorteil, dass das Senden der HTTP-Anfragen vollständig ausgelagert
wird. Der Treiber übernimmt dann die Aufgabe des Zusammensetzens der HTTP-Anfragen
im von der Datenbank erwarteten Format.
Durch die Möglichkeit der Java-Interoperabilität bei Clojure bietet sich zwar die
Verwendung des bereits verfügbaren Java Treibers für ArangoDB 4 in direkter Weise oder
mittels eines Clojure-Wrappers an. Dies ist allerdings umständlich, denn unter anderem
müssen bei der Verwendung des Java Treibers Objekte erzeugt werden, um mit der
Datenbank zu arbeiten (z.B. Instanzen des Treibers selbst). In der funktionalen und
zustandsarmen Clojure-Programmierung ist dies jedoch nicht erwünscht und Zustände
sollten so weit es geht vermieden werden. Ein nativer Clojure Treiber lässt sich weitaus
besser in die Clojure-übliche Programmierweise integrieren. Für die Verwendung von
Clojure spricht außerdem, dass die Programmiersprache nativ die Datenstruktur der Maps
unterstützt. Diese ist dem JSON-Format, das von ArangoDB verwendet wird, ähnlich und
kann leicht in dieses übersetzt werden.
Im Laufe dieser Arbeit wird zunächst eine Einführung in die verwendeten Technologien
und zugehörigen Themengebiete gegeben. Hierbei wird ein besonderer Schwerpunkt auf
3 Eine offizielle Liste der verfügbaren Treiber kann hier eingesehen werden:
https://www.arangodb.org/drivers
4 https://github.com/tamtam180/arangodb-java-driver
6
das Thema NoSQL gelegt. Anschließend wird die Datenbank ArangoDB in Bezug zu
bereits existierenden Datenbanken gestellt und in das Feld der NoSQL-Datenbanken
eingeordnet. Nach einer genaueren Beschreibung der Aufgabenstellung werden die
Befehlssätze einiger anderer Treiber für NoSQL-Datenbanken untersucht, um eine
Entscheidungsgrundlage für ein möglichst gutes Design der Clarango API 5 zu erhalten.
Anschließend wird dann auf grundsätzliche Designüberlegungen in Kombination mit
konkreten Implementierungsdetails eingegangen und das Design der Clarango API sowie
die Architektur der Anwendung erläutert. Zum Schluss werden die zur Qualitätssicherung
verwendeten Maßnahmen beschrieben und einige Codebeispiele zur möglichen
Verwendung von Clarango aufgeführt.
5 API steht für „Application Programming Interface“ und bezeichnet eine Schnittstelle eines
Programms, die von anderen Programmen benutzt wird um auf Dienste des Programmes zugreifen zu
können. Siehe hierzu auch http://de.wikipedia.org/wiki/Programmierschnittstelle.
7
2. Fachliche u. technische Grundlagen
Die folgenden Abschnitte sollen eine Einleitung bieten in für diese Arbeit verwendete und
grundlegende Technologien.
2.1 Clojure
Bei der Programmiersprache Clojure handelt es sich um einen Dialekt der
Programmiersprache Lisp6 und um eine Sprache, die den Einsatz funktionaler
Programmiermodelle fördert. Clojure wird auf der Java Virtual Machine (JVM) 7
ausgeführt, der Laufzeitumgebung in der auch die Programmiersprache Java ausgeführt
wird. Clojure Code wird nach JVM-Bytecode kompiliert, bietet jedoch alle Features auch
zur Laufzeit an und bleibt damit vollständig dynamisch.
Clojure wurde von Rich Hickey als „general-purpose language“ geschaffen [wwwClojure]
und erstmals im Jahr 2007 veröffentlicht. Ohne dass besondere Mittel des Marketings
eingesetzt wurden, hatte die Sprache schnell einen größeren Kreis von Anhängern und eine
lebendige Community8.
2.1.1 Eigenschaften von Clojure und Lisp
Clojure ist eine dynamisch und stark typisierte Sprache. Es handelt sich um einen Dialekt
von Lisp, einer der ersten Programmiersprachen überhaupt, die auch heute noch
Verwendung findet. Clojure gehört dabei genau wie der heute noch verwendete Dialekt
Scheme zur Familie der „lisp-1“ Dialekte [Tate2011]. Durch den Verzicht auf die
Abwärtskompatibilität schlägt die Sprache jedoch, verglichen mit anderen Dialekten, eine
neue Richtung ein. Diese äußert sich unter anderem in der Erweiterung der verfügbaren
Datenstrukturen um Vektoren und Maps und der dazugehörigen Einführung von
zusätzlichen Klammer-Typen, die die Lesbarkeit erhöhen sollen, sowie der Einführung der
standardmäßigen Unveränderlichkeit der Datenstrukturen [wwwClojureRatio]. Clojure
versteht sich laut Rich Hickey auch als ein „praktisches“ Lisp, denn die bisherigen LispDialekte wurden hauptsächlich in der Forschung verwendet und sind nie richtig in der
produktiven Programmierung der Industrie angekommen [Tate2011].
Lisp steht für „LISt Processing“ und der Name rührt daher, dass der gesamte Code aus
Listen besteht. Funktionsaufrufe verwenden das erste Element einer Liste als
6 http://de.wikipedia.org/wiki/Lisp
7 http://de.wikipedia.org/wiki/Java_Virtual_Machine
8 Vgl. „Interview with Rich Hickey“: https://www.ugtastic.com/rich-hickey/
8
Funktionsnamen und die restlichen Elemente als deren Argumente. Da Lisp seine eigenen
Datenstrukturen verwendet um Programme auszudrücken, lautet eine wichtige Strategie
der Sprache „Daten als Code“ (code-as-data). Durch dieses Konzept ist die Sprache auch
insbesondere gut zur Metaprogrammierung geeignet. Letztere stellt auch eine der Stärken
von Clojure dar.
Wie bereits erwähnt wird Clojure auf der Java Virtual Machine ausgeführt. Dies verschafft
der Sprache einen infrastrukturiellen Vorteil einerseits durch die Verfügbarkeit der
Plattform auf vielen (Betriebs-)Systemen und der bereits vorhandenen breit gestreuten
Akzeptanz dieser Plattform; andererseits durch die Möglichkeit der Einbindung der vielen
bereits existierenden Java-Bibliotheken (Java-Interoperabilität). Die Vielseitigkeit von
Clojure wird noch vergrößert durch einen existierenden Clojure-nach-JavaScript Compiler
namens ClojureScript9, der die Ausführung in JavaScript-Umgebungen erlaubt.
2.1.2 Funktionale Programmierung und Clojure
Bei Clojure handelt es sich um eine Programmiersprache, die teilweise auch imperative
Programmierung zulässt [Tate2011]. In erster Linie ist Clojure jedoch eine funktionale
Programmiersprache und die funktionale Programmierung wird von Clojure stark
gefördert. Daher soll dieses Programmierparadigma und damit weitere wichtige
Eigenschaften der Sprache Clojure hier kurz erläutert werden.
Eine sehr gute Definition der funktionalen Programmierung findet sich auf S.13 in
[Edlich2011]:
„Das Konzept einer Funktion im Sinne der Mathematik ist in der funktionalen
Programmierung am klarsten umgesetzt. Hier stellen die Funktionen
Abbildungsvorschriften dar. Eine Funktion besteht dann aus einer Reihe von
Definitionen, die diese Vorschrift beschreibt. Ein funktionales Programm besteht
ausschließlich aus Funktionsdefinitionen und besitzt keine Kontrollstrukturen wie
Schleifen. Wichtigstes Hilfsmittel für die funktionale Programmierung ist daher die
Rekursion. Funktionen sind in funktionalen Programmiersprachen Objekte, mit
denen wie mit Variablen gearbeitet werden kann. Insbesondere können Funktionen
als Argument oder Rückgabewert einer anderen Funktion auftreten. Man spricht
dann von Funktionen höherer Ordnung.“
Folgende Eigenschaften und Ideen zeichnen sowohl die funktionale Programmierung als
auch die Programmiersprache Clojure aus:
9 https://github.com/clojure/clojurescript
9
•
First-Class Functions: Damit bezeichnet man die Tatsache, dass Funktionen selber
Werte sind. Mit ihnen kann genau wie mit anderen Daten gearbeitet werden. Das
heißt insbesondere können sie anderen Funktionen als Argumente übergeben
werden und von diesen als Ergebnisse zurückgegeben werden.
•
Funktionen höherer Ordnung: Die Existenz von Funktionen höherer Ordnung
resultiert direkt aus den First-Class Functions: Es handelt sich hierbei um
Funktionen, die andere Funktionen als Argumente übergeben bekommen oder
Funktionen als Ergebnisse zurückliefern. Klassische Beispiele aus der Welt der
funktionalen Programmierung sind die Funktionen Map und Reduce, die jeweils
eine Funktion übergeben bekommen, die sie auf allen Elementen einer Collection
ausführen.
•
unveränderliche Werte (immutable Values): Die zentralen Datenstrukturen in
Clojure sind unveränderlich, anders als zum Beispiel Variablen in Sprachen wie Java
oder JavaScript. Dadurch wird die Fehleranfälligkeit von Programmen reduziert,
denn es werden die sogenannten „Pure Functions“ (siehe unten) erst möglich.
Außerdem ist das Arbeiten mit unveränderlichen Werten ein wichtiger Aspekt der
nebenläufigen Programmierung, einer der Stärken von Clojure: wird mit mehreren
Threads parallel auf denselben Datenstrukturen gearbeitet, können diese sich darauf
verlassen, dass die Daten nicht gleichzeitig von anderen Threads verändert werden.
Dies erleichtert die nebenläufige Programmierung stark.
•
keine Seitenefekte: Als Seiteneffekte werden Interaktionen einer Funktion mit der
„Außenwelt“ bezeichnet; per Definition handelt es sich bei jedem Input/Output
eines Programms oder der Modifikation eines veränderlichen Objekts um einen
Seiteneffekt [Emerick2012].
•
Pure Functions: „Pure Funktionen“ verzichten auf die Nutzung von Seiteneffekten.
Bei gleichen Eingabewerten resultieren hier immer die gleichen Ausgabewerte.
Dadurch ist das Verhalten einer Funktion zu hundert Prozent vorhersagbar, was sie
wiederum sehr gut testbar macht und die Fehleranfälligkeit eines Programms
reduziert.
Die rein funktionale Programmierung kann man sich auch wie einen Baum vorstellen: Es
wird eine Funktion aufgerufen, die die Wurzel des Baumes bildet und wiederum weitere
Funktionen aufruft, welche wiederum weitere Funktionen aufrufen usw. Am Ende eines
jeden Astes liefert jeweils die unterste Funktion einen Rückgabewert, der dann an die
aufrufenden Funktionen weitergereicht bzw. von diesen weiterverarbeitet wird und
ebenfalls an die aufrufende Funktion zurückgegeben wird bis wieder die Wurzel des Baumes
erreicht ist.
10
Da in Programmen jedoch immer irgendeine Art von Eingabe und Ausgabe erfolgen muss,
damit das Programm einen sinnvollen Zweck erfüllen kann, sind rein funktionale
Programme in der Realität nicht möglich. Es wird stattdessen vielmehr auf eine Mischform
zurückgegriffen, wie sie in Abbildung 1 dargestellt ist. Ein Programm kann einen
funktionalen Kern haben, in dem alle Vorteile der funktionalen Programmierung genutzt
werden können. Es muss jedoch auch über einen Teil verfügen, der mit der restlichen Welt
kommunizieren kann und deswegen nicht seiteneffektfrei ist.
Abb. 1: Diagramm eines
funktionalen Programms
11
2.2 NoSQL
Bei NoSQL handelt es sich um eine ca. seit dem Jahr 200410 stattfindende Bewegung weg
von den allseits verbreiteten relationalen Datenbanken hin zu alternativen
Datenbankmodellen. Insbesondere hat sich der Bedarf nach neuen Datenbanktypen durch
die rasante Entwicklung des World Wide Web und besonders des sogenannten „Web 2.0“
ergeben. Einerseits da im Web 2.0 mit besonders großen Datenmengen (Big Data)
gearbeitet wird, wie diese zum Beispiel in sozialen Netzwerken vorkommen, und diese
effektiv und skalierbar gespeichert und verarbeitet werden sollen. Andererseits, da die
neuen, meist schemalosen Datenbanken eine agile Entwicklung, wie sie häufig bei WebStartups gefordert wird, unterstützen.
2.2.1 Polyglot Persistence
Ein bekanntes Stichwort der Bewegung lautet „Polyglot Persistence“. Damit gemeint ist,
dass unterschiedliche Anforderungen in Anwendungen auch mit unterschiedlichen
Datenbanken gelöst werden sollten. Unterschiedliche Datenbanktypen wurden für
bestimmte Zwecke geschaffen. Zu versuchen, alle unterschiedlichen Problembereiche mit
nur einem Datenbanktypen zu lösen, das heißt unterschiedliche Datentypen in das selbe
Schema zu pressen, ist oft kontraproduktiv und mündet in schlechter Performanz.
In [Sadalage2013] findet sich dazu ein Beispiel anhand einer E-Commerce Plattform: Es
macht wenig Sinn das Session-Management, den Benutzer-Einkaufswagen, die Bestell- und
Produktdaten sowie Kaufempfehlungen einer solchen Anwendung im selben
Datenbankmodell abzulegen. Denn es bestehen hier unterschiedliche Anforderungen an die
Konsistenz, die Verfügbarkeit, die Skalierbarkeit und die Datensicherheit. Die Autoren
schlagen hier einen hybriden Ansatz vor, in dem für das Session-Management und den
Einkaufswagen ein Key/Value-Store benutzt wird. Dieser ist schnell und skalierbar und
eignet sich hier insbesondere, da der Datenzugriff üblicherweise über bekannte User- und
Session-IDs erfolgen wird. Ein erhöhtes Maß an Datensicherheit ist dabei nicht
erforderlich. Abgeschickte Bestellungen und das Produkt-Inventar können dagegen in einer
traditionellen relationalen Datenbank abgespeichert werden. Alternativ könnte aber auch
eine dokumentbasierte Datenbank genutzt werden. Für Kaufempfehlungen aufgrund von
bereits gekauften Produkten oder den Bestellungen anderer Kunden eignet sich dagegen
insbesondere eine Graph-Datenbank, da diese in besonderem Maße dafür geeignet ist,
Verknüpfungen von Datensätzen untereinander abzubilden.
10 2004: Entwicklung von Googles BigTable und GFS (Google File System), aufgrund derer laut
[Edlich2011] Google als der „NoSQL-Vorreiter schlechthin“ gilt
12
Die Autoren von [Edlich2011] sehen zudem hinter dem Begriff Polyglot Persistence eine Art
Bewegung für eine freie Datenbank-Auswahl. Oft sind Unternehmen durch Verträge oder
auch durch mangelndes Wissen auf genau eine Datenbank festgelegt und so müssen die
unterschiedlichsten Datenstrukturen in unzählige relationale Datenbanken „gepresst“
werden. Die Autoren fordern, dass vor der Auswahl einer Datenbank die Anforderungen
genauer untersucht werden und auch schon in der Lehre mehr darauf geachtet wird,
Kenntnisse über die unterschiedlichen verfügbaren (NoSQL-)Systeme zu vermitteln.
2.2.2 Definition
Der Begrif NoSQL war zu Beginn der Bewegung als eine Art Negativ-Definition zu
verstehen, der auf eine Abkehr der auf der Abfragesprache SQL basierenden relationalen
Datenbanken hinwies. Mittlerweile wird der Begriff von der Community aber auch als
„Not only SQL“ definiert. Dies ist wohl darauf zurückzuführen, dass auch einige NoSQL
Datenbanken eine Abfrage mittels SQL oder einer (evtl. erweiterten) Untermenge dieser
Sprache ermöglichen.
Eine scharfe Trennung der Bereiche der klassischen relationalen, SQL-basierten Lösungen
und den Datenbanken der NoSQL-Fraktion wird erschwert durch eine Vielzahl an
Hybridlösungen zwischen beiden Welten und durch viele unterschiedliche Meinungen
darüber, wo eine mögliche Grenze zu ziehen ist [Edlich2011].
Die Autoren von [Edlich2011] haben aber einen Versuch gewagt und 7 Kriterien
entwickelt, um NoSQL Datenbanken als solche zu identifizieren11:
1. „Das zugrunde liegende Datenmodell ist nicht relational.“
→ hier hinter verbirgt sich die Erkenntnis, dass das relationale Datenmodell nicht immer
das passendste für ein Problem sein muss;
2. „Die Systeme sind von Anbeginn an auf eine verteilte und horizontale
Skalierbarkeit ausgerichtet.“
→ die herkömmlichen relationalen Datenbanken waren immer schwerer für große Web
2.0 Anwendungen zu skalieren; die Datenbanken der NoSQL-Bewegung haben dieses
Problem von Anfang an mit im Design berücksichtigt; hier können durch horizontale
Skalierung auch auf Standard-Hardware sehr große Datenmengen efektiv verwaltet
werden;
11 Es müssen jedoch nicht alle Punkte zwingend erfüllt sein, um eine Datenbank zur NoSQL-Fraktion zu
zählen.
13
3. „Das NoSQL-System ist Open-Source.“
→ dies ist das wohl am wenigsten strikt gemeinte Kriterium; ist ein System nicht Open
Source, ist das kein Ausschlusskriterium, aber viele der NoSQL-Systeme sind frei verfügbar
und verstehen sich als eine Art Protestbewegung gegen die Dominanz der (teilweise
kostspieligen) relationalen Systeme;
4. „Das System ist schemafrei oder hat nur schwächere Schemarestriktionen.“
→ hierdurch ergibt sich die bereits erwähnte Möglichkeit der agilen Entwicklung und
fexiblen Änderung und Erweiterung, wie sie mit konventionellen relationalen
Datenbanken und ihrem starren Tabellensystem schwer umzusetzen ist;
5. „Aufgrund der verteilten Architektur unterstützt das System eine einfache
Datenreplikation.“
→ dies wurde ebenfalls bei den meisten Systemen von Anfang an als Anforderung mit
umgesetzt; oft kann durch ein einziges Kommando eine ganze Datenbank repliziert werden
und auf einem zusätzlichen Server-Knoten bereitgestellt werden;
6. „Das System bietet eine einfache API.“
→ Datenbank-Anfragen konventioneller SQL-Systeme können durch viele JoinOperationen leicht kompliziert werden; außerdem ergibt sich eine gewisse Fehleranfälligkeit
und Starrheit dadurch, dass die Anfragen in Form von Strings formuliert werden; NoSQLLösungen bieten hier häufg eine einfachere API; Beispiel sind etwa Datenbanken, deren
Interaktion komplett über eine REST-Schnittstelle läuft12; bei komplexen DatenbankAnfragen hat jedoch meist noch SQL die Nase vorn, denn in NoSQL-Systemen müssen
diese häufg als Map/Reduce-Abfragen formuliert werden;
7. „Dem System liegt meistens auch ein anderes Konsistenzmodell zugrunde:
Eventually Consistent und BASE, aber nicht ACID.“
→ bei Web 2.0 Anwendungen (wie zum Beispiel bei Social Media Portalen) handelt es
sich häufg um nicht sicherheitskritische Anwendungen (im Gegensatz zum Beispiel zu
einer Bankanwendung); es muss damit häufg kein klassisches ACID-System 13 verwendet
werden, sondern die Daten können auch für einen kurzen Zeitraum inkonsistent sein, was
der Skalierbarkeit und der Verfügbarkeit zugute kommt; es reicht, wenn die Daten
„eventually consistent“ sind14;
12 Siehe hierzu auch Abschnitt 2.3.6
13 ACID steht für Atomicity, Consistency, Isolation, Durability und beschreibt grundsätzliche
Eigenschaften von Verarbeitungsschritten in Datenbank-Systemen; siehe hierzu auch
http://de.wikipedia.org/wiki/ACID
14 Siehe hierzu auch http://en.wikipedia.org/wiki/Eventual_consistency; das dazugehörige
Konsistenzmodell wird auch als BASE für Basically Available, Soft state, Eventual consistency
bezeichnet;
14
2.2.3 Dokumentbasierte Datenbanken
Eine dokumentbasierte Datenbank speichert Dokumente. Bei diesen Dokumenten handelt
es sich aber im Gegensatz zu „echten“ Dokumenten wie zum Beispiel Textdateien 15 um
strukturierte Datensammlungen wie Hashes oder JSON 16. Es gibt in den Dokumenten IDFelder und dazugehörige Values (Werte), die wiederum weiteren Dokumenten entsprechen
können. So können Daten beliebig geschachtelt werden. Dadurch dass die meisten
Datenbanken auf diesem Gebiet schemafrei sind, ist dieses Datenmodell sehr fexibel.
Die wohl bekanntesten Vertreter sind hier MongoDB17 und CouchDB18. Bei beiden
handelt es sich um Open Source Projekte. MongoDB wurde erstmals im Jahr 2009
veröffentlicht und CouchDB bereits 2005. Beide Datenbanken haben viele Gemeinsamkeiten. So eignen sich beide sowohl für kleine als auch für sehr große Anwendungen
[Redmond2012]. Bei beiden Datenbanken werden die Daten als JSON-Dokumente
abgespeichert19, es wird JavaScript als primäre Interaktionssprache eingesetzt und beide
bieten Map/Reduce-Funktionen20. Bei CouchDB funktioniert die Abfrage per sogenannter
Views, in denen Map/Reduce-Funktionen spezifiziert sind; die Views speichern die
Ergebnisse der Abfrage zwischen, bis sich die beteiligten Daten ändern. MongoDB bietet
zusätzlich zu Map/Reduce noch Ad Hoc Querying 21 und den Zugriff per Indices, wie man
ihn aus relationalen Systemen gewohnt ist. So wird eine Brücke geschlagen zwischen
klassischen relationalen Systemen und den Vorteilen der schemafreien, verteilten NoSQL
Systeme.
CouchDB bezeichnet sich selber als „Database for the Web“ und ist sehr nah an WebTechnologien gebaut. Als Interface zur Interaktion mit der Datenbank dient HTTP/REST.
CouchDB bietet im Gegensatz zu MongoDB ein integriertes Browser-Interface, welches das
Durchsuchen und Anlegen von Datensätzen erlaubt 22. Und mit CouchApps lassen sich
sogar Webseiten und (über JSON-Dokumente hinausgehende) Inhalte direkt an den
Browser senden, ohne eine weitere Softwareschicht dazwischen23.
15 Der Begriff der Dokumentendatenbank stammt von der Datenbank Lotus Notes, wo noch echte
Anwenderdokumente in der Datenbank gespeichert wurden [Edlich2011]
16 http://de.wikipedia.org/wiki/JSON
17 https://www.mongodb.org/
18 http://couchdb.apache.org/
19 Bei MongoDB genauer gesagt als BSON, was für „Binary JSON“ steht (siehe http://bsonspec.org/).
20 http://en.wikipedia.org/wiki/Map_reduce
21 Als Ad Hoc Queries bezeichnet man Queries, deren Inhalt erst zum Zeitpunkt der Ausführung in der
Anwendung bekannt ist; im Gegensatz zu vordefinierten Queries wie „zeige alle Datensätze der
Datenbank“; vgl. dazu auch http://www.learn.geekinterview.com/data-warehouse/dw-basics/what-isan-ad-hoc-query.html
22 Bei MongoDB sind hier aber Lösungen von Drittanbietern verfügbar; vgl.
http://docs.mongodb.org/ecosystem/tools/administration-interfaces/
23 Vgl. http://couchapp.org/page/what-is-couchapp
15
CouchDBs Design wurde auf hohe Verfügbarkeit und Datensicherheit ausgerichtet. Zu
jedem Dokument wird nicht nur eine ID abgespeichert, sondern auch eine RevisionsNummer für jeden Änderungszustand des Dokuments seit seiner Entstehung. Jeder Stand
des Dokuments wird mit der dazu gehörigen Revisions-Nummer abgespeichert und ist zur
Abfrage verfügbar. Änderungen an Dokumenten werden nur durchgeführt, wenn der
Benutzer zusätzlich zur Dokumenten-ID auch noch die Revisions-Nummer seiner
aktuellsten Version kennt. Dieses append-only Storage Modell macht die Daten sehr sicher
und ermöglicht eine leichte Replikation und Wiederherstellung, auch wenn Teile des
Netzwerks ausfallen sollten. Ein Nachteil ist, dass die Datenbank-Größe schnell zunimmt,
wenn sich die Daten häufig ändern.
MongoDBs Design wurde stark auf horizontale Skalierbarkeit ausgelegt. Wo bei CouchDB
vor allem vertikale Skalierung durch die Replikation und Bereitstellung der Daten auf
verschiedenen Servern möglich ist, ermöglicht MongoDB zusätzlich die horizontale
Skalierung durch das sogenannte Sharding. Hierbei werden Collections in Teile aufgeteilt,
die dann auf verschiedenen Servern bereitgestellt werden [Redmond2012].
Als weiterer Vertreter in der Gattung dokumentbasierter Datenbanken soll hier noch Riak
erwähnt werden. In dieser Datenbank werden üblicherweise Dokumente gespeichert, diese
jedoch per Key/Value-Funktionalität in sogenannten Bucket-Namensräumen abgespeichert
und in einem Ring-Adressraum verwaltet, weshalb die Entwickler bei Riak von einer
Key/Value-Datenbank sprechen [Edlich2011]. Riak wird deswegen im nächsten Abschnitt
nochmals beschrieben.
2.2.4 Key/Value Datenbanken
Bei Key/Value handelt es sich um ein sehr einfaches Datenbankmodell. Hier werden Keys
mit Values, also jeweils einem zum Key gehörigen Wert, gepaart. Key/Value Datenbanken
sind aufgrund dieser einfachen Datenstruktur ohne Relationen leicht skalierbar. Welcher
Datentyp dabei als Value gespeichert werden kann, variiert dabei von Datenbank zu
Datenbank. Bekannte Vertreter der Key/Value Gattung sind Redis 24 sowie das bereits
erwähnte Riak25, beides Open Source-Projekte.
Redis gilt als sehr schneller Key/Value-Store, da hier alle Daten im RAM gespeichert
werden und nur von Zeit zu Zeit mit der Festplatte synchronisiert werden. Es werden die
unterschiedlichsten Datentypen angeboten, die als Werte gespeichert werden können:
Strings, Hashes, Listen, Sets und sortierte Sets. Des Weiteren werden atomische
24 http://redis.io/
25 http://basho.com/riak/
16
Operationen auf den Datenstrukturen angeboten sowie Message Queues mit publish/
subscribe-Funktionalität.
Bei Riak handelt es sich um eine sehr vielseitige Datenbank. Sie wurde mit den Zielen
Verfügbarkeit, Fehlertoleranz und Skalierbarkeit entworfen. Die eigentliche Speicherengine
ist hier austauschbar. Grundsätzlich werden in Riak Dokumente gespeichert. Diese werden
in sogenannten Bucket-Namensräumen verwaltet, in denen dann die Keys abgelegt werden.
Durch die Möglichkeit, Links zwischen den Dokumenten abzuspeichern, können mit Riak
auch Graphen- oder relationale Strukturen umgesetzt werden. Als Konsistenzmodell wird
hier BASE/Eventually Consistent angewendet und Riak bietet Map/Reduce. Wie bei
CouchDB erfolgt der Zugriff auf die Datenbank immer über REST/HTTP-Anfragen.
Einer der Unterschiede zwischen den beiden Systemen ist jedoch, dass Riak mehr auf die
Skalierung und Verteilung der Daten ausgelegt ist [Edlich2011].
2.2.5 Graphdatenbanken
Graphdatenbanken speichern untereinander vernetzte Strukturen, die aus Knoten und
ihren Verbindungen, den Kanten, bestehen. Zeichnen sich Datensätze durch eine große
Anzahl an Verlinkungen der Einheiten untereinander aus und müssen in der Anwendung
diese Datensätze oft anhand ihrer Verlinkungen durchlaufen (traversiert) werden, so ist
meist eine Graph-Datenbank eine gute Wahl. Im Web-Umfeld werden diese zum Beispiel
oft im Bereich des Social Networking verwendet.
Der wahrscheinlich bekannteste Vertreter der Fraktion der Graphdatenbanken ist Neo4j26.
Es handelt sich bei Neo4J um eine hochskalierbare und gleichzeitig leichtgewichtige Open
Source Datenbank. Als Datenmodell bietet Neo4j Knoten sowie gewichtete Kanten, wobei
beide Typen Eigenschaften in Form von beliebigen Daten annehmen können 27. Die
Datenbank bietet ACID-Konsistenz und eine eigene Query-Language namens Cypher. Der
Zugriff auf die Daten kann abgesehen von Cypher entweder per REST-Interface oder einer
objektorientierten Java-Schnittstelle erfolgen. Ein Browser-Interface mit einer komfortablen
Graph-Visualisierung ist ebenfalls bereits in die Standard-Version integriert.
Des Weiteren soll kurz erwähnt werden, dass es mit dem Tinkerpop Blueprints Projekt
eine standardisierte Open Source API für die Programmiersprache Java gibt, welche einen
einheitlichen Zugriff auf Graph-Datenbanken ermöglicht und die von vielen
Graphdatenbanken, darunter auch Neo4J 28, implementiert wird. Neo4J bietet damit zwei
26 http://www.neo4j.org/
27 Das sich hier hinter verbergende Modell wird auch „Property Graph“ genannt.
28 https://github.com/tinkerpop/blueprints/wiki/Neo4j-Implementation
17
Query-Languages, das bereits erwähnte Cypher, sowie die Abfragesprache Gremlin, die Teil
von Tinkerpop Blueprints ist. Cypher weist dabei eher Ähnlichkeiten mit SQL auf,
während Gremlin mit seinem Collection-orientierten Zugriff Ähnlichkeiten zum DOMZugriff in jQuery aufweist [Redmond2012].
2.2.6 Wide Column Stores
Eine weitere Gruppe im NoSQL-Bereich sind die Wide Column Stores. Diese ähneln den
relationalen Datenbanken. Die Daten werden hier ebenfalls in Tabellen gespeichert, jedoch
anders als in relationalen Datenbanken ist die Speicherung nicht zeilen- sondern
spaltenorientiert. Das bedeutet, dass physisch auf dem Speichermedium nicht die
Datensätze (oder Tupel) hintereinander gespeichert werden, sondern die Attribute einer
Spalte. Dies bietet Vorteile bei der Analyse der Daten, bei der Datenkompression
[Edlich2011] und lässt außerdem zu, dass kein Speicherplatz verschwendet wird, sollten
nicht alle Spalten einer Datenbankzeile mit Werten belegt sein. Weiterhin ist das
Hinzufügen von Spalten wesentlich zeitefzienter als bei zeilenorientierten Datenbanken.
Es gibt aber auch Nachteile. Hierzu zählen der größere Aufwand beim Suchen und
Einfügen von Daten sowie beim Lesen von zusammengehörigen Datensätzen.
Die drei bekanntesten Vertreter der Gattung der spaltenorientierten Datenbanken sind
HBase, Cassandra und Hypertable (hier stimmen [Redmond2012] und [Edlich2011]
überein). Diese Datenbanken orientieren sich allesamt an Googles BigTable 29, weichen
jedoch von der oben beschriebenen Idee etwas ab und bieten eine Art Kombination aus
spaltenorientiertem Design in Verbindung mit Key/Value-Funktionalitäten. Durch den
Einsatz von mehrdimensionalen Tabellen in Kombination mit einer guten Skalierbarkeit
eignen sich diese Datenbanken sehr gut für besonders große Datenmengen.
Wide Column Stores spielen für diese Bachelorarbeit keine besondere Rolle und wurden
deshalb nur der Vollständigkeit halber erwähnt.
2.2.7 Multimodel-Datenbanken
Multimodel-Datenbanken vereinen die Konzepte vieler NoSQL-Datenbanken in einem
System. Neben der Datenbank ArangoDB, die später näher untersucht wird, ist
OrientDB30 ein Vertreter dieser Gattung. Bei OrientDB handelt es sich um eine Open
Source Datenbank, deren Basis eine Dokument-Datenbank ist. Diese wurde jedoch um
Graphen-Funktionalitäten erweitert und so lassen sich genau wie bei Riak in den
Dokumenten auch Links zu anderen Dokumenten abspeichern. OrientDB unterstützt
29 http://de.wikipedia.org/wiki/BigTable
30 http://www.orientdb.org/
18
Tinkerpop Blueprints und somit ist auch die Graphen-Traversierung mittels Gremlin
möglich. Der allgemeine Zugriff ist mittels einer erweiterten Untermenge von SQL als
Abfragesprache, über die native Java-API, sowie über eine REST/HTTP-Schnittstelle
möglich. Weiterhin verfügt OrientDB über ein umfangreiches Rechtemanagement für
Benutzer, es werden ACID-Transaktionen unterstützt und Dokumente können sowohl mit
als auch ohne Schemata benutzt werden sowie zusätzlich in einem gemischten Modus.
Zum Abschluss soll hier erwähnt werden, dass auch die Datenbank Riak über Merkmale
einer Multimodel-Datenbank verfügt. Genau wie in OrientDB werden bei Riak die Daten
in Dokumenten gespeichert und diese können zusätzlich über Verlinkungen verfügen. Riak
unterstützt zwar nicht wie OrientDB die Tinkerpop Graph-API zur Traversierung, es lassen
sich aber sehr wohl Graphen-Strukturen hiermit abbilden. OrientDB kann außerdem
ebenso wie Riak, das in erster Linie als Key/Value-Datenbank gilt, als Key/ValueDatenbank verwendet werden31.
2.2.8 Zusammenfassung
Man sieht, dass die Grenzen bei den Datenmodellen der NoSQL-Datenbanken teilweise
fießend sind und es einige Mischformen gibt, bzw. Aspekte von NoSQL-„Genres“ in
andere übernommen werden. Je nach Anwendungsbereich ist ein bestimmtes Datenmodell
besonders passend. Bei komplexen Anwendungen mit unterschiedlichen Anforderungen
bietet sich die Verwendung mehrerer unterschiedlicher Datenbanktypen nach dem Konzept
der „Polyglot Persistence“ an. In anderen Anwendungsbereichen macht dagegen eher die
Verwendung einer Multimodel-Datenbank Sinn, die die Konzepte mehrerer Typen vereint.
Zu diesem Typ zählt auch die Datenbank ArangoDB, die Gegenstand dieser Arbeit ist.
ArangoDB wird in Abschnitt 2.4 näher erläutert. In Abschnitt 4.4 werden außerdem einige
Clojure Treiber für die bisher vorgestellten NoSQL-Datenbanken verglichen und
untersucht.
31 Vgl. https://github.com/orientechnologies/orientdb/wiki/Key-Value-engine
19
2.3 REST und HTTP
2.3.1 REST Einführung
REST steht für REpresentational State Transfer und ist ein Entwurfsmuster, welches ein
verteiltes System bestehend aus Client und Server extrem skalierbar macht. Als
grundlegende Architektur des Web machte REST dessen enormes Wachstum und dessen
enormen Erfolg erst möglich. Dennoch bietet REST einen hohen Grad von Anpassbarkeit
und lässt Kompromisse zu [Tilkov2009]. Das REST-Prinzip wurde von Roy Thomas
Fielding, der vorher bereits das Protokoll HTTP mitentwickelt hatte 32, in dessen
Dissertation „Architectural Styles and the Design of Network-based Software Architectures“
beschrieben [Fielding2000].
Nach [Tilkov2009] lässt sich REST auf fünf Grundprinzipien reduzieren:
–
Ressourcen mit eindeutiger Identifikation
→ lesbare und manipulierbare Einheiten, die mittels global gültiger und eindeutiger Adressen
(URIs) identifziert werden
–
Unterschiedliche Repräsentationen
→ die Einheiten sind nach außen nur durch ihre Repräsentationen sichtbar und
manipulierbar; für jede Ressource kann es eine Vielzahl an Repräsentationen geben
–
Verknüpfungen/Hypermedia
→ Benutzung von Hypertext33 zur Verknüpfung von Inhalten untereinander
–
Standardmethoden
→ ein Satz von Methoden, der auf alle Ressourcen angewendet werden kann, bildet eine
einheitliche Schnittstelle
–
Statuslose Kommunikation
→ die Verantwortung für die Verwaltung des Applikationsstatus liegt beim Client, dadurch
wird das System deutlich vereinfacht
Bis in die 90er Jahre wurde das World Wide Web vor allem benutzt, um statische
Dokumente abzurufen. Vor allem mit dem Aufkommen vieler dynamischer Websites wurde
der Bedarf nach einer grundsätzlichen Theorie, einem theoretischen Fundament, das dem
World Wide Web zugrunde liegt, immer größer [Tilkov2009]. Roy Fielding hat mit REST
ein einheitliches Konzept für statische und dynamische Inhalte geschaffen: die Ressource.
32 Vgl. [wwwHTTP1999]
33 Die Begriffe Hypertex und Hypermedia werden oft synonym verwendet. Siehe dazu auch
http://de.wikipedia.org/wiki/Hypermedia
20
Im Zusammenhang mit REST spricht man daher auch von einer Ressourcen-orientierten
Architektur (ROA).34
2.3.2 Ressourcen und Repräsentationen
Bei einer Ressource handelt es sich um ein abstraktes Konzept für eine Einheit oder ein
Objekt. Laut [Richardson2007] ist eine Ressource „alles, was wichtig genug ist, um als
eigenständiges Etwas referenziert zu werden“. Möchte ein Nutzer Informationen über eine
Einheit abrufen, Änderungen an ihr vornehmen oder einen Hypertext-Verweis darauf
weiterleiten, sind dies Argumente dafür, etwas als Ressource zu identifizieren. Eine
Ressource kann dabei sowohl ein Dokument sein, ein reales physikalisches Objekt, ein
Eintrag in einer Datenbank (der wiederum die beiden vorgenannten Beispiele abbilden
könnte) oder auch eine Aufistung anderer Ressourcen.
Ressourcen als solche sind nach außen nicht sichtbar. Sichtbar sind stattdessen ihre
sogenannten Repräsentationen. Davon kann jede Ressource mehrere haben. Eine andere
Definition einer Ressource lautet daher auch: „eine durch eine gemeinsame ID
zusammengehaltene Menge von Repräsentationen“ [Tilkov2009]. Bei einer Repräsentation
kann es sich zum Beispiel um ein HTML-Dokument handeln, ein PDF-Dokument, ein
JSON-Dokument oder auch ein Bild. Repräsentationen werden auch benötigt, um
Ressourcen zu verändern; ein Beispiel hierfür könnte ein Formular sein, mit dem man die
Eigenschaften einer Ressource verändern kann.
Um Ressourcen zu identifizieren werden Uniform Resource Identifer35, kurz URIs, benutzt.
Hierbei handelt es sich um „Adressen“, welche global gültig und einzigartig sind. Jeder URI
identifiziert hierbei genau eine Ressource. Umgekehrt können aber auch mehrere URIs auf
dieselbe Ressource verweisen [wwwW3CArchitecture].
2.3.3 Die Bestandteile von REST
Bei REST handelt es sich um einen „Hybrid-Style“ für verteilte Systeme, der aus diversen
netzwerkbasierten Architekturstilen abgeleitet wurde und mit zusätzlichen Einschränkungen versehen wurde [Fielding2000]. Die wichtigsten Bestandteile sollen nun hier
kurz erläutert werden.
34 Genauer gesagt ist die ROA ein Weg, eine REST-konforme Architektur umzusetzen, da sie bereits
Gebrauch von konkreten Konzepten wie URIs und HTTP macht [Richardson2007].
35 Siehe auch http://de.wikipedia.org/wiki/Uniform_Resource_Identifier
21
Client-Cache-Stateless-Server
Ausgangspunkt bei REST ist das Client-Server Architekturmuster 36. Als nächste wichtige
Einschränkung wird festgelegt, dass die Kommunikation im System zustandslos sein soll,
womit das System das „Client-Stateless-Server“ Muster umsetzt. Diese Einschränkung
bedingt, dass jede Anfrage vom Client an den Server alle nötigen Informationen beinhalten
muss, um die Anfrage vollständig zu verstehen. Es wird somit auf dem Server kein für die
Kommunikation wichtiger Zustand gespeichert und der Client trägt die Verantwortung,
diesen zu verwalten. Durch diese Einschränkung wird das System weitaus verlässlicher und
skalierbarer. Da sich der Zustand auf dem Client befindet, können auch unterschiedliche
Server-Maschinen die Anfragen beantworten und es macht keine Probleme, sollte einer
ausfallen. Außerdem werden auf dem Server weniger Ressourcen verbraucht, wenn keine
Zustände gespeichert werden müssen und die Implementierung des Servers wird deutlich
einfacher.
Dadurch dass zusätzlich bei jeder Datenübermittlung angegeben wird, ob die Daten
cacheable37 sind, wird die Efzienz noch weiter erhöht, weil insgesamt weniger Daten
übermittelt werden müssen (wenn sich diese nicht ständig ändern). Zusätzlich wird die
Latenzzeit für viele Aktionen verringert.
Layered-System und Code-on-Demand
Zwei weitere Entwurfsmuster, die in REST mit eingefossen sind, sind das Layered-System
Muster und das Code-on-Demand Muster. Beim Layered-System Muster kann die
Architektur aus mehreren hierarchischen Ebenen bestehen, die jeweils nur Kenntnis von
einer weiteren Ebene besitzen. Mit dieser interagieren sie.
Das Code-on-Demand Muster propagiert, dass die Funktionalität des Clients dynamisch
erweitert werden kann. Zusätzlicher Code wird vom Server ausgeliefert und auf dem Client
ausgeführt. Dies gehört zu den Grundfunktionalitäten des World Wide Web, denn bei den
meisten Websites, die im Browser betrachtet werden, wird zusätzlicher Javascript Code vom
Server geladen und im Browser ausgeführt. So muss der Client von sich aus über keine
Informationen verfügen, wie er die vom Server gesendeten Daten verarbeiten kann,
sondern der Server liefert gewissermaßen die „Anleitung“ dazu gleich mit. Das gesamte
System wird dadurch sehr fexibel und beliebig erweiterbar.
Uniform Interface
REST propagiert die lose Kopplung durch eine „uniforme Schnittstelle“ [Fielding2000].
Mittels dieser können Ressourcen und deren Repräsentationen abgerufen und manipuliert
36 Siehe http://en.wikipedia.org/wiki/Client-server_model
37 Siehe http://de.wikipedia.org/wiki/Cache
22
werden. Jede Ressource muss dabei den gleichen Satz von Methoden unterstützen (daher
auch uniform = einheitlich). Durch die Nutzung dieser einheitlichen Schnittstelle werden
Abhängigkeiten vermieden und Teile des Systems leichter austauschbar, da die
Implementierung jeweils unter dem Interface verborgen ist.
2.3.4 REST, HTTP und die HTTP-Methoden
Bei der Entwicklung von REST wurden die Aspekte der Implementierung ausgeblendet
und stattdessen der Blick vollständig auf das System und dessen Eigenschaften als Ganzes
konzentriert. In der Praxis tritt REST meist im Zusammenhang mit dem Protokoll HTTP
auf38. HTTP bietet eine Schnittstelle um das REST Paradigma anzuwenden.
Vielen ist HTTP nur als einfaches Protokoll zum Abrufen von Websites bekannt. In
Wahrheit verfügt HTTP jedoch über mehr Fähigkeiten. Das Protokoll bietet eine Anzahl
von Operationen, die auch als Verben bezeichnet werden und für alle Ressourcen
gleichermaßen gültig sein sollen. Man spricht daher von der bereits erwähnten „uniformen“
Schnittstelle. Über diese Verben soll nun ein kurzer Überblick gegeben werden.
GET
GET ist die grundlegende und am häufigsten verwendete Operation von HTTP. Sie wird
bei jeder Anfrage eines Webbrowsers nach einer Website, also einem HTML-Dokument,
benutzt. Allgemeiner gesagt fragt sie eine Repräsentation einer Ressource ab. REST folgend
müssen GET Anfragen vom Client beliebig wiederholt werden können ohne eine
Änderung am Zustand des Servers hervorzurufen. Man sagt deshalb, dass die Methode
sicher (safe) ist. Der Client fordert keine Änderung am Zustand des Servers an und geht
somit auch keine Verpfichtungen ein. Ein Seiteneffekt in Form zum Beispiel eines Eintrags
in eine Logdatei auf dem Server ist jedoch durchaus möglich [Tilkov2009]. Selbst
Webanwendungen, die nicht das komplette REST Paradigma implementieren wollen,
sollten sich an diese Regel halten, sonst könnten zum Beispiel schon einfache Webcrawler
mittels Anfragen wie http://www.example.com/ressource/?action=delete große Schäden in der
Anwendung anrichten [Edlich2011].
HEAD
HEAD hat dieselben Eigenschaften wie GET, liefert aber statt der ganzen Repräsentation
nur die Metainformationen über eine Ressource zurück. Laut Spezifikation müssen genau
dieselben Daten im HTTP-Header zurück gesendet werden wie bei einer GET-Anfrage,
nur dass der üblicherweise dazugehörige Daten-Body nicht mitgesendet wird. Somit kann
38 Wie oben schon erwähnt wurde der HTTP Standard maßgeblich von Roy Fielding, dem auch REST zu
verdanken ist, mitgestaltet.
23
zum Beispiel die Existenz einer Ressource überprüft werden oder Informationen über den
Umfang einer Ressource eingeholt werden, bevor diese wirklich übertragen wird.
PUT
Im Gegensatz zur Abfrage mit GET können Ressourcen mit PUT geschrieben, das heißt
neu angelegt oder geändert werden. Ein PUT-Befehl überträgt eine neue Repräsentation
einer Ressource oder seine geänderten Eigenschaften an den Server. Die Methode ist
„idempotent“, das heißt wird der Befehl mehrere Male mit denselben Argumenten
aufgerufen, so muss dies immer zum selben Zustand auf dem Server führen.
POST
Mittels der POST Methode überträgt der Client Daten zur Verarbeitung an den Server. Die
Ergebnisse dieser Verarbeitung können zum Anlegen oder zur Änderung von Ressourcen
führen oder auch komplett seiteneffektfrei sein. POST ist damit die Methode der Wahl um
beliebige Funktionalitäten umzusetzen, die in der HTTP-Spezifikation nicht vorgesehen
sind und in denen der Client Daten an den Server senden muss. Als Alternative zur
Übertragung von Daten bietet sich noch GET an, hier müssen jedoch alle Daten komplett
im URI codiert werden. Dies kann aber zu Problemen führen und die Datenmenge ist hier
begrenzt. Weiterhin würde beispielsweise beim Ändern von Ressourcen die Einschränkung
unter Umständen nicht mehr eingehalten werden, dass GET zu keiner Änderung am
Zustand des Servers führen kann.
Im Umgang mit Ressourcen nach dem REST-Ansatz hat sich etabliert, dass zwischen den
beiden schreibenden Methoden PUT und POST folgendermaßen unterschieden wird:
POST legt neue Ressourcen an und und PUT ändert sie39. Ein grundsätzlicher Unterschied
zwischen beiden Methoden ist, dass man bei PUT die Anfrage an den URI der
anzulegenden/zu ändernden Ressource sendet, während bei POST die Anfrage an den URI
der für die Datenverarbeitung zuständigen Ressource gesendet wird. Der URI einer evtl.
neu angelegten Ressource kann dann vom Server bestimmt werden und wird im HTTPHeader der Antwort zurück gesendet. PUT eignet sich dagegen auch für das Neuanlegen
von Ressourcen unter einem durch den Client vorgegebenen URI.
PATCH
Bei PATCH handelt es sich um eine zusätzliche HTTP-Methode, die in [wwwHTTP2010]
vorgeschlagen wurde, um Ressourcen zu ergänzen bzw. teilweise zu ändern. Mit der PUTMethode wird immer die gesamte Ressource ausgetauscht; bei Ergänzungen erfordert dies
somit zunächst eine GET-Anfrage, um den aktuellen Stand der Ressource zu erfragen.
39 Vgl. z.B. Ruby on Rails Guides: http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions
24
Diese Anfrage erspart man sich mit PUT, da man direkt Ergänzungen vornehmen kann
ohne die Ressource als Ganzes übersenden zu müssen. Des Weiteren ist die Gefahr geringer,
dass Änderungen verloren gehen, sollten mehrere Änderungen in einem kurzen Zeitraum
gesendet werden.40
DELETE
Wie der Name schon verrät, bewirkt DELETE die Löschung von Ressourcen. DELETE ist
genau wie PUT idempotent, denn da eine Ressource nur einmal gelöscht werden kann,
führen mehrere Aufrufe auf derselben Ressource zum gleichen Zustand auf dem Server.
TRACE, OPTIONS und CONNECT
Es gibt in der HTTP Spezifikation noch drei weitere Methoden: TRACE, OPTIONS und
CONNECT. Diese spielen jedoch keine wesentliche Rolle für Webanwendungen nach dem
REST-Prinzip. Sie sollen jedoch trotzdem hier kurz erwähnt werden: Mit der OPTIONS
Methode können Metadaten über eine Ressource angefordert werden, unter anderem
darüber, welche der HTTP-Methoden von ihr unterstützt werden. TRACE dient zur
Diagnose von HTTP-Verbindungen und liefert die Anfrage genau so zurück, wie sie vom
Server empfangen wurde. So kann festgestellt werden, ob sie eventuell auf dem Weg
verändert worden ist. CONNECT dient zur Initiierung einer Proxy-Verbindung durch
einen SSL-Tunnel.
2.3.5 HTTP-Header
Außer der HTTP-Methode, dem URI und dem Datenteil (Body) bestehen HTTPAnfragen und Antworten noch aus dem weiter oben bereits erwähnten Header. Hier
können Metainformationen in Form von Schlüssel/Wert-Paaren übergeben werden. Diese
Paare können beliebig sein, die meisten Headerfelder sind jedoch standardisiert
([wwwHTTP1999] und [wwwHTTP2005]). Sie erlauben zum Beispiel Angaben über
akzeptierte Antwortformate (Accept)41, über akzeptierte Sprachen der Antwort (AcceptLanguage) oder die Steuerung des Caching-Verhaltens, das zum Beispiel auch ganz verboten
werden kann (Cache-Control: no-cache). Einige Parameter werden genutzt um konditionale
GET-Anfragen zu senden, so wird einem Server zum Beispiel mittels des Headerfeldes IfModifed-Since erlaubt, bei unveränderten Daten nur einen Header mit dem Statuscode
304 (Not Modified)42 und ohne Daten zurückzusenden. So kann das unnötige Senden von
40 Siehe auch: http://www.mnot.net/blog/2012/09/05/patch
41 Um wieder den Bogen zum Konzept der Ressource zu schlagen kann man die Accept-Angabe auch so
sehen: Der Client entscheidet über die Art von Repräsentation(en), die er von der adressierten
Ressource zugesendet bekommen möchte. Dies kann aber auch schon durch den URI festgelegt sein,
wenn dieser z.B. eine Dateiendung wie .html enthält.
42 Weitere Informationen zu den Statuscodes finden sich unter http://de.wikipedia.org/wiki/HTTPStatuscode
25
großen Datenmengen vermieden werden, wenn sich nichts an der Repräsentation geändert
hat.
2.3.6 REST und NoSQL
Viele NoSQL Datenbanken, darunter auch ArangoDB, bieten REST/HTTP-Schnittstellen
zur Interaktion mit der Datenbank. Einige NoSQL Datenbanken verfolgen sogar intern
einen REST-Entwurfsansatz. Von den in Abschnitt 2.2 vorgestellten Datenbanken zählen
hierzu CouchDB, Riak und Neo4J. Bei allen diesen Datenbanken läuft der primäre ClientZugriff über REST/HTTP-Anfragen. Bei CouchDB und Neo4J gibt es dazu, ebenso wie
bei ArangoDB, ein integriertes Browser-Interface zur Verwaltung der Datenbank, welches
ebenfalls von der HTTP-Schnittstelle Gebrauch macht.43
Im nächsten Abschnitt wird nun näher auf die Datenbank ArangoDB eingegangen, die
Gegenstand dieser Arbeit ist. Neben einer grundlegenden Beschreibung ihrer Eigenschaften
wird auch ihre REST/HTTP-Schnittstelle näher untersucht.
43 Bei Riak sind ebenfalls Browser-Interfaces verfügbar, allerdings nur als Community-Projekte, die
nicht standardmäßig mitgeliefert werden.
26
2.4 ArangoDB
Bei ArangoDB handelt es sich um eine Multimodel Open Source NoSQL-Datenbank. Die
Datenbank unterstützt die Datenmodelle Dokumente, Key/Value und Graphen. Es handelt
sich somit um eine Art Mischung aus den in Abschnitt 2.2 vorgestellten Datenbanktypen.
Das Projekt ArangoDB wurde im Jahr 2011 von der Firma triAGENS 44 gestartet. Im
Frühling 2012 wurde Version 1.0 veröffentlicht und die Datenbank befindet sich nach wie
vor in stetiger Weiterentwicklung, sodass allein während des Entstehens dieser Arbeit
mehrere neue Versionen veröffentlicht wurden. Die in dieser Arbeit betrachtete Version ist
1.4.* (Version 1.4.0 wurde am 30.10.2013 veröffentlicht45).
Im Folgenden sollen die Eigenschaften von ArangoDB näher untersucht werden, um eine
Einordnung in das Feld der NoSQL-Datenbanken vornehmen zu können. Es soll auch
versucht werden, Parallelen und Unterschiede zu den in Abschnitt 2.2 beschriebenen
Datenbanken aufzuzeigen. Da es sich bei ArangoDB zuallererst um eine DokumentDatenbank handelt, soll der Vergleich vor allem mit CouchDB und MongoDB erfolgen.
2.4.1 Eigenschaften und Designziele
ArangoDB wird von seinen Entwicklern als „Database for the Web“ bezeichnet und von
ihnen außerdem zu einer „zweiten Generation von NoSQL-Datenbanken“ gezählt
[wwwArangoTalk1]. Die Datenbank ist eine Art „Allzweckwaffe“, die möglichst viele
Möglichkeiten der Anpassung an die sich stetig verändernden Anforderungen einer WebAnwendung im Laufe ihrer Entwicklung bieten soll.
ArangoDB bietet unter anderem verschiedene Möglichkeiten der Skalierung, verschiedene
Konsistenzmodelle, verschiedene Möglichkeiten der Abfrage von Daten sowie verschiedene
Möglichkeiten der Indizierung von Dokumentattributen. Sollte sich beispielsweise
während der Entwicklung einer Anwendung herausstellen, dass auch die Möglichkeit des
Durchsuchens von Datensätzen nach der geografischen Lage benötigt wird, kann in
ArangoDB auch eine Indizierung nach Geokoordinaten erfolgen [wwwArangoTalk2].
Ebenso sind Möglichkeiten der Datenreplikation vorhanden, es wird aber ausdrücklich
nicht versucht, eine horizontale Skalierbarkeit in Dimensionen, wie sie zum Beispiel mit
Riak oder MongoDB möglich ist, umzusetzen.46
44 http://de.triagens.com/
45 https://www.arangodb.org/2013/10/30/arangodb-1-4-0-released
46 Hier sollte aber erwähnt werden, dass ArangoDB ab Version 2 auch das sogenannte Sharding
unterstützt, womit eine horizontale Skalierung möglich wird. Siehe dazu auch Kapitel 7 „Fazit und
Ausblick“.
27
Eine der Hauptstärken von ArangoDB liegt in der Verfügbarkeit der verschiedenen
Datenmodelle. So muss nicht gleich auf eine zusätzliche Datenbank zurückgegriffen
werden, sollte sich beispielsweise während der Entwicklung einer Anwendung herausstellen,
dass nicht nur Dokumente, sondern auch Graphenstrukturen abgespeichert werden sollen.
Insbesondere bietet ArangoDB daher die Möglichkeit, das Konzept der Polyglot
Persistence mit nur einer Datenbank umzusetzen, sodass der technische Verwaltungsaufwand viel geringer ist als beim Einsatz mehrerer Datenbank-Systeme.
ArangoDB erlaubt in erweitertem Maße die Benutzung von JavaScript um mit der
Datenbank zu arbeiten, nicht nur auf der Client- sondern auch auf der Server-Seite. Hierzu
wird auf der Server-Seite von Googles JavaScript-Engine V8, die auch in Google Chrome
zum Einsatz kommt, Gebrauch gemacht. Der Kern von ArangoDB ist in C/C++ sowie
teilweise auch in JavaScript implementiert.
Der Server von ArangoDB arbeitet mit mehreren nebenläufig ausgeführten Threads und ist
für die Ausführung auf Multiprozessor-Systemen optimiert [ArangoDBBlog2]. DatenbankAnfragen können somit von mehreren Threads gleichzeitig bearbeitet werden. ArangoDB
unterstützt „blocking“ und „non-blocking“ Requests. Bei der optionalen Nutzung von
non-blocking Requests werden die Requests auf dem Server in einer Queue gespeichert; der
Client muss dann nicht auf eine Antwort des Servers warten, sondern kann gleich weitere
Anfragen schicken.
Eine Besonderheit von ArangoDB sind die sogenannten „schema-free Schemata“.
Gleichartige Dokumente in einer Collection werden von ArangoDB automatisch erkannt
und platzefzient abgespeichert. Gleichzeitig hat man weiterhin die Freiheit, komplett
schemafrei zu arbeiten [ArangoDBBlog1]. Bei diesem Ansatz werden die Vorteile der
Schemafreiheit einer Dokumenten-Datenbank kombiniert mit den Vorteilen von
Schemata, wie sie aus relationalen Systemen bekannt sind.
2.4.2 Speichereffizienz und Performance
ArangoDBs Design ist laut seiner Entwickler nicht auf Performance ausgelegt, sondern auf
vielseitige Anwendungsmöglichkeiten. Trotzdem erhält die Datenbank bei Performanceund Speicherverbrauchs-Vergleichen mit anderen Datenbanken durchaus gute Werte.
Laut den Tests in [wwwArangoBlog3] verbraucht ArangoDB bei einer großen Anzahl an
Datensätzen in der Datenbank durchweg weniger Speicherplatz als MongoDB. Dies ist
wohl vor allem der impliziten Schema-Erkennung (schema-free Schemata) bei ArangoDB
28
zu verdanken. Bei MongoDB und CouchDB werden die Strukturinformationen für jedes
Dokument redundant abgespeichert, während bei ArangoDB die Struktur bei gleich
aufgebauten Dokumenten automatisch erkannt wird und pro Collection nur einmal
gespeichert werden muss. Bei CouchDB ist dagegen aber eine Kompression der Daten
möglich, womit der benötigte Speicherplatz auch teilweise unter dem Niveau von
ArangoDB liegen kann.
Da bei ArangoDB alle Kommunikation zwischen Server und Client über HTTP-Requests
läuft, ist die Geschwindigkeit des HTTP-Layers der Datenbank von entscheidender
Wichtigkeit für die Gesamt-Performance der Datenbank. In [wwwArangoBlog4] wurde die
HTTP-Performance von ArangoDB in einigen Benchmark-Tests mit der einiger gängiger
Webserver verglichen47. Es zeigte sich, dass die Performance von ArangoDB im FileserverModus mit der gängiger Webserver mithalten kann. In Testfällen mit einer sehr hohen
Anzahl an gleichzeitig geöffneten Client-Verbindungen schlägt die Performance von
ArangoDB sogar die der anderen Webserver. Der die Performance betreffende nächste
„Konkurrent“ war hierbei der Webserver nginx, der bei wenigen gleichzeitig geöffneten
Verbindungen teilweise mehr Anfragen pro Sekunde beantworten konnte.
Die HTTP-Performance von ArangoDB kann außerdem noch verbessert werden durch die
Nutzung von Batch-Requests. Hierbei werden mehrere Datenbank-Anfragen im Body
einer HTTP-Anfrage übermittelt. Laut [wwwArangoBlog5] kann dadurch die benötigte
Zeit für das Einfügen und Ändern von Dokumenten um 80% reduziert werden 48. Im
Vergleich mit MongoDB liegt die Performance von ArangoDB bei Batch-Requests meist
nah an der von MongoDB; wobei ArangoDB bei großen Datensätzen auch schneller sein
kann als MongoDB. Die Bearbeitungszeit von Batch-Requests bei CouchDB betrug in
vielen Tests ein Vielfaches derer von ArangoDB und MongoDB [wwwArangoBlog6].49
2.4.3 Concurrency, Transaktionen, Skalierbarkeit und Replikation
Als Concurrency-Control Strategie kommt bei ArangoDB Append-Only/MVCC (letzteres
steht für „Multi-Version-Concurrency-Control“) zum Einsatz. Hierbei wird ein Dokument
bei einem Schreibvorgang nicht blockiert, sondern eine neue Version des Dokumentes
47 Hierbei handelte es sich ausdrücklich nur um vergleichende Tests, d.h. es bestand kein Interesse, die
absolute Performance der Produkte zu messen.
48 Dies ist jedoch abhängig vom Use-Case. Es ergeben sich vor allem Vorteile wenn eine größere Anzahl
an Requests mit jeweils wenig Daten gesendet werden.
49 Zu beachten ist hier einerseits, dass MongoDB nicht mit dem HTTP-Protokoll, sondern mit einem
binären Protokoll arbeitet, was der Datenbank einen initialen Performance-Vorsprung gibt;
Andererseits, dass in den Tests die Daten-Kompression bei CouchDB ausgeschaltet war, um die
Performance zu erhöhen, womit dann gleichzeitig die Vorteile des niedrigen Speicherverbrauchs von
CouchDB verloren gingen.
29
inklusive einer Versionsnummer erzeugt. So kann ein Lesezugriff zu jeder Zeit erfolgen,
auch wenn das Dokument gerade geändert wird. Ältere Versionen des Dokumentes werden
dann von einem Garbage-Collection Prozess regelmäßig gelöscht [ArangoDBBlog2]. Bei
ArangoDB sind hierdurch auch konditionale Schreibvorgänge möglich, wie zum Beispiel
„ändere ein Dokument nur, wenn die letzte Version die Versionsnummer 123456 hat“.
Dieses Vorgehen entspricht in etwa der Strategie bei CouchDB. Hier können im Gegensatz
zu ArangoDB jedoch auch mehrere konkurrierende Schreibvorgänge gleichzeitig
durchgeführt werden; eventuell entstehende Konfikte werden dann in einem MergeProzess beseitigt [Edlich2011].
Der MVCC-Ansatz erinnert zudem auch an die Concurrency-Strategie von Clojure. Hier
gibt es unveränderliche Datenstrukturen, die nur durch das Erstellen einer neuen,
aktualisierten Version „geändert“ werden können. Hierdurch wird ein sicheres
nebenläufiges Arbeiten möglich. Nicht mehr benötigte ältere Versionen der Daten werden
daraufhin von der Garbage-Collection50 der Java Virtual Machine gelöscht.
Seit Version 1.3 unterstützt ArangoDB zusätzlich auch ACID-Transaktionen. ACID steht
für „Atomic, Consistent, Isolated, and Durable“. Dies bedeutet, dass jede Transaktion
entweder vollständig ausgeführt wird oder gar keinen Effekt hat. Erst bei einem
erfolgreichen Abschluss einer Transaktion wird das Ergebnis nach außen sichtbar und dann
persistent abgespeichert. Bei ArangoDB werden die Transaktionen als Ganzes an den Server
geschickt, dort ausgeführt und anschließend eine Meldung an den Client über Erfolg oder
Misserfolg der Transaktion gesendet. Dieses Vorgehen unterscheidet sich von Transaktionen
in SQL, wo auch während einer Transaktion Kommunikation zwischen Client und Server
stattfinden kann [wwwArangoFAQ].
ArangoDB wird von seinen Entwicklern auch als „mostly-memory database“ bezeichnet.
Damit gemeint ist, dass die Performance besonders gut ist, wenn die gesamten Daten in
den RAM der ausführenden Maschine passen und die Datenbank nicht gezwungen ist,
Daten zwischen RAM und Festplatte hin- und her zu kopieren [wwwArangoFAQ].
Um die Festplatten-Synchronisation zu kontrollieren, bietet ArangoDB die zwei
Optionen „eventual“ und „immediate“. Diese beiden Optionen können jeweils pro
Collection dauerhaft konfiguriert werden, sowie auch bei jeder Aktion individuell. Bei
„immediate“ wird die Änderung sofort nicht nur im RAM gespeichert, sondern auch auf
der Festplatte gesichert. Erst wenn dies erfolgreich geschehen ist, sendet der Server eine
50 Vgl. http://en.wikipedia.org/wiki/Garbage_collection_(computer_science)
30
Antwort an den Client. Bei „eventual“ wird dagegen die Änderung zunächst nur im RAM
durchgeführt. Die Synchronisation mit der Festplatte erfolgt dann erst später im
Hintergrund. Insgesamt bedeutet dies einen Gewinn an Geschwindigkeit, da weniger
System-Anfragen durchgeführt werden. Dieses Vorgehen kann jedoch auch zu
Datenverlusten führen, sollte es beispielsweise zu einem Absturz des Systems kommen
[wwwArangoFAQ].
Als Strategie für die Replikation bietet ArangoDB die sogenannte asynchrone
Master/Slave-Replikation. Hierbei können die Datenbanken jeweils als Master oder als
Slave konfiguriert werden. Der Client kann dann Leseanfragen an alle Datenbanken
senden, Schreibanfragen jedoch nur an den Master. Änderungen am Master können
daraufhin von den Slaves aus dessen Log gelesen werden und jeweils auf die eigenen Daten
angewendet werden. Die Daten sind somit „eventual consistent“ [wwwArangoManRep].
Der Gewinn bei der Master/Slave-Replikation liegt in der „Lese-Skalierung“ und außerdem
in der Möglichkeit von „Hot Backups“. Es sollen aber laut der Entwickler bei ArangoDB
bewusst keine Features für unbegrenztes horizontales Skalieren umgesetzt werden. Generell
ist die Datenbank dafür konzipiert, dass alle Daten auf einen Server passen. In der
Abwägung zwischen Skalierbarkeit und guten Abfragemöglichkeiten geht ArangoDBs
Design eher in Richtung der Abfragemöglichkeiten. Diese Entscheidung steht im
Gegensatz beispielsweise zum Design von Riak, dessen Abfragemöglichkeiten nicht sehr
umfangreich sind (Key/Value, Volltext-Suche und Map/Reduce). Dafür ist die Datenbank
aber hochskalierbar.
2.4.4 ArangoDBs HTTP/REST-Interface
ArangoDB kommuniziert mit der Außenwelt, das heißt mit allen Clients, durch sein
HTTP-Interface. Dieses unterstützt die HTTP-Methoden GET, POST, PUT, DELETE
und PATCH sowie die HTTP-Versionen 1.0 und 1.1, wobei Antworten vom Server jedoch
immer in Version 1.1 erfolgen. Daten werden im Body des HTTP-Requests im JSONFormat an den Server gesendet und werden ebenfalls ausschließlich als JSON von diesem
zurückgegeben. Des Weiteren werden sowohl Standard- als auch einige Custom-Header
Parameter sowie einzelne Parameter auch als URI-Parameter unterstützt.
2.4.5 Datenbanken, Collections, Dokumente und Graphen in ArangoDB
Im Folgenden sollen kurz die Eigenschaften der in ArangoDB vorhandenen Datenstrukturen zur Organisation von Daten erläutert werden.
31
Datenbanken
Datenbanken stellen in ArangoDB die oberste Hierarchieebene zur Organisation von
Daten dar. Jeder ArangoDB Server kann mehrere Datenbanken enthalten, wobei immer
mindestens eine Datenbank namens _system vorhanden ist, die „System-Datenbank“
genannt wird. Jede Datenbank besteht aus Collections und datenbankspezifischen WorkerProzessen [wwwArangoAPIDB]. Die Collections unterteilen sich in User-Collections, die
vom User selbst erstellt werden und System-Collections, die interne Informationen
enthalten, wie beispielsweise Angaben über User und über Replikation.
Werden Aktionen über die REST/HTTP-API ausgeführt, so erfolgen diese immer im
Kontext einer Datenbank. Die Adresse einer API-Ressource wird üblicherweise wie folgt
aufgebaut:
http://server:port/_db/<database-name>/...<API-method>
zum Beispiel:
http://localhost:8529/_db/mydb/...<API-method>
Die Datenbank muss jedoch nicht in jeder URI explizit angegeben werden. Wenn keine
Datenbank angegeben ist, wird die Aktion standardmäßig im Kontext der SystemDatenbank ausgeführt. Weiterhin gibt es ein sogenanntes „Database-to-Endpoint
Mapping“, das heißt für jeden Port kann eine Liste von Datenbanken angegeben werden,
die über diesen Port erreichbar sind. Wird eine API-Anfrage an einen Port gesendet, für den
das Database-to-Endpoint Mapping konfiguriert ist, so wird standardmäßig die erste
Datenbank in dieser Liste verwendet. Auf die API-Methoden zur allgemeinen DatenbankKonfiguration kann nur im Kontext der _system Datenbank zugegriffen werden.
Collections
Collections entsprechen vom Konzept her in etwa den Tabellen der relationalen
Datenbanksysteme und stellen in ArangoDB die nächste Hierarchieebene unter den
Datenbanken dar. Genau wie auch in MongoDB können Dokumente in ArangoDB nur
innerhalb einer Collection existieren. In CouchDB dagegen werden die Dokumente direkt
auf der Datenbank-Ebene abgelegt.
Collections können und sollten benutzt werden, um Dokumente logisch zu gruppieren.
ArangoDB bietet hier die bereits erwähnte Besonderheit der impliziten Schema-Erkennung,
das heißt je größer die Ähnlichkeiten in der Struktur von Dokumenten innerhalb einer
Collection, desto platzefzienter können diese abgespeichert werden.
32
Collections können zwei Typen haben: document, wobei die Collection dann normale
Dokumente speichert, sowie edge; In letzterem Fall kann die Collection als Edge-Collection
für einen Graphen dienen (siehe unten).
Dokumente
Dokumente in ArangoDB sind JSON-Objekte, die Listen enthalten können und unendlich
tief geschachtelt werden können [wwwArangoAPIDoc]. Jedes Dokument wird eindeutig
identifiziert durch sein Document-Handle in der Form myusers/2345678. Der Teil vor dem
Schrägstrich ist hierbei der Name der Collection und der Teil hinter dem Schrägstrich der
Document-Key. Das Document-Handle wird in jedem Document unter dem Key „_id“
abgespeichert und der Document-Key selber noch einmal unter dem Key „_key“51. Beim
Document-Key handelt es sich um einen in der Collection einzigartigen Key, den der
Benutzer bei der Erstellung des Dokumentes selber angeben kann, oder der automatisch
über einen auf Collection-Ebene konfigurierbaren Key-Generator erstellt wird. Als drittes
vom System kommendes Attribut gibt es noch die Revisions-Nummer, die unter „_rev“
abgespeichert wird. Die Attribute _id und _key sind unveränderbar, sobald das Dokument
einmal erstellt wurde. Die Revisions-Nummer identifiziert die Version des Dokumentes
und wird jeweils neu zugewiesen, wenn das Dokument geändert wurde.
Alle Dokumente in einer Collection vom Typ edge haben außerdem jeweils ein „_from“ und
ein „_to“ Attribut. Diese enthalten jeweils einen Key eines anderen Dokumentes, auf das
verwiesen wird.
Der Dokument-Zugriff über die HTTP-API erfolgt mit einem URI im Format:
http://server:port/_db/<database-name>/_api/document/<document-handle>
zum Beispiel:
http://localhost:8529/_db/mydb/_api/document/demo/362549736
Graphen
Zur Repräsentation von Graphenstrukturen wird auf die bisher bereits vorgestellten
Strukturen zurückgegriffen. Graphen bestehen aus jeweils einer Collection vom Typ edge,
die die Kanten des Graphen enthält, sowie einer Collection vom Typ document, die die
Knoten des Graphen enthält. Als Knoten dienen somit normale Dokumente und als
Kanten die Dokumente der Collection vom Type edge, die mit ihren zusätzlichen
Attributen _to und _from jeweils eine Verbindung zwischen zwei Knoten repräsentieren.
51 Bei den Dokument-Attributen, die mit einem Unterstrich beginnen, handelt es sich in ArangoDB um
reservierte Keys [wwwArangoAPINaming].
33
2.4.6 Querying
In ArangoDB gibt es vier unterschiedliche Methoden der Abfrage und des Findens von
Dokumenten [wwwArangoTalk1]. Die einfachste Methode ist die Abfrage mit einem
bekannten Document-Key.
Ist der Key und damit das genaue Dokument nicht bekannt, hat man grundsätzlich zwei
Arten der Suche zur Auswahl. Für einfache Suchen bietet sich das „Querying by Example“
an, bei dem ein Beispiel-Dokument an den Server gesendet wird. Dieses enthält die
Attribute, nach denen gesucht werden soll. Der Server gibt dann alle Dokumente zurück
die diese Attribute ebenfalls enthalten. Diese Art der Suche kann immer nur im Kontext
einer Collection durchgeführt werden.
Für komplexere Suchanfragen, bei denen auch Joins über mehrere Collections hinweg
möglich sind, bietet ArangoDB seine eigene Query-Sprache namens AQL (für „ArangoDB
Query Language“). Diese weist Ähnlichkeiten zu JSONiq 52 und zu SQL auf. Es wurden
aber explizit andere Keywords als bei SQL verwendet, um eine Verwechslung der beiden
Sprachen durch den Benutzer zu vermeiden [wwwArangoTalk1]. Zusätzlich zu Joins
werden in AQL außerdem Helper-Funktionen angeboten wie for-in-Schleifen und StringVerkettungen53. Die Ergebnisse der Anfragen werden als Cursor zurückgegeben, über die
iteriert werden kann; das heißt es werden nicht alle gefundenen Daten auf einmal
zurückgegeben.
Zum Vergleich: Suchanfragen bei CouchDB sind nur per Map/Reduce möglich, was recht
aufwendig ist, da dies immer die Programmierung von JavaScript-Funktionen verlangt. Bei
MongoDB erfolgt die Abfrage immer per JSON, was für komplizierte Suchanfragen schnell
unübersichtlich werden kann. Daher haben sich die Entwickler von ArangoDB für die
Entwicklung einer eigenen Abfragesprache entschieden.
Für Suchanfragen, die so komplex sind, dass sie auch durch AQL schwer auszudrücken
sind, bietet sich weiterhin noch das Formulieren von Anfragen in Form von eigenem
JavaScript Code an. Dieser kann in Form von Graph-Traversierungen an den Server
gesendet werden oder auch über Foxx (siehe unten) als Ressource bereit gestellt werden.
52 http://www.jsoniq.org/
53 Für Beispiele von AQL-Abfragen siehe:
https://www.arangodb.org/manuals/current/AqlExamples.html
34
2.4.7 Indizierung
Für die Indizierung von Dokumenten und damit die Beschleunigung der Suche nach
Dokumenten gibt es bei ArangoDB verschiedene Möglichkeiten: Hash Indices werden
benutzt, um Dokumente nach Beispiel zu durchsuchen (Querying by Example). Sie
können für ein oder mehrere Attribute des Dokuments erstellt werden und beschleunigen
dann die Suche nach diesen Attributen. Für den Document-Key werden jeweils
automatisch Hash Indices erstellt, um eine Abfrage nach diesem zu ermöglichen
[wwwArangoManIndex]. Die Abfragezeit beträgt dann O(1).
Mit Hash Indices können nur Suchen mit Überprüfung auf die Gleichheit von Attributen
durchgeführt werden. Sollen zusätzlich Überprüfungen auf die Zugehörigkeit zu
Wertebereichen stattfinden, können sogenannte Skip List Indices benutzt werden. Fulltext
Indices können benutzt werden, um nach Wörtern in Attributen mit Textinhalten zu
suchen. Hat man es mit Attributen für Geo-Locations zu tun, so werden auch Geo Indices
zur Umkreissuche mit Längen- und Breitengraden unterstützt. Für die Suche nach
Verbindungen in Graphenstrukturen werden in Edge Collections automatisch Edge
Indices erstellt. Außerdem werden noch Bit-Array Indices54 unterstützt.
2.4.8 Die Bestandteile von ArangoDB
ArangoDB kommt mit einer Vielzahl an mitgelieferten Tools. Diese sollen hier kurz
beschrieben werden.
2.4.8.1 Die Kommandozeilen-Tools
Bei arangod, arangosh und arangoimp handelt es sich um Kommandozeilen-Tools, die
grundsätzliche Funktionen der Datenbank zur Verfügung stellen.
arangod steht für „Arango Daemon“. Hierbei handelt es sich um den eigentlichen
Datenbank-Server, der als Deamon Prozess ausgeführt wird. Clients können zu ihm eine
Verbindung via TCP/HTTP aufnehmen.
arangosh steht für „Arango Shell“. Hierbei handelt es sich um eine interaktive JavaScriptShell, die alle Operationen zur Konfiguration, Manipulation und Abfrage der Datenbank
unterstützt.
arangoimp steht für „Arango Import“. Hierbei handelt es sich um ein Import-Tool für
Datensätze. Es können im einfachsten Fall eine Anzahl von Datensätzen, die bereits im
54 Siehe hierzu http://en.wikipedia.org/wiki/Bitmap_index
35
JSON-Format vorliegen, in eine Collection importiert werden. Auch Daten im CSV 55
Format werden unterstützt.
Weitere Tools sind arangodump zur Erstellung von Backups, arangorestore zur
Wiederherstellung von Backups, foxx-manager zur Verwaltung von Foxx Applications
(siehe 2.4.8.3) sowie arango-dfdb zum Debuggen von Datafiles und arangob für
Benchmark-Tests. Letztere sind hauptsächlich zur Verwendung während der Entwicklung
von ArangoDB gedacht [wwwArangoFS].
2.4.8.2 Browser-Interface
In ArangoDB mitgeliefert wird ein Browser-Interface namens Aardvark. Dieses bietet
Funktionen zum Durchsuchen und Verwalten von ArangoDB. Hiermit ist das Verwalten
von Datenbanken, Collections und Dokumenten auch komfortabel per User-Interface
möglich. Es können alle Inhalte eingesehen werden, sowie neue Dokumente, Collections
und Datenbanken angelegt werden. Graphen können in einer interaktiven Graph-Ansicht
traversiert und durchsucht werden. Außerdem können Foxx-Anwendungen verwaltet
werden (siehe 2.4.8.3).
Weiterhin können über das Browser-Interface Statistiken (User-Time, Speicher-Auslastung
etc.) sowie das Logging der Datenbank eingesehen werden. Es gibt einen Editor, mit dem
AQL-Queries formuliert und abgeschickt werden können. Dieser bietet auch Templates,
um das Erstellen von Queries zu vereinfachen. Außerdem steht ein KommandozeilenInterface zur Verfügung, das dieselben Funktionalitäten wie das Tool arangosh bietet. Hier
können jedoch aufgrund von Browser-Einschränkungen nicht alle Befehle genutzt werden,
zum Beispiel keine Befehle, welche Betriebssystem-Aufrufe bedingen. Das BrowserInterface bietet außerdem eine Übersicht über die ArangoDB HTTP-API inklusive der
Möglichkeit des Absendens und Testens von Requests mit verschiedenen Optionen.
2.4.8.3 Foxx
Bei Foxx handelt es sich um ein Framework, welches es zulässt, eigenen JavaScript Code auf
der Datenbank zu hinterlegen und auszuführen. Dieser Code kann zum Beispiel eine
komplexe Datenbankabfrage sein oder auch eine ganze Anwendung beinhalten, wie etwa
ein Content-Management-System. Foxx Anwendungen können komfortabel mit dem Tool
foxx-manager aus einem zentralen Repository 56 installiert werden und zusätzlich auch im
ArangoDB Browser-Interface verwaltet werden.
55 http://de.wikipedia.org/wiki/CSV_(Dateiformat)
56 https://github.com/triAGENS/foxx-apps/
36
Durch Foxx kann ArangoDB als Application Server genutzt werden. Jede Foxx Anwendung
wird auf dem Datenbank-Server unter einem eigenen URI bereit gestellt. Mit Hilfe von
Controllern können dann auch eigene REST-APIs innerhalb von ArangoDB definiert
werden. So kann beispielsweise auch eine ganze Single Page Webanwendung ausgeführt
werden ohne ein zusätzliches zwischengeschaltetes Web-Framework und mit JavaScript als
einziger Programmiersprache [wwwArangoFoxx].
Die Foxx-Technologie lässt sich vergleichen mit den CouchApps in CouchDB. Mit dem
Unterschied jedoch, dass CouchApps vor allem darauf ausgerichtet ist, auch direkt HTML
an den Browser auszuliefern, anstatt nur JSON wie bei Foxx.
37
3. Aufgabenstellung
Ziel dieser Abschlussarbeit ist die Entwicklung eines Treibers (oder auch Clients 57) für die
Datenbank ArangoDB in der Programmiersprache Clojure. Dieser soll in seiner
vollständigen Implementierung die gesamten Funktionalitäten der ArangoDB
REST/HTTP-Schnittstelle umfassen, so wie sie im „Implementor Manual“ auf der Website
von ArangoDB58 dokumentiert ist.
Der Treiber soll durch die Nutzung einer HTTP-Library Anfragen an die HTTPSchnittstelle von ArangoDB senden und die Antworten entgegennehmen und in einem
geeigneten Format an den Benutzer, bzw. an das aufrufende Programm zurückgeben. Zu
den zu sendenden Anfragen zählen unter anderem Anfragen an die Administrationsfunktionen von ArangoDB, Anfragen an die Funktionen zur Verwaltung von Datenbanken,
Collections und Graphen sowie Anfragen zur Erzeugung und Änderung von Dokumenten.
Besondere Ziele bei der Umsetzung sind:
•
das Schaffen einer konsistenten, einfach zu verstehenden Schnittstelle (API 59) für
den Treiber;
•
die vom Benutzer verwendeten Funktionen sollen dabei sowohl intuitiv einsetzbar
als auch vielseitig verwendbar in Bezug auf Zusatzoptionen sein;
→ Damit gemeint ist, dass eine Funktion zum Datenbankzugriff unter
Zuhilfenahme von Default-Werten mit einem möglichst kurzen Funktionsaufruf
ausführbar sein soll; sollte der Benutzer jedoch zusätzliche Optionen angeben
wollen, so soll dies in einer möglichst fexiblen Art möglich sein, ohne ihn jedoch
zu verwirren.
•
der Treiber soll möglichst zustandslos sein, da die verwendeten Technologien
REST/HTTP und Clojure ebenfalls zustandslos sind; stehen Benutzungskomfort
und Zustandshaftigkeit in Konkurrenz, so können jedoch aus Gründen des
Benutzungskomforts auch Ausnahmen gemacht werden;
•
der Treiber soll möglichst leichtgewichtig sein; er soll mit möglichst geringem
Overhead die Anfragen an den ArangoDB Server weiterleiten;
57 Die beiden Begriffe werden in dieser Arbeit synonym verwendet.
58 http://www.arangodb.org/manuals/current/ImplementorManual.html
59 Die Abkürzung API sowie die Worte „Schnittstelle“ und „Interface“ werden ebenfalls in dieser Arbeit
weitgehend synonym verwendet; wobei jedoch die Schnittstelle des zu entwickelnden Treibers
Clarango, d.h. der Satz an Methoden der durch den Benutzer aufgerufen wird, von nun an immer als
„API“ bezeichnet werden soll.
38
4. Entwurf und Implementierung
Bei der Entwicklung von Clarango wurde ein agiler Ansatz verfolgt. Das bedeutet, die
Software wurde in vielen kleinen Schritten entwickelt und in ihren Funktionalitäten
erweitert und verbessert. Im Gegensatz zum klassischen Ansatz bei der Softwareentwicklung
wurde die Software nicht komplett fertig entworfen, bevor mit der Umsetzung begonnen
wurde. Dies bot den Vorteil, direkt mit der Entwicklung von Clarango beginnen zu
können, erste Features umsetzen zu können und sich nicht vorher „in Details verstricken“
zu müssen. Denn oft sind die Anforderungen an das Design einer Software zu Beginn noch
nicht vollständig klar.
So war es auch bei Clarango. Erst während der Entwicklung wurde klar, welche
Anforderungen die Software genau erfüllen muss. Dies hängt vor allem mit den
Anforderungen des ArangoDB REST/HTTP-Interfaces zusammen. Hier wurde zunächst
experimentiert, wie und mit welchen Features der Sprache Clojure sich am besten eine
Anfrage an das ArangoDB Interface zusammensetzen lässt und in welcher Form die
Rückgabewerte zurückgegeben werden sollen. Der agile Ansatz bot zudem den Vorteil, dass
bei jeder Fertigstellung einer Funktion wieder eine lauffähige und testbare Version der
Software verfügbar war.
Da auf den klassischen Ansatz der Trennung von Entwurf und Implementierung verzichtet
wurde, soll auch im Text dieser Arbeit keine Trennung der beiden Bereiche erfolgen. Daher
trägt dieses Kapitel den Titel „Entwurf und Implementierung“. Zu Beginn dieses Kapitels
sollen kurz die Werkzeuge und Libraries, die bei der Entwicklung von Clarango eingesetzt
wurden, aufgezählt und erläutert werden. Anschließend sollen die APIs einiger anderer
Clojure NoSQL Treiber untersucht werden, um danach unter Einbeziehung einiger
grundsätzlicher Überlegungen eine API für Clarango entwickeln zu können, die sich
möglichst an bereits existierenden Clojure-Treibern orientiert. Anschließend sollen einige
Implementierungsdetails sowie die Architektur von Clarango als Ganzes erläutert werden.
39
4.1 Werkzeuge
4.1.1 Versionsverwaltung
Als verteiltes Versionsverwaltungs-Werkzeug kommt Git 60 und der dazu gehörige HostingDienst GitHub61 zum Einsatz. Bei Git und GitHub handelt es sich um einen de facto
Standard bei Open Source Projekten. Git ist die eigentliche Versionsverwaltungssoftware,
die lokal auf dem Rechner des Entwicklers ausgeführt wird. Die sogenannten Git
Repositories, die den Code von Softwareprojekten enthalten, können dann im Falle von
Open Source Software kostenlos auf GitHub gehostet werden. Dies erleichtert die
Zusammenarbeit zwischen Entwicklern. Auf GitHub kann der gesamte Code in allen
Versionen komfortabel durchsucht werden. Außerdem gibt es Readme-Dateien, die eine
erste Einführung in die Software bieten sowie kostenlose „Github Pages“, auf denen eine
ausführliche Dokumentation eines Projektes bereitgestellt werden kann.
Das Github Repository von Clarango mit dem gesamten Code der Software findet sich
unter dieser Adresse:
https://github.com/edlich/clarango
4.1.2 Clojure Projektmanagement
Als Projektmanagement-Tool kommt Leiningen62 zum Einsatz. Leiningen ist der QuasiStandard für das Management von Clojure-Projekten. Das Tool gibt eine Projektstruktur
vor, verwaltet und lädt automatisch Abhängigkeiten aus öffentlichen Repositories und kann
die Anwendung sowie dazugehörige Tests mit einem einzigen Kommando ausführen 63.
Ebenso ist der automatische Upload von Projekten als Libraries in öffentliche Repositories
möglich, so dass diese wiederum von anderen Leiningen Projekten geladen und verwendet
werden können.
60
61
62
63
http://git-scm.com/
http://www.github.com
http://leiningen.org/
Für detailliertere Informationen siehe
https://github.com/technomancy/leiningen/blob/stable/doc/TUTORIAL.md
40
4.2 verwendete Libraries
4.2.1 clj-http
clj-http64 ist eine Clojure-Library, die dazu dient, HTTP-Anfragen zu senden und die
Antworten zu empfangen. Die Library funktioniert als Wrapper der Apache
HttpComonents Library65 für Java.
4.2.1 Cheshire
Bei Cheshire66 handelt es sich um eine Library zum Enkodieren und Dekodieren von
JSON-Objekten. Clojure Maps können damit einfach zu JSON-Strings konvertiert werden
und umgekehrt. Hierbei werden Clojure Keywords, die in den Maps als Key dienen,
automatisch zu Strings umgewandelt und umgekehrt. Cheshire unterstützt alle StandardDatenstrukturen von Clojure sowie einige weitere Java-Datenstrukturen.
64 https://github.com/dakrone/clj-http
65 http://hc.apache.org/
66 https://github.com/dakrone/cheshire
41
4.3 Versionierung
Git und GitHub bieten die Möglichkeit, verschiedene Entwicklungsstände einer Software
mit Tags zu markieren und in sogenannten Releases zu veröffentlichen. Releases bieten die
Möglichkeit, den Stand einer Software mit einer Überschrift sowie einer weiteren
Beschreibung zu versehen und als Download bereitzustellen67.
Zur Versionierung wurde das in [wwwVersioning] erläuterte System verwendet. Dieses
schlägt vor, Versionsnummern in drei Teile nach folgendem Muster zu unterteilen:
„MAJOR.MINOR.PATCH“. Bei den drei Teilen handelt es sich jeweils um ganze Zahlen,
die nur herauf, nicht herabgesetzt werden können und nach den folgenden Regeln
heraufgesetzt werden:
–
die MAJOR-Nummer sollte sich nur ändern, wenn zur Vorgängerversion
inkompatible Änderungen an der API vorgenommen wurden
–
die MINOR-Nummer ändert sich, wenn Funktionen hinzugefügt wurden, die
äbwartskompatibel zu Vorgängerversionen sind
–
die PATCH-Nummer ändert sich, wenn Bug-Fixes vorgenommen wurden; diese
müssen ebenfalls äbwartskompatibel sein
Die während der Entstehung dieser Arbeit fertiggestellten Releases von Clarango sind unter
dieser Adresse einzusehen:
https://github.com/edlich/clarango/releases
Eine erste Version 0.1.0 wurde veröffentlicht, als die umfangreichen Document-CRUD 68
Funktionen im document Namespace von Clarango fertiggestellt wurden. Version 0.2.0
erhielt dann zusätzlich Query-Funktionalitäten. Die zuletzt während der Entstehung dieser
Arbeit fertiggestellte Version von Clarango ist 0.3.2 69. Sie enthält zusätzlich GraphFunktionalitäten sowie einige Bug-Fixes. Da die API von Clarango noch nicht als stabil
betrachtet wird und sich noch in der Entwicklung befindet, wurde noch keine Version
1.0.0 veröffentlicht.
Die Releases von Clarango wurden mit Leiningen in das Open Source Repository Clojars 70
hochgeladen und eine Informationsseite ist dort unter folgender Adresse zu erreichen:
https://clojars.org/clarango
67
68
69
70
Für weitere Informationen siehe auch https://help.github.com/articles/about-releases
CRUD ist eine Abkürzung für „Create, Read, Update, Delete“
https://github.com/edlich/clarango/releases/tag/v0.3.2
https://clojars.org/
42
4.4 Vergleich von APIs anderer Clojure
Datenbanktreiber
Um die in Kapitel 3 aufgeführten Anforderungen an die Schnittstelle von Clarango
möglichst gut umsetzen zu können, sollen nun einige APIs anderer ClojureDatenbanktreiber untersucht werden. Die gesammelten Informationen werden dann später
genutzt, um eine API für Clarango zu entwickeln, die sich möglichst „natürlich“ anfühlt.
Unter anderem weil sie sich an bereits existierenden Treibern orientiert.
Untersucht werden sollen Treiber für die Dokumenten-Datenbanken MongoDB,
CouchDB und Elasticsearch. Ein MongoDB Client eignet sich hierbei besonders gut zum
Vergleich, da bei MongoDB die Dokumente ebenso wie bei ArangoDB in Collections
organisiert sind [wwwMongoDBCRUD]. Andererseits soll auch ein CouchDB Client
untersucht werden, da das Design von CouchDB durch dessen REST-Ansatz
Gemeinsamkeiten mit dem von ArangoDB aufweist. Weiterhin sollen noch ein Treiber für
die Multimodel-Datenbank OrientDB und ein Treiber für die Key/Value-Datenbank Redis
untersucht werden, um auch den Multimodel- und Key/Value-Aspekt von ArangoDB
abzudecken. Um auch Graph-Funktionalitäten und den Einsatz einer eigenen QuerySprache einzubeziehen, wird zuletzt noch der Treiber Neocons für die Graph-Datenbank
Neo4J untersucht.
4.4.1 Monger für MongoDB
Bei Monger71 handelt es sich um einen Clojure Wrapper um den MongoDB Java Driver.
Alle Codebeispiele stammen aus [wwwMongerDocs1], außer wenn anderweitig angegeben.
Connect:
(ns my.service.server
(:require [monger.core :as mg])
(:import [com.mongodb MongoOptions ServerAddress]))
;; localhost, default port
(mg/connect!)
[…]
;; given host, given port
(mg/connect! { :host "db.megacorp.internal" :port 7878 })
71 http://clojuremongodb.info/
43
Zu beachten ist, dass es eine connect und eine connect! Methode gibt. Der Unterschied ist,
dass die connect! Methode die Verbindungsdaten zusätzlich in einer „globalen“ *mongodbconnection* Variablen speichert [wwwMongerAPI].
Festlegen einer Default Database:
(ns my.service.server
(:require [monger.core :as mg]))
;; localhost, default port
(mg/connect!)
(mg/set-db! (mg/get-db "monger-test"))
Disconnect:
(monger.core/disconnect! )
Create Documents:
(ns my.service.server
(:use [monger.core :only [connect! connect set-db! get-db]]
[monger.collection :only [insert insert-batch insert-and-return]])
(:import [org.bson.types ObjectId]
[com.mongodb DB WriteConcern]))
;; without document id (when you don't need to use it after storing the document)
(insert "document" { :first_name "John" :last_name "Lennon" })
;; with explicit document id (recommended)
(insert "documents" { :_id (ObjectId.) :first_name "John" :last_name "Lennon" })
;; returns the inserted document that includes generated _id
(insert-and-return "documents" {:name "John" :age 30})
;; multiple documents at once
(insert-batch "document" [{ :first_name "John" :last_name "Lennon" }
{ :first_name "Paul" :last_name "McCartney" }])
;; with a different database
(let [archive-db (get-db "monger-test.archive")]
(insert
archive-db
"documents"
{
:first_name
"John"
:last_name
"Lennon"
}
WriteConcern/NORMAL))
44
Hinweis: (ObjectId.) generiert automatisch eine neue ID für das Dokument. Wird keine ID
mit angegeben, so wird diese automatisch vom MongoDB Java Treiber erzeugt, was jedoch
bei Clojures unveränderlichen Datenstrukturen nicht funktioniert. Das explizite Angeben
einer ID wird deshalb stets empfohlen [wwwMongerDocs1].
Get Document by Id [wwwMongerDocs2]:
(let [oid (ObjectId.)]
(monger.collection/insert
"documents"
{:_id
oid
:first_name
"John"
:last_name
"Lennon"})
(monger.collection/find-map-by-id "documents" oid))
Die fnd-map-by-id Methode gibt ein Dokument aus der Collection „documents“ als
Clojure-Map zurück.
Update Documents:
;; updates a document by id
(monger.collection/update-by-id "scores" oid {:score 1088})
;; updates score for player "sam" if it exists; creates a new document otherwise
(monger.collection/update "scores" {:player "sam"} {:score 1088} :upsert true)
Ist die ID bekannt, so kann die Methode update-by-id genutzt werden. Ist die ID nicht
bekannt, wird die update Methode benutzt und nach dem Dokument, das :player „sam“
enthält, gesucht. Sofern dieses existiert, wird das Dokument um :score 1088 erweitert. Wird
zusätzlich :upsert true übergeben, so wird das Dokument außerdem erzeugt, falls es noch
nicht existiert.
Remove Documents:
;; remove multiple documents
(monger.collection/remove "documents" { :language "English" })
;; remove ALL documents in the collection
(monger.collection/remove "documents")
;; with a different database
(let [archive-db (get-db "monger-test.archive")]
(monger.collection/remove archive-db "documents" { :readers 0 :pages 0 }))
;; remove document by id
(let [oid (ObjectId.)]
45
(monger.collection/insert "documents" { :language "English" :pages 38 :_id oid })
(rmonger.collection/remove-by-id "documents" oid))
Wie man sieht, ist die remove Methode sehr fexibel einsetzbar. Ein Dokument kann
anhand seiner ID gelöscht werden, aber genauso auch durch die Angabe eines BeispielDokuments. Zusätzlich können durch das Weglassen weiterer Parameter auch alle
Dokumente der Collection gelöscht werden. Die Angabe einer Datenbank kann bei allen
Aufrufen jeweils optional erfolgen.
Create Collection [wwwMongerDocs3]:
;; creates a non-capped collection
(monger.collection/create "recent_events" {})
;; creates a collection capped at 1000 documents
(monger.collection/create "recent_events" {:capped true :max 1000})
Die create Methode kann entweder eine normale Collection erstellen, oder wenn :capped
true übergeben wird, eine auf eine bestimmte Anzahl an Dokumenten begrenzte Collection.
Zum Löschen von Collections wird die Methode monger.collection/drop verwendet, die den
Namen der Collection als einziges Argument erhält. Eine Collection kann außerdem
umbenannt werden mit der monger.collection/rename Methode, die den alten und den
neuen Namen als Argumente erhält.
Querying [wwwMongerDocs4]:
Monger bietet zwei Möglichkeiten der Dokumenten-Abfrage. Die Suche anhand eines
Beispiel-Dokuments funktioniert ähnlich wie das Querying-by-Example bei ArangoDB. Als
Erweiterung gegenüber ArangoDB ist hier jedoch zusätzlich die Nutzung der sogenannten
MongoDB Query Operators72 möglich. Diese bieten erweiterte Möglichkeiten, wie etwa
die Suche innerhalb von Wertebereichen. Die Ergebnisse können entweder als Cursor oder
direkt in Form von Dokumenten als Clojure-Maps zurückgegeben werden.
;; returns a cursor documents with name field value „Ringo“
(monger.collection/find "documents" {:first_name "Ringo"})
;; with a query that uses MongoDB query operators
(monger.collection/find "products" { :price_in_subunits { "$gt" 1200 "$lte" 4000 } })
;; returns documents with year field value of 1998, as Clojure maps
(monger.collection/find-maps "documents" { :year 1998 })
72 Siehe hierzu: http://docs.mongodb.org/manual/reference/operator/
46
Darüber hinaus gibt es noch die Methoden monger.collection/fnd-one und monger.collection/
fnd-one-as-map, die jeweils dieselben Aktionen ausführen, aber nur ein Dokument
zurückgeben. Letztere Methode bietet zusätzlich die Möglichkeit, einen Vektor mit
Attributnamen zu übergeben. In diesem Fall werden vom Ergebnis-Dokument
ausschließlich diese Attribute zurückgegeben, was im Falle von großen Dokumenten das
unnötige Laden von Daten verhindern kann.
Als zweite Möglichkeit der Abfrage wird die Nutzung einer eigenen Abfragesprache, der
Monger Query DSL angeboten. Diese sollte benutzt werden, wenn die Ausgabe der
Suchergebnisse genauer kontrolliert werden soll. Etwa durch Sortieren, Überspringen von
Dokumenten, Einteilung in Seiten (Pagination) und eine Begrenzung der Anzahl der
Ergebnisse. Diese Art der Abfrage wird durch eine Aneinanderreihung von Funktionsaufrufen aufgebaut und unterscheidet sich damit grundsätzlich von AQL in ArangoDB, die
aus reinem Text besteht. Daher soll die Monger Query DSL hier nicht näher untersucht
werden.
4.4.2 Clutch für CouchDB
Bei Clutch73 handelt es sich um einen Treiber für die Dokumenten-Datenbank CouchDB.
In CouchDB gibt es keine Collections, sondern die Dokumente werden direkt auf der
Datenbankebene abgelegt74. Die Ebene der Collections fällt also weg, was die API ein wenig
einfacher macht. Codebeispiele aus [Emerick2012], Kapitel 15, und [wwwClutch].
Erstellen einer Datenbank und create Documents:
(use '[com.ashafa.clutch :only (create-database with-db put-document get-document
delete-document) :as clutch])
(def db (create-database "repl-crud"))
;; [create document]
(put-document db {:_id "foo" :some-data "bar"})
;; [update document]
(put-document db (assoc *1 :other-data "quux"))
Das erstellte Dokument kann dann mittels (get-document db "foo") abgefragt werden und
mit (delete-document db *1) gelöscht werden. Wie man sieht, sind die Methoden in Clutch
nach den HTTP-Methoden benannt. Dies signalisiert dem Benutzer, dass darunterliegend
73 http://www.github.com/clojure-clutch/clutch
74 Siehe hierzu auch den Abschnitt „Organization“ in http://openmymind.net/2011/10/27/A-MongoDBGuy-Learns-CouchDB/
47
eine HTTP-Anfrage stattfindet. Die Erstellung eines CouchDB Dokuments erfolgt mit der
Methode clutch/create-document, der eine Clojure-Map übergeben wird.
Um mehrere Operationen auf derselben Datenbank durchzuführen, können mittels einer
Methode with-db auch mehrere Funktionsaufrufe im Kontext derselben Datenbank
ausgeführt werden:
(with-db "clutch_example"
(put-document {:_id "a" :a 5})
(put-document {:_id "b" :b 6})
(-> (get-document "a")
(merge (get-document "b"))
(dissoc-meta)))
Clutch bietet außerdem noch ein experimentelles Feature: Die Verwendung von Clojureeigenen Collection-Funktionen wie assoc, conj, get etc. zum Arbeiten mit der Datenbank.
Dies wurde umgesetzt in Form von Wrapper-Funktionen, die unterliegend auf bereits
existierenden Methoden der Clutch API aufbauen. Dieser Ansatz soll im Abschnitt 4.7.3
diskutiert werden.
Als wesentlicher Unterschied zur API von Monger befinden sich bei Clutch alle Methoden
innerhalb eines Namespaces. Dies macht einerseits den Import leichter, da man sich nicht
mit den verschiedenen Namespaces auseinandersetzen muss. Gleichzeitig fehlt aber eine
grundsätzliche Gliederung der Methoden. Diese wurde bei Clutch stattdessen im Namen
der Methoden vorgenommen, durch ein Anhängen der jeweiligen Ressource, die behandelt
werden soll. So etwa bei create-database und create-document.
4.4.3 Elastisch für Elasticsearch
Bei Elasticsearch handelt es sich um eine dokumentbasierte Datenbank und eine verteilte
Such- und Datenanalyseplattform. Elastisch75 ist ein „minimalistischer“ Clojure Client für
Elasticsearch. Codebeispiele aus [wwwElastischDocs1], außer wenn anderweitig angegeben.
Create Documents und Connect:
(ns clojurewerkz.elastisch.docs.examples
(:require [clojurewerkz.elastisch.rest
:as esr]
[clojurewerkz.elastisch.rest.index :as esi]
[clojurewerkz.elastisch.rest.document :as esd]))
75 http://www.github.com/clojurewerkz/elastisch
48
(defn -main
[& args]
(esr/connect! "http://127.0.0.1:9200" )
;; submit a document for indexing. Document id will be generated by ElasticSearch,
;; in case the index does not exist, it will be automatically created.
(println (esd/create "myapp" "tweet" {:username "happyjoe" :text "My first
document submitted to ElasticSearch!" :timestamp "20120802T101232+0100" })))
Als erstes Argument erhält die create Methode den Namen des Indexes („myapp“), unter
dem das Dokument abgelegt werden soll. Indexe entsprechen hier einem Namensraum, zu
vergleichen mit den Datenbanken in relationalen Systemen [wwwElasticGlossary]. Als
zweites Argument wird der sogenannte „mapping type“ angegeben und dann als drittes
Argument das abzulegende Dokument.
Get Document by Id [wwwElastischDocs2]:
(clojurewerkz.elastisch.rest.document /get "myapp" "articles"
"521f246bc6d67300f32d2ed60423dec4740e50f5")
Update Document:
(esd/put "myapp" "tweet" "happyjoe_tweet1" {:username "happyjoe" :text "My first
document submitted to ElasticSearch!" :timestamp "20120802T101232+0100" })
Hier wird als drittes Argument der put Methode die document ID übergeben.
Create Index:
(clojurewerkz.elastisch.rest.index /create "myapp_development")
Optional können hier auch die Mapping-Types und weitere Einstellungen mit übergeben
werden. Aus diesem kurzen Einblick in die API von Elastisch lässt sich zusammenfassen,
dass die Methoden vom Konzept her ähnlich funktionieren wie bei Monger und Clutch.
Zudem findet sich auch hier die Methodenbenennung nach HTTP-Verben genau wie bei
Clutch.
4.4.4 clj-orient für OrientDB
Als Vertreter eines Clients für eine Multimodel-Datenbank soll nun clj-orient 76 für
OrientDB untersucht werden. Bei clj-orient handelt es sich um einen Wrapper für die
OrientDB Java API. Daher arbeitet der Treiber auch in erhöhtem Maße mit Objekten und
76 http://www.github.com/eduardoejp/clj-orient
49
Klassen. Die Datenbank OrientDB arbeitet ebenso wie ArangoDB mit Collections als
übergeordnete Ebene über den Dokumenten, das heißt, bevor man ein Dokument ablegen
kann, muss man zunächst eine Collection erstellen [wwwOrientDB1]. Codebeispiele aus
[wwwClj-Orient].
Connect:
Zum Zwischenspeichern von Verbindungsdaten bietet clj-orient die Möglichkeit, eine
Standard-Datenbank mittels set-db! zu setzen. Gleichzeitig kann innerhalb eines
geschachtelten Ausdrucks mittels with-db auch eine andere Datenbank verwendet werden:
(use 'clj-orient.core)
; Opening the database as a document DB and setting the *db* var for global use.
; A database pool is used, to avoid the overhead of creating a DB object each time.
(set-db! (open-document-db! "remote:localhost/my-db" "writer" "writer"))
; Dynamically bind *db* to another DB.
; The DB is closed after all the forms are evaluated.
(with-db (open-document-db! "remote:localhost/another-db" "writer" "writer")
(form-1 ...)
(form-2 ...)
(form-3 ...)
...
(form-n ...))
; Close the DB
(close-db!)
Write Document:
(use 'clj-orient.core)
(let [u (document :user {:first-name "Foo", :last-name "Bar", :age 10})
u (assoc u :first-name "Mr. Foo", :age 20)]
(save! u))
Hier arbeitet clj-orient mit einer save! Methode, wie sie eher aus relationalen
Datenbanksystemen bekannt ist.
Queries:
Es folgen noch zwei Beispiele zum Senden von Queries an die Datenbank. Das erste
Beispiel erinnert an das Querying-by-Example von ArangoDB und das zweite Beispiel
50
sendet eine SQL-Anfrage, die zusätzlich noch ein Objekt mit Attributen erhält, die
dynamisch in die Anfrage eingesetzt werden.
(use 'clj-orient.query)
(native-query :user {:country "USA", :age [:$>= 20], :first-name [:$like "J%"]})
(sql-query "SELECT FROM user WHERE country = :country AND age >= :age AND first-name
LIKE :fname LIMIT 10" {:country "USA", :age 20, :fname "J%"})
Insgesamt stellt man fest, dass sich clj-orient in der Verwendung etwas anders „anfühlt“ als
die bisher untersuchten Treiber Monger, Clutch und Elastisch. Der Treiber ist sehr viel
zustandshafter als die bisher untersuchten Treiber (siehe zum Beispiel die Methoden opendocument-db! und save!). Das liegt unter anderem daran, dass clj-orient auf einem Java
Treiber aufbaut, in dem intern bei den meisten Aktionen Objekte erzeugt werden. Aus
diesen Gründen eignet sich clj-orient weniger als Orientierungshilfe für den Entwurf der
Clarango API.
4.4.5 Carmine für Redis
Als nächstes soll Carmine77 angeschaut werden. Bei Carmine handelt es sich um einen
Client für die Key/Value-Datenbank Redis, der zusätzlich Funktionen einer MessageQueue bietet. Carmine ist ein Projekt, welches versucht, die Vorteile bereits existierender
Redis Clients in einem Projekt zu vereinen. Es bietet außerdem Unterstützung für alle
Clojure-Datentypen, obwohl Redis intern nur mit Byte-Strings arbeitet. Codebeispiele aus
[wwwCarmine].
Connect:
(def server1-conn {:pool {<opts>} :spec {<opts>}}) ; See `wcar` docstring for opts
(defmacro wcar* [& body] `(car/wcar server1-conn ~@body))
Hier werden per defmacro-Befehl die Verbindungsdaten an den Value wcar* gebunden,
sodass diese nicht bei jedem Aufruf von wcar wieder übergeben werden müssen.
Read/Write:
(wcar* (car/ping)
(car/set "foo" "bar")
(car/get "foo"))
;; Output:
=> ["PONG" "OK" "bar"]
77 http://www.github.com/ptaoussanis/carmine
51
Zu beachten ist hier, dass mehrere Befehle gleichzeitig gesendet werden. Diese werden dann
auf der Serverseite in einer Pipeline78 abgearbeitet und anschließend alle Ergebnisse
zusammen als Vektor zurückgesendet.
Zusammenfassend lässt sich sagen, dass Carmine über einen sehr kompakten Befehlssatz
verfügt, der sich nur ansatzweise mit dem eines Treibers für eine Dokument-Datenbank
vergleichen lässt. Im Unterschied zu den bisher untersuchten Treibern bietet Carmine einen
anderen Ansatz für die Verbindungsdatenspeicherung. Hier wird Gebrauch von einem
Makro Befehl gemacht, um die Verbindungsdaten dauerhaft an eine Methode zu binden,
die für die Verbindung und das Senden der Anfragen zuständig ist.
4.4.6 Neocons für Neo4J
Als letztes soll noch ein Treiber für eine Graph-Datenbank untersucht werden: Neocons 79.
Es handelt sich hierbei um einen Treiber für die Datenbank Neo4J. Diese bietet genau wie
ArangoDB auch eine eigene Query-Sprache: Cypher. Daher soll Neocons auch daraufhin
untersucht werden, wie die Anwendung dieser Abfragesprache funktioniert. Codebeispiele
aus [wwwNeoconsGuide].
Connect:
(neocons.rest/connect! "http://localhost:7474/db/data/")
Create Vertices und Edge sowie Ausführen einer Cypher Query:
(let [amy (neocons.rest.nodes/create {:username "amy"})
bob (neocons.rest.nodes/create {:username "bob"})
rel (neocons.rest.relationships/create amy bob :friend {:source "college"})
res (neocons.rest.cypher/tquery
"START person=node({sid}) MATCH person-[:friend]->friend RETURN friend"
{:sid (:id amy)})]
(println res)))
In diesem Beispiel werden zunächst zwei Knoten „Amy“ und „Bob“ erstellt. Diese werden
dann durch eine Kante verbunden. Die anschließend ausgeführte Query gibt alle Freunde
von Amy zurück. In diesem Beispiel also nur Bob. Die zum Senden der Query benutzte
Methode tquery gibt hierbei das Ergebnis in einer besser lesbaren Tabellenform zurück,
während die ebenfalls verfügbare Methode query die Spalten und Zeilen getrennt
zurückgibt.
78 Vgl. http://redis.io/topics/pipelining
79 https://github.com/michaelklishin/neocons
52
Graph Traversierungen:
Es gibt zwei Arten der Traversierung: die Traversierung von Knoten und die Traversierung
von Kanten:
(neocons.rest.nodes/traverse (:id john)
:relationships [{:direction "out" :type "friend"}]
:return-filter {:language "builtin" :name "all_but_start_node"})
(neocons.rest.relationships/traverse (:id john)
:relationships [{:direction "out" :type "friend"}])
Man sieht, dass die beiden Arten der Traversierung etwa ähnlich funktionieren. Als erstes
Argument wird die ID eines Knoten übergeben, an dem die Traversierung gestartet werden
soll. Als zweites Argument wird die Richtung der Traversierung sowie ein Typ von Kanten
übergeben, der für die Traversierung berücksichtigt werden soll. Im ersten Beispiel wird
außerdem ein Default-Filter eingesetzt, der die Knoten des Ergebnisses filtert. Zusätzlich zu
diesen beiden Typen der Traversierung ist außerdem noch eine Traversierung von
sogenannten Paths möglich. Bei einem Path handelt es sich um eine Kombination aus
Knoten und Kanten.
4.4.7 Zusammenfassung
In diesem Abschnitt wurden einige Clojure-Treiber für verschiedene Datenbankmodelle
untersucht. Da es sich bei ArangoDB vorwiegend um eine Dokument-Datenbank handelt,
eignen sich besonders die untersuchten Treiber für Dokument-Datenbanken als Grundlage
für den Entwurf einer API für Clarango. Hier wurde unter anderem ein Einblick gegeben
in die Methodenbenennung und -organisation dieser Treiber. Es wurden außerdem
Gemeinsamkeiten in der Verbindungsdatenspeicherung bei vielen Treibern festgestellt. Des
Weiteren wurden einige Ansätze zur Ausführung von Queries betrachtet und zuletzt noch
die Erstellung und Traversierung eines Graphen bei Neocons.
Nach der Erläuterung einiger grundsätzlicher Designentscheidungen im nächsten Abschnitt
soll im darauffolgenden Abschnitt 4.6 eine API für Clarango hergeleitet werden. Hierfür
sollen die Ergebnisse aus der Untersuchung in diesem Abschnitt sowie die Überlegungen
aus dem nächsten Abschnitt 4.5 berücksichtigt werden.
53
4.5 grundsätzliche Überlegungen
Im Folgenden sollen einige grundsätzliche Überlegungen erläutert werden, die sowohl in
das Design der Clarango API, als auch in den Entwurf der Architektur von Clarango mit
einbezogen werden.
4.5.1 ähnliche Methoden
In der ArangoDB HTTP-API werden oft ähnliche Parameter an verschiedenen Stellen
übergeben. Ein gutes Beispiel hierfür ist der Collection-Name. Hier wurden bei den
Methoden der ArangoDB API insgesamt drei verschiedene Arten gefunden, diesen zu
übergeben. Beim Abfragen und Ändern von Dokumenten beispielsweise ist der Name der
Collection Teil des URI eines Dokumentes. Beim Nutzen der by-example Methoden der
Simple Queries wird der Collection Name dagegen im JSON-Body des HTTP-Requests
übergeben. Beim Anlegen von neuen Dokumenten wird der Collection Name als URLParameter /?collection=... an den URI angehängt. Bei der Clarango API sollen diese
Variationen in eine einheitliche Funktionsschnittstelle überführt werden. Alle Methoden
der Clarango API sollen möglichst gleich aufgebaut sein in Bezug auf die Reihenfolge der
Aufrufparameter.
4.5.2 Angabe von Verbindungsdaten
Da das HTTP-Protokoll ein zustandsloses Protokoll ist und die REST/HTTP-API von
ArangoDB ebenfalls zustandslos ist, kommt das klassische Herstellen einer DatenbankVerbindung, wie man es von relationalen Datenbanken kennt, bei ArangoDB nicht vor.
Sofern der Datenbank-Server erreichbar ist, können Anfragen an diesen gesendet werden
und Operationen durchgeführt werden, ohne dass zunächst eine Datenbank-Verbindung
hergestellt werden muss.
Trotzdem soll es in Clarango eine Möglichkeit geben, dauerhaft Verbindungsdaten wie eine
Server-URL und eine Standard-Datenbank zu hinterlegen, damit diese nicht bei jeder
Anfrage erneut angegeben werden müssen. Dies soll erreicht werden durch eine Variable im
Namespace core. In dieser sollen die Verbindungsdaten als Map abgespeichert werden. Bei
der Variablen handelt es sich dann um den einzigen Zustand innerhalb von Clarango.
Diese Variante wurde der Einfachheit halber gewählt. Eine Alternative hierzu, die gänzlich
auf Seiteneffekte verzichtet, wäre es, die Verbindungsdaten immer in jedem Methodenaufruf zu übergeben. Damit der Benutzer die Daten nicht permanent zwischenspeichern
und immer wieder angeben muss, bietet sich bei Clojure in diesem Fall an, die API
54
Methoden zunächst teilweise nach dem folgenden Muster anzuwenden:
(def create-document-with-connection (partial document/create {...connection data...}))
Als erster Parameter der document/create Methode werden hier die Verbindungsdaten
übergeben und dann die Methode inklusive dieser Daten als neue Variable create-documentwith-connection „eingefroren“. In dieser Variante wären die Verbindungsdaten dauerhaft
gespeichert, allerdings auf Ebene der Java Virtual Machine und seiteneffektfrei, da es sich
bei mehreren mit verschiedenen Parametern teilweise angewendeten Methoden nicht mehr
um dieselbe Methode handelt. Diese Art der Verbindungsdatenspeicherung wäre aber für
den Benutzer sehr viel aufwendiger als die in der Implementierung von Clarango gewählte
Variante, da er sie für jede benutzte Methode getrennt anwenden müsste und unter
Umständen mehrere teilangewendete Methoden zwischenspeichern müsste.
Der Treiber Carmine (siehe 4.4.5) löst dieses Problem, indem die Verbindungsdaten per
Makro-Befehl an eine Methode gebunden werden, die als Hüllmethode für alle API
Methoden dient. Diese Hüllmethode wird jedoch im Kontext des Pipelining bei Redis
benötigt und wäre bei Clarango nicht sinnvoll.
Sinnvoll für Clarango erscheint dagegen aber zusätzlich die Idee einer Methode with-db,
wie sie in clj-orient und Clutch enthalten ist (siehe 4.4). Diese Methode bietet eine „Hülle“
oder auch einen „Scope“, in dem alle Methoden mit der Datenbank arbeiten, welche withdb als Argument übergeben wurde. So können auch mehrere Methoden auf einer anderen
als der Default-Datenbank arbeiten, ohne dass in jedem Funktionsaufruf wiederholt diese
Datenbank angegeben werden muss.
4.5.3 Überprüfung der Eingabedaten
Eine grundsätzliche weitere Frage, die sich beim Entwurf von Clarango stellte, war, in wie
weit und ob überhaupt die Eingabedaten der Clarango API Methoden auf Gültigkeit
überprüft werden sollten80.
Eine Überprüfung der Eingabedaten wäre einerseits gut, denn so würden keine ungültigen
Anfragen an den Server gesendet. Es würde somit Netzwerk-Trafc gespart. Es könnte hier
jeweils überprüft werden, ob alle angegebenen Ressourcen wirklich existieren, ob die
Namenskonventionen eingehalten wurden und ob alle geforderten Attribute jeweils
angegeben sind (zum Beispiel bei der Erstellung von Kanten eines Graphen die „_from“
und „_to“ Knoten). Gegen die Überprüfung der Eingabedaten spricht allerdings, dass der
80 Mit dieser Fragestellung beschäftigt sich auch diese Diskussion von ArangoDB Treiber-Entwicklern:
https://github.com/triAGENS/api-implementors/issues/5
55
Kern von Clarango so leichtgewichtig wie nur möglich sein soll. Clarango soll möglichst
wenig Overhead erzeugen und die Anfragen möglichst schnell an den Server weiterleiten.
Im Speziellen spricht auch gegen die Überprüfung der Eingabedaten, dass alle oben
genannten Punkte auch auf dem ArangoDB Server überprüft werden. Sollte eine
Namenskonvention nicht eingehalten worden sein, so wirft ArangoDB einen Fehler. Sollte
ein Attribut wie zum Beispiel der „_from“ Knoten bei der Erstellung einer Kante fehlen
oder der angegebene Knoten nicht existieren, so wird ebenfalls ein Fehler geworfen.
Würden die genannten Punkte schon auf der Clientseite überprüft, so würden zwar einige
Server-Anfragen eingespart, es würden aber letztendlich auch alle Tests zwei mal
durchgeführt. Zudem kann die Prüfung, ob eine Ressource existiert oder nicht, auf der
Clientseite nur durch eine gesonderte Anfrage an den Server durchgeführt werden. Damit
würde der Overhead in einem nicht akzeptablen Maße steigen.
Es wurde deshalb entschieden, bei Clarango gar keine Überprüfung der Eingabedaten
vorzunehmen. Hier bleibt jedoch anzumerken, dass einige Überprüfungen trotzdem
automatisch und ohne gesonderten Aufwand erfolgen: Durch den Einsatz der JSONLibrary Cheshire wird automatisch die Gültigkeit der übergebenen JSON-Daten überprüft,
das heißt ob sie dem JSON-Standard entsprechen oder nicht. Die Existenz zwingend
benötigter Attribute wird außerdem sichergestellt, da sie in der Methodensignatur gefordert
werden. Sollte eine Methode ohne das betreffende Argument aufgerufen werden, so wird
von Clojure ein Fehler geworfen.
4.5.4 Methodenbenennung
Bei der Methodenbenennung stellte sich die Frage, ob und bei welchen Methoden ein
Rufzeichen am Ende der Methodennamen stehen soll. Es gibt in Clojure eine inofzielle
Konvention81, die vorgibt, dass Methoden, die einen Zustand manipulieren, mit einem
Rufzeichen versehen werden sollten. In einem Datenbank-Treiber gilt diese Voraussetzung
im Prinzip für alle API-Methoden, da diese den Zustand der Datenbank modifizieren (mit
der Ausnahme von reinen Leseoperationen). Bei der Methodenbenennung der ClarangoAPI wurde sich daher an anderen Datenbank-Treibern orientiert. Bei den meisten
untersuchten Treibern wurden lediglich die Methoden zur Speicherung der Verbindungsdaten mit einem Rufzeichen versehen (zum Beispiel connect! bei Monger). Genauso wurde
deshalb auch bei Clarango verfahren. Die Methoden zur Speicherung der Verbindung im
core Namespace haben ein Rufzeichen erhalten, die weiteren API-Methoden in den übrigen
Namespaces dagegen nicht.
81 Vgl. http://stackoverflow.com/questions/20606249/when-to-use-exclamation-mark-in-clojure-or-lisp
56
4.5.5 Gliederung der Funktionalitäten in eigene Funktionsräume vs.
Gliederung der ArangoDB HTTP-API
Die HTTP-Schnittstelle von ArangoDB ist in sehr viele sogenannte Interfaces aufgeteilt.
Eines ist etwa zuständig für Dokument-Operationen, eines für Collection-Operationen,
eines für Graph-Operationen etc. Es stellte sich die Frage, ob diese Gliederung für die
Funktionalitäten von Clarango genau so übernommen werden sollten.
Es wurde beschlossen, sich zwar grundsätzlich an der Aufteilung der ArangoDB Interfaces
zu orientieren; in Einzelfällen jedoch wurden Methoden in andere Bereiche verschoben,
wenn dies sinnvoll schien (dazu mehr in Abschnitt 4.6). Des Weiteren wurden einige
ArangoDB Interfaces, die nur wenige Funktionen enthalten oder deren Zuständigkeitsbereiche sich überschneiden, in Clarango zu größeren Funktionsbereichen zusammengefasst. So sollte eine klare Gliederung mit nur wenigen Namespaces hergestellt werden
(auch dazu mehr im nächsten Abschnitt).
57
4.6 Clarango API
Die Untersuchung aus Abschnitt 4.4 und die Überlegungen aus 4.5 werden nun
zusammengeführt und eine API für Clarango entwickelt.
Für die Gliederung der Methoden wurde ein Ansatz ähnlich dem von Monger gewählt. Die
Methoden werden hierbei in Namespaces gegliedert nach dem Muster Hierarchieebene/
Befehl. Die Hierarchieebene entspricht hierbei den Zuständigkeiten „document“,
„collection“, „graph“ etc. Im Gegensatz zu Monger wird hier aber eine feinere und nach
Meinung des Autors sinnvollere Gliederung angestrebt. So wird es auch einen Namespace
document geben, der für alle Aktionen mit Bezug auf Dokumente benutzt wird. Bei Monger
dagegen werden alle Dokument-Operationen auf der Collection-Ebene durchgeführt. Von
einem globalen Namespace, der alle Methoden enthält wie etwa bei Clutch, wurde
abgesehen, da das oben erläuterte System weitaus übersichtlicher scheint. Es müssen
dadurch, um alle Funktionsbereiche von Clarango zu nutzen, zwar mehr Namespaces
importiert werden; gleichzeitig wird dadurch aber auch das bewusstere Importieren der
Methoden gefördert, was die Gefahr eines unbemerkten Überdeckens von anderen
Methoden minimiert.
Die Clarango API Namespaces heißen core, document, collection, database, query und graph.
Diese Namespaces und die darin enthaltenen Methoden, sowie deren Benennung, sollen
nun jeweils kurz erläutert werden. Des Weiteren gibt es noch den experimentellen Namespace collection-ops, hierzu siehe Abschnitt 4.7.3. Die vollständige Liste der Clarango APIMethoden inklusive deren Dokumentation findet sich im Anhang im Abschnitt 10.2.
4.6.1 Clarango Core
Wie bereits in Abschnitt 4.5.4 erläutert, stellt der core Namespace Methoden zur
permanenten Speicherung von Verbindungsdaten zur Verfügung. Die Namen dieser
Methoden wurden aus den in 4.5.5 erläuterten Gründen jeweils mit einem Rufzeichen am
Ende versehen. Die Haupt-Methode zur Speicherung von Verbindungsdaten ist setconnection! Diese nimmt eine Map mit Verbindungsdaten entgegen und speichert sie in
einer dem core Namespace angehörigen Variablen. Bei den in Abschnitt 4.4 untersuchten
Treibern wurde eine ähnliche Methode meist connect! genannt. Für Clarango wurde hier
jedoch bewusst ein anderer Name gewählt, um beim Benutzer nicht den Eindruck zu
erwecken, dass es sich um eine zustandshafte Datenbank-Verbindung handelt. Ähnlich wie
bei Monger ist auch ein Aufruf von set-connection! ohne Argumente möglich. In diesem Fall
werden Default-Werte für die Verbindung gesetzt (localhost, Port 8529, „_system“
58
Datenbank). Zusätzlich gibt es noch die Methoden set-connection-url!, set-default-db!, setdefault-collection! und set-default-graph!, die jeweils nur einen Parameter der Verbindung
dauerhaft ändern. Statt set-db!, wie die Methode bei mehreren der in 4.4 untersuchten
Treiber heißt, wurde die Methode zur Speicherung eines Datenbanknamens in Clarango
set-default-db! genannt. Der Name wurde gewählt, um dem Benutzer bewusst zu machen,
dass er trotzdem eine andere Datenbank verwenden kann und es sich nur um einen
Default-Wert handelt.
Zusätzlich wurden Methoden with-connection, with-db, with-collection und with-graph
ähnlich wie bei clj-orient und Clutch implementiert. Diese führen jeweils ein lokales
Rebinding der globalen connection Variablen bzw. eines Teiles dieser durch. Im „Scope“
dieser Methoden kann dann jeweils mit der jeweiligen geänderten Verbindung gearbeitet
werden, ohne diese permanent ändern zu müssen.
4.6.2 Document API
Der document Namespace enthält alle Methoden für das Document-CRUD. Hierzu zählen
sowohl die Methoden, die ein Dokument anhand seines Keys finden, ausgeben und ändern,
als auch die Methoden, die dieselben Aktionen anhand eines Beispiel-Dokumentes
durchführen. In der ArangoDB API handelt es sich hier zwar um zwei verschiedene
Interfaces (Interface for Documents und Interface for Simple Queries). Es schien jedoch
sinnvoll, diese Methoden in einem Namespace zusammenzufassen, da es sich in beiden
Fällen um Document-CRUD handelt. Auch bei Monger sind der Key- und der ExampleAnsatz Teil desselben Namespaces. Im Gegensatz zu Monger sollten die Methoden, die mit
einem Beispiel-Dokument arbeiten, in Clarango aber deutlicher als solche gekennzeichnet
werden. Daher wurden sie „delete-/replace-/update-by-example“ genannt. Bei Monger sind
zudem alle Document-CRUD Methoden Teil des collection Namespaces. Es schien jedoch
sinnvoll, bei Clarango einen eigenen Namespace nur für die Dokument-Operationen zu
schaffen, um diese als eine Einheit von den Collection-Operationen zu trennen.
Bei Clarango wurde sich außerdem gegen die Benennung der Document-CRUD
Methoden nach den HTTP-Methoden entschieden, so wie es bei Clutch und bei Elastisch
der Fall ist. Dieser Ansatz macht zwar durchaus Sinn, um den Benutzer auf die darunter
liegenden HTTP-Anfragen hinzuweisen; es wäre jedoch im Falle einer document/get
Methode zu einem Namenskonfikt mit der Clojure-Core Methode get gekommen. Im Falle
von Methodennamen put und post ist außerdem nicht intuitiv erkennbar, bei welcher
Methode es sich um die Erstellung und bei welcher Methode es sich um das Ersetzen eines
bereits bestehenden Dokumentes handelt. Es wurden daher die eindeutigeren Namen
59
create, replace-by-key, replace-by-example sowie update-by-key und update-by-example gewählt.
Letztere Methode führt einen HTTP PATCH Request durch.
4.6.3 Collection API
Im Unterschied zum Treiber Monger wurden im collection Namespace nur die Methoden
untergebracht, die der Erstellung und Modifikation von Collections dienen. Bei Monger
dagegen sind auch die Document-CRUD Methoden im collection Namespace enthalten.
Um eine sauberere Trennung zu erhalten, wurden die beiden Bereiche in Clarango getrennt.
Auch die Methode get-all-documents wurde hier untergebracht, obwohl diese in der
ArangoDB API Teil des document Interfaces ist. Da die Methode aber die URIs aller
Dokumente einer Collection in einer Liste ausgibt und nicht der Ausgabe der Dokumente
selber dient, handelt es sich nach Meinung des Autors eher um eine Operation auf
Collection-Ebene als um eine Document-CRUD Methode. Die Methode reiht sich eher
ein in die get-[...-]info[-...] Methoden des collection Namespaces, die eine ähnliche Ausgabe
erzeugen.
4.6.4 Datenbank API
Ähnlich wie die get-all-documents Methode im collection Namespace wurden im database
Namespace die Methoden get-collection-info-list und get-all-graphs untergebracht. Diese
geben jeweils alle Collections und alle Graphen in der Datenbank als Liste zurück und sind
nach Meinung des Autors auf der Datenbank-Ebene einzuordnen. In der ArangoDB API
sind sie jedoch jeweils Teil des graph und des collection Interfaces.
Des Weiteren enthält der database Namespace noch eine Methode create und eine Methode
delete zum Erzeugen und Löschen von Datenbanken ähnlich wie der collection und wie der
document Namespace. Hier wurden, um die Konsistenz zu wahren und obwohl es sich um
verschiedene Namespaces handelt, für die gleichen Aktionen jeweils dieselben Methodennamen gewählt. Die Aktionen beziehen sich aber auf unterschiedliche Ressourcen, die
jeweils am Namespace abzulesen sind.
4.6.5 Query API
Im query Namespace von Clarango wurden drei Interfaces der ArangoDB HTTP-API
vereint: explain, query und cursor. Alle drei Namespaces sind für das Auswerten und
Ausführen von Queries bzw. für das Auswerten der Ergebnisse einer Query zuständig. Um
die Clarango API möglichst übersichtlich und kompakt zu gestalten, wurden alle in einem
query Namespace vereint.
60
Das Senden von Queries wurde zunächst in einer einfachen Text-Variante umgesetzt. Das
heißt es wird den query Methoden lediglich ein fertiger Query-String übergeben, ähnlich
wie bei Neocons und seinen Cypher Queries. Als Erweiterung wäre später aber noch eine
mehr „clojuresque“ Variante denkbar, wie sie bei der Monger Query DSL 82 oder auch bei
dem Graph-Abfrage-Framework clj-gremlin83 vorzufinden ist: Hier werden anstatt Strings
zu übergeben mehrere Funktionen ineinander geschachtelt und so die Query
zusammengesetzt.
4.6.6 Graph API
Die Methoden des graph Namespaces orientieren sich von der Namensgebung her
weitestgehend an den Methoden aus dem document Namespace. Es gibt alle CRUDMethoden sowohl für Knoten als auch für Kanten: „get-/replace-/update-/delete-vertex“ sowie
„get-/replace-/update-/delete-edge“. Außerdem gibt es genau wie bei den Namespaces
document, collection und database eine create und eine delete Methode, um eine GraphRessource erstellen und wieder löschen zu können. Weiterhin gibt es eine Methode zum
Ausführen von Graph-Traversierungen: execute-traversal; sowie die Methoden get-vertices
und get-edges, die jeweils mehrere zusammenhängende Knoten und Kanten zurückgeben
und somit auch eine Art Traversierung darstellen. Hier kann man Parallelen ziehen zum
Treiber Neocons, der ebenfalls Methoden bietet, um jeweils nur Knoten oder Kanten zu
traversieren oder beides zusammen (was bei Neocons dann „Paths“ genannt wird).
Im Falle von graph wurden wieder zwei ArangoDB Interfaces kombiniert: Das eigentliche
Graph-Interface und das Interface für Traversierungen. Letzteres enthält nur eine einzelne
Methode zum Ausführen von Traversierungen und wurde daher in den graph Namespace
integriert.
4.6.7 Flexible Funktionssignaturen
Für die Clarango API sollte eine Möglichkeit gefunden werden, die es erlaubt, die APIMethoden in einer möglichst fexiblen Art und Weise aufzurufen. Dies bezieht sich sowohl
auf die Möglichkeit, automatisch Default-Werte zu nutzen, zum Beispiel für eine zu
verwendende Datenbank, aber auch auf die Möglichkeit, einer Methode zusätzliche
Optionen zu übergeben. Das Ziel dabei ist, es möglich zu machen, alle Funktionen in einer
möglichst kurzen Art mit wenigen Argumenten aufzurufen. Gleichzeitig sollen bei Bedarf
aber auch mehr Argumente erlaubt werden, falls der Benutzer eine detailliertere Kontrolle
wünscht.
82 Siehe http://clojuremongodb.info/articles/querying.html
83 https://github.com/olabini/clj-gremlin
61
Sollten die Angaben für die zu verwendende Datenbank und Collection im
Methodenaufruf weggelassen werden, so werden automatisch die im core Namespace
hinterlegten Default-Werte verwendet. Sollten diese nicht vom Benutzer gesetzt sein, so
wird ein Fehler geworfen. Eine ähnliche Möglichkeit, optional eine Datenbank anzugeben,
bietet zum Beispiel die Methode insert bei Monger (siehe 4.4.1). Die Möglichkeit
zusätzliche Optionen zu übergeben (zum Beispiel um mit der Methode document/updateby-key ein konditionales Update anhand einer Revisionsnummer durchzuführen) wurde
umgesetzt, indem den API-Methoden eine Map mit Optionen als zusätzliches Argument
übergeben werden kann. Diese kann an einer beliebigen Position zwischen dem CollectionNamen und dem Datenbank-Namen übergeben werden. Es handelt sich jedoch um ein
optionales Argument, welches auch weggelassen werden kann.
Alle zuletzt erwähnten Parameter stehen jeweils am Ende der Methodensignatur. Die
Flexibilität wurde hier erreicht, indem von der Möglichkeit optionaler Parameter bei
Clojures Funktionen Gebrauch gemacht wurde. Hierbei kann der Aufrufer einer Methode
eine beliebige Anzahl an zusätzlichen Parametern übergeben. Diese werden dann in der
Methodenimplementierung als Vektor bereitgestellt. Um die Möglichkeit umzusetzen, eine
zusätzliche Map mit Optionen an beliebiger Stelle zu übergeben, wurden zwei Methoden
implementiert, die eine Map anhand ihres Typs aus diesem Vektor herausfiltern.
62
4.7 Implementierungsdetails
Im Folgenden sollen einige weitere Implementierungsdetails von Clarango erläutert werden,
die vom Autor als wichtig angesehen werden.
4.7.1 Error-Handling
Zur Behandlung von Fehlern wurde für die erste Implementierung von Clarango eine sehr
einfache Variante gewählt. In der Methode, die für das Senden aller HTTP-Anfragen
zuständig ist84, wurde ein Try-Catch-Block eingefügt. In diesem findet die gesamte ServerInteraktion statt. Der Block fängt dann alle Fehler ab, die von clj-http geworfen werden.
Hierbei handelt es sich entweder um Fehler, die auf dem ArangoDB Server auftreten oder
um Fehler, die auf den HTTP/TCP/IP-Schichten auftreten. Ersteres kann zum Beispiel
auftreten, wenn versucht wird, eine ungültige Aktion auf dem Server durchzuführen, und
letzteres beispielsweise, wenn die Verbindung zum Server fehlschlägt.
Im Falle eines Fehlers wird eine weitere Methode zur Behandlung des Fehlers aufgerufen 85.
Bei dieser handelt es sich um eine Clojure-Multimethode. Die Methode filtert zwei
besondere Fehlerfälle heraus, indem nach Fehlerklassen unterschieden wird. Wenn ein
Fehler vom ArangoDB Server kommt, so wird dies als Textmeldung ausgegeben und
zusätzlich die Fehlerinformationen von ArangoDB ausgegeben. Im Falle eines
Verbindungsfehlers (Server nicht erreichbar oder ähnliches), wird dies ebenfalls als
gesonderte Meldung ausgegeben. Für alle anderen Fälle wird in einer DefaultImplementierung der Multimethode lediglich der Fehler geworfen, der vorher abgefangen
wurde.
4.7.2 Rückgabewerte
Bei den Rückgabewerten der Clarango API-Methoden stellte sich die Frage, was jeweils als
Rückgabewert zurückgegeben werden soll. Bei der Abfrage nach einem Dokument ist diese
Frage einfach zu beantworten, denn dort soll offensichtlich das Dokument an den Aufrufer
der Methode zurückgegeben werden. Bei anderen Operationen wie dem Anlegen oder
Löschen von Collections oder Datenbanken ist diese Frage jedoch nicht so leicht zu
beantworten.
Der ArangoDB Server liefert generell als Antwort auf jede Aktion eine Map mit diversen
Angaben zurück. Hierzu gehören oft Angaben über Erfolg und Misserfolg, der HTTP84 die Methode send-request im Namespace http-utility
85 handle-error im selben Namespace
63
Statuscode sowie bei Abfragen das Ergebnis der Abfrage. Dieses Ergebnis ist jedoch oft in
der Antwort-Map des Servers verschachtelt und mit unterschiedlichen Keys versehen. Man
könnte nun in Clarango als Rückgabewert der Methoden immer genau die Map
zurückgeben, die vom Server zurückkommt. Dies wäre konsistent und außerdem sicher
gegen Änderungen der ArangoDB API. Dagegen spricht aber, dass der Benutzer bei der
Abfrage nach einem Dokument intuitiv erwarten wird, dass er nur das Dokument als
Rückgabewert erhält. Genauso wird er bei einer Abfrage nach einer Liste von allen
Collections in einer Datenbank auch eine Liste als Rückgabewert erwarten und keine
verschachtelte Map, die irgendwo eine Liste enthält. Es wurde deshalb ein Mittelweg
gewählt.
Der Mittelweg besteht darin, bei offensichtlich erwarteten Rückgabewerten wie oben
beschrieben, diese Werte aus dem Server-Ergebnis herauszufiltern und zum Rückgabewert
der Methode zu machen. Damit aber bei Bedarf auch alle Daten verfügbar sind, wird das
gesamte Server-Ergebnis noch zusätzlich als Clojure-Metadaten an den Rückgabewert
angehängt.
Ein paar Beispiele: Bei einer Abfrage nach einem Dokument wird das Dokument vom
ArangoDB Server direkt unter dem Key :body im Ergebnis zurückgegeben. Bei einer
Abfrage nach allen Dokumenten in einer Collection wird das Ergebnis der Abfrage dagegen
unter dem Key „documents“ im :body-Teil abgelegt. Bei API-Aufrufen wie der Erzeugung
oder Löschung von Ressourcen wird als Haupt-Ergebnis nur ein Boolean-Wert
zurückgegeben, der über Erfolg oder Misserfolg berichtet. Dieser wird dann unter dem Key
„result“ abgelegt. Die Ergebnisse werden also immer unter verschiedenen Keys abgelegt.
Darum wurde eine Methode86 implementiert, die einen Vektor mit Keys übergeben
bekommt und einen hierarchischen Keyword-Lookup auf der Map, die vom Server
zurückgesendet wird, durchführt. Diese Methode macht es möglich, dass nur das vom
Benutzer erwartete Ergebnis direkt von der Clarango API-Methode zurückgegeben werden
kann. Damit der Benutzer aber bei Bedarf auch auf alle vom Server zurückgesendeten
Informationen zurückgreifen kann, wird die ganze vom Server zurückgelieferte Map immer
noch zusätzlich an den Rückgabewert der Clarango API-Methode als Clojure-Metadaten 87
angehängt.
86 incremental-keyword-lookup im Namespace http-utility
87 Siehe http://clojure.org/metadata; Metadaten sind ein Weg, um in Clojure zusätzliche Informationen
an einen Wert anzuhängen.
64
4.7.3 „klassische“ Datenbank-Methoden vs. „clojuresque“ Methoden
Ein weiterer Aspekt des Designs eines Clojure-Treibers für eine NoSQL Datenbank ist die
Überlegung, in wie weit sich die API so entwerfen lässt, dass die Arbeit mit ihr möglichst
mit der Clojure-nativen Art der Arbeit auf Datenstrukturen übereinstimmt. Dies wirft
insbesondere die Frage auf, in wie weit sich die API Methoden an „klassischen“ DatenbankMethoden orientieren sollen oder ob auf mehr „clojuresque“ Methoden gesetzt werden soll.
Einer der Autoren von [Emerick2012] machte den Vorschlag, den CouchDB Treiber
Clutch (siehe 4.4.2) um einige Methoden zu erweitern, die den Clojure CollectionMethoden assoc, dissoc, get usw. nachempfunden sind. In [wwwClutchGroup] argumentiert
er, dass bei der Benutzung von Clutch 95% der Interaktionen mit der Datenbank eine
Benutzung von Clojure-untypischen Methoden verlangen. Die Definitionen der ClojureMengenoperationen wie conj, assoc, dissoc etc. würden aber durchaus auch die Arbeit auf
einer Datenbank zulassen. Es gäbe nur den Unterschied, dass diese Methoden, angewendet
auf eine Datenbank, deren Zustand verändern, während die Clojure Collection-Methoden
immer eine neue Version der jeweiligen Datenstruktur erzeugen.
In der Testimplementierung88 hat der Autor die Methodennamen daher mit einem
Rufzeichen am Ende versehen, um den Unterschied zu den Clojure-Core-Methoden
deutlich zu machen. Die Methoden wurden implementiert, indem auf bereits verfügbare
Methoden der Clutch API aufgebaut wurde. Seit Version 0.3.1 sind diese Methoden auch
ofziell in Clutch als experimentelles Feature enthalten.
In dieser Bachelorarbeit soll der Versuch gemacht werden, ein ähnliches Feature testweise
für Clarango umzusetzen. Dabei soll ebenfalls auf die bereits verfügbaren Methoden der
Clarango API zurückgegriffen werden. Die erstellten Methoden sollen jedoch nicht zur
direkten Modifikation von Datenbanken dienen wie bei Clutch, sondern stattdessen
Mengenoperationen auf Collections erlauben. Dies bietet sich an, da es bei ArangoDB im
Unterschied zu CouchDB die Collections als Organisationsstruktur für Dokumente gibt.
Als Methoden zur probeweisen Implementierung wurden assoc89, dissoc90, conj91 und get92
ausgewählt. Diese sollen in einer Variante für Clarango umgesetzt werden. Um
Namenskollisionen mit den gleichnamigen Methoden aus dem Clojure-Core sowie mit den
88
89
90
91
92
Hier einzusehen: https://gist.github.com/cemerick/1485920
http://clojuredocs.org/clojure_core/clojure.core/assoc
http://clojuredocs.org/clojure_core/clojure.core/dissoc
http://clojuredocs.org/clojure_core/clojure.core/conj
http://clojuredocs.org/clojure_core/clojure.core/get
65
Methoden assoc!93, dissoc!94 und conj!95 zu vermeiden, wurden als Namen cla-assoc!, cla-dissoc!,
cla-conj! und cla-get! gewählt. Die Methoden wurden in einem separaten Namespace, der als
experimentell gekennzeichnet wurde, untergebracht: clarango.collection-ops96.
Nach der Wahl dieser Vorgaben war die Umsetzung nicht mehr schwer, da bei der
Implementierung auf bereits existierende Methoden aus dem document Namespace
zurückgegriffen wurde. Im Unterschied zu den Methoden aus dem document Namespace
wird hier jedoch erzwungen, dass das erste Argument der Methode jeweils der Name der
Collection sein muss, um eine gleiche Methoden-Signatur wie bei den Clojure-Core
Methoden zu erreichen.
Des Weiteren stellte sich die Frage, was von den Methoden als Rückgabewerte
zurückgegeben werden soll. Die entsprechenden Clojure-Core Methoden geben jeweils eine
veränderte Version der Datenstruktur zurück, da der übergebene Wert nicht verändert
werden kann. Bei den Clarango Methoden wird jedoch der Wert in der Datenbank
verändert und in der Server-Antwort der jeweiligen Operation ist die neue Version der
Datenstruktur nicht enthalten. Um trotzdem immer den Inhalt der ganzen Collection als
Rückgabewert zurückzugeben, müssten immer mehrere zusätzliche GET-Anfragen an den
Server gesendet werden. Dies würde einen großen zusätzlichen Overhead für jeden
Methoden-Aufruf bedeuten. Von dieser Möglichkeit wurde daher abgesehen und es werden
nur die Server-Rückgabewerte von den Methoden zurückgegeben; bzw. die entsprechenden
Rückgabewerte der unterliegenden document Methoden.
Als Ergebnis der testweisen Umsetzung dieser Methoden lässt sich daher die Frage stellen,
in wie weit diese Collection-Methoden Sinn machen. Sie unterscheiden sich nämlich
grundsätzlich in drei wichtigen Punkten von den Clojure-Core Methoden, die ihnen als
Vorbild dienen:
93
94
95
96
•
sie führen Veränderungen am Zustand der Datenbank durch
•
sie unterscheiden sich in ihren Rückgabewerten
•
es wird nicht wie bei den Clojure-Core Methoden das jeweilige Collection-Objekt
selbst übergeben, sondern der Name der Collection
http://clojuredocs.org/clojure_core/clojure.core/assoc !
http://clojuredocs.org/clojure_core/clojure.core/dissoc !
http://clojuredocs.org/clojure_core/clojure.core/conj !
https://github.com/edlich/clarango/blob/bb365b266ed390a16897a1807999da29b2bd6d55/src/clarango
/collection_ops.clj
66
4.7.4 Batch Requests
Es wurde ein Ansatz unternommen, mit Clarango auch die von der ArangoDB HTTP-API
unterstützten Batch-Requests97 zu unterstützen. Die Verwendung von Batch-Requests bietet
sich generell an, wenn wiederholt eine ähnliche Aktion ausgeführt werden soll. Ein Beispiel
ist das Einfügen mehrerer Dokumente nacheinander in eine Collection. Beim Senden
einzelner HTTP-Requests für jede Anfrage entsteht hier ein vermeidbarer Overhead durch
die verwendeten Protokolle HTTP/TCP/IP usw. Dieser lässt sich vermeiden, wenn alle
Einfüge-Aktionen gebündelt als ein HTTP-Request gesendet werden, was durch das BatchInterface der ArangoDB API möglich gemacht wird.
Das Batch-Interface erwartet hierbei, dass alle Anfragen hintereinander im Textkörper der
HTTP-Anfrage gesendet werden. Die Umsetzung mit der Library clj-http stellte sich
jedoch als schwer heraus. Die Library bietet zwar die Möglichkeit des Sendens von
mehreren POST-Requests innerhalb einer HTTP-Anfrage in Form von „Multipart FormPosts“. Es wurde zunächst angestrebt, diese Möglichkeit auch zu nutzen, um die BatchRequests umzusetzen. Sehr schnell zeichnete sich jedoch ab, dass das von der ArangoDB
API erwartete Format des HTTP-Textkörpers mit clj-http nur schwer umzusetzen ist.
Letztendlich scheiterte das Vorhaben daran, dass der von ArangoDB erwartete „TrennString“ zur Markierung der einzelnen Teil-Requests vom Server schon im Header der
HTTP-Anfrage erwartet wurde. Bei den Multipart Requests in clj-http wird dieser jedoch
automatisch generiert und es wurde keine Möglichkeit gefunden, den String bereits bei der
Generierung des HTTP-Headers zu erhalten und mitzusenden98.
Als Lösung würde sich hier anbieten, direkt auf die von clj-http benutzte Java Library
Apache HttpComponents zuzugreifen und dort nach einer Lösung zu suchen. Dies ist
jedoch ein erhöhter Aufwand und wurde aus Zeitgründen bisher nicht umgesetzt.
4.7.5 Allgemein verwendbare unterliegende Methoden
Bei der Implementierung von Clarango wurde Wert darauf gelegt, dass die Implementierungen der API-Methoden möglichst kurz sind. Hierzu wurde ein Satz an unterliegenden
Methoden geschaffen, die von allen Methoden der Clarango API gleichsam in deren
Implementierung verwendet werden. Die Implementierungen der API-Methoden sind
daher meist nur eine Zeile oder wenig mehr lang.
97 http://www.arangodb.org/manuals/current/HttpBatch.html
98 Dieses Problem wurde auch im clj-http GitHub-Repository als Frage formuliert, bis zum Zeitpunkt der
Fertigstellung dieser Arbeit wurde diese jedoch nicht beantwortet: https://github.com/dakrone/cljhttp/issues/191
67
Erreicht wurde dies durch von allen API-Methoden verwendeten Utility-Methoden zum
URI-Building, Wrapping der HTTP-Requests, sowie dem Lookup der DefaultVerbindungsdaten. Diese Methoden befinden sich in den Namespaces im Unterordner
src/clarango/utilities. Sie wurden so allgemein gehalten, dass sie bei der Implementierung
aller API-Methoden eingesetzt werden konnten.
Für weitere Details zur Architektur von Clarango und eine ausführlichere Beschreibung der
Utility-Methoden siehe auch die nächsten beiden Abschnitte 4.8 „Clarango SystemArchitektur“ und 4.9 „Exemplarische Untersuchung: Aufbau und Aufruf einer Clarango
Methode“.
68
4.8 Clarango System-Architektur
Im Folgenden soll der Aufbau und die Architektur von Clarango erläutert werden. Hierzu
wird jeweils eine kurze Beschreibung aller in Clarango vorhandenen Namespaces gegeben
und anschließend ihr Zusammenspiel in einem Diagramm verdeutlicht.
4.8.1 Clarango Namespaces
Die Namespaces in Clarango sind in zwei Ebenen gegliedert:
1. Namespaces mit API-Methoden, die vom Benutzer verwendet werden.
2. Utility-Namespaces, die nur intern verwendet werden sollen.
Die Datei-Struktur der Namespaces:
Abb. 2: Clarango Source Dateistruktur
core
Enthält Funktionen zur Speicherung von Verbindungsdaten.
Abhängigkeiten: keine
utilities/core-utility
Funktionen zum Abrufen der in core gespeicherten Verbindungsdaten sowie Funktionen
zum Filtern von optionalen Funktionsparametern bei den API Methoden.
Abhängigkeiten: core
utilities/uri-utility
Beinhaltet die Funktion zum Zusammenbau der Ressourcen-URIs.
Abhängigkeiten: core-utility
69
utilities/http-utility
Bietet Funktionen zum Zugriff auf Datenbank-Ressourcen. Dieser Namespace benutzt die
clj-http Library zum Senden von HTTP-Anfragen, abstrahiert diese jedoch weiter, um in
den Methoden der Clarango-API einen vereinfachten Zugriff auf die Ressourcen zu haben
und um Code-Wiederholungen zu vermeiden. Er fängt außerdem Fehler auf, die auf dem
HTTP-Level auftreten und wirft Custom-Fehlermeldungen.
Abhängigkeiten: core-utility, uri-utility, Cheshire Library, clj-http Library
database
Beinhaltet die Funktionen der Datenbank-Ebene, wie das Erstellen und Ändern von
Datenbanken und den Abruf von Informationen über diese.
Abhängigkeiten: core-utility, http-utility, uri-utility
collection
Beinhaltet die Funktionen der Collection-Ebene, wie das Erstellen und Ändern von
Collections und den Abruf von Informationen über diese.
Abhängigkeiten: core-utility, http-utility, uri-utility
document
Beinhaltet alle Funktionen des Document-CRUD. Dazu zählen das Document-CRUD mit
bekanntem Document-Key sowie das Document-CRUD mit Angabe eines BeispielDokuments.
Abhängigkeiten: core-utility, http-utility, uri-utility
graph
Beinhaltet alle Funktionen zum Erstellen und Bearbeiten von Graphen, sowie zum Abrufen
von Knoten und Kanten und dem Ausführen von Graph-Traversierungen.
Abhängigkeiten: core-utility, http-utility, uri-utility
query
Beinhaltet Funktionen zum Ausführen und Auswerten von AQL-Queries. Außerdem gibt
es Funktionen zum Umgang mit Datenbank-Cursors; so kann in einer großen Menge von
Suchergebnissen navigiert werden.
Abhängigkeiten: core-utility, http-utility, uri-utility
collection-ops
Dieser Namespace bietet Abstraktionen der Document-CRUD Methoden aus dem
Namespace document. Dadurch soll eine mehr „clojuresque“ Art des Arbeitens mit
70
Collections und Dokumenten geboten werden. Siehe dazu auch Abschnitt 4.7.3.
Abhängigkeiten: document
main
Dieser Namespace ist nur im Git-Branch development enthalten, da er nicht Teil der Library
Clarango sein soll. Er beinhaltet eine Main-Methode, die eine ausführliche Demonstration
der Großzahl aller Clarango API-Methoden bietet. So kann in der Kommandozeile mit
dem Befehl „lein run“ ein ausführlicher Test durchgeführt werden99.
Abhängigkeiten: alle Namespaces mit Ausnahme der Utility-Namespaces
4.8.2 Diagramm
Im folgenden Diagramm ist das Zusammenspiel der Namespaces und der importierten
Libraries in Clarango verdeutlicht. Ein Pfeil bedeutet, dass der Namespace, von dem der
Pfeil ausgeht, den Namespace, auf den der Pfeil zeigt, importiert.
Abbildung 3: Clarango Architektur und Abhängigkeiten der Namespaces
99 Nicht zu verwechseln jedoch mit den eigentlichen Tests, die mit „lein test“ ausgeführt werden können.
71
4.9 Exemplarische Untersuchung: Aufbau
und Aufruf einer Clarango Methode
Hier soll exemplarisch der Aufbau einer Methode des document Namespaces untersucht
werden. Dadurch soll der Aufbau von Clarango, das Zusammenspiel der Namespaces und
der Zweck der verschiedenen Utility-Methoden verdeutlicht werden.
Das folgende Diagramm verdeutlicht das Zusammenspiel der Namespaces für einen Aufruf
einer Methode des document Namespaces.
Abbildung 4: Ablaufdiagramm: Aufruf einer Methode aus dem document
Namespace (allgemein)
72
Als Beispiel für eine detailliertere Untersuchung soll nun ein Aufruf der Methode
document/get-by-example dienen. Die Methode soll mit folgenden Parametern aufgerufen
werden:
(document/get-by-example {:name "some test document"} {"limit" 2} "test-collection")
In dem Beispiel wird ein Dokument als erster Parameter übergeben. Nach diesem soll in
allen Dokumenten der Collection „test-collection“ gesucht werden. Das heißt es sollen alle
Dokumente zurückgegeben werden, die das Attribut :name „some test document“ enthalten.
Außerdem wird eine Map mit dem optionalen Parameter „limit“ 2 übergeben, womit die
Anzahl der Suchergebnisse auf zwei begrenzt wird. Da hier nicht explizit eine Datenbank
angegeben wurde, soll die im Clarango-Core gespeicherte Default-Datenbank verwendet
werden. In diesem Beispiel soll diese den Namen „test-DB“ tragen. Die im Core
gespeicherte Server-URL soll „http://localhost:8529“ sein.
Ein Blick in die Datei document.clj100 offenbart die relativ kurze Implementierung der getby-example Methode:
(defn get-by-example
"[… docstring …]"
[example & args]
(http/put-uri [:body "result"]
(build-ressource-uri "simple/by-example" nil nil (filter-out-database-name args))
(merge
{:example example :collection (filter-out-collection-name args)}
(filter-out-map args))))
Die Liste der Methoden-Argumente besteht nur aus einem festen Argument: Der ExampleMap, nach der gesucht werden soll (example). Alle weiteren Argumente sind optional (&
args). Hierzu zählen der Collection-Name, der Datenbank-Name und eine Map mit
weiteren Optionen (im obigen Beispiel {„limit“ 2}). Sollten beim Aufruf kein CollectionName und/oder kein Datenbank-Name angegeben werden, so wird jeweils der DefaultWert verwendet, welcher im Core gesetzt wurde. Soll jedoch ein Datenbank-Name
angegeben werden, so muss auch ein Collection-Name angegeben werden, da in der
Parameter-Liste der Collection-Name vor dem Datenbank-Namen steht.
Im Folgenden sollen kurz alle wichtigen Methoden, die in der get-by-example Methode
aufgerufen werden (rötlich hervorgehoben), erläutert werden.
filter-out- Methoden
Diese Methoden aus dem Namespace core-utility dienen dazu, aus dem Vektor der
optionalen Funktionsparameter (& args) von get-by-example die jeweils benötigten Teile
100https://github.com/edlich/clarango/blob/master/src/clarango/document.clj
73
herauszufiltern. Es wird erkannt, an welcher Stelle sich die Map mit weiteren Optionen,
falls vorhanden, befindet, und an welchen Stellen sich, falls vorhanden, der Name einer
Collection und einer Datenbank befinden. Sollten letztere bei einem Aufruf von get-byexample nicht vorhanden sein, so werden automatisch die Default-Werte aus dem Clarango
Core nachgeschaut und zurückgegeben.
Für den oben gegebenen Beispiel-Aufruf der get-by-example Methode gibt flter-out-map die
Options-Map {„limit“ 2} zurück. flter-out-collection-name gibt den Namen „test-collection“
zurück und flter-out-database-name gibt den Namen der im Clarango Core gespeicherten
Default-Datenbank „test-DB“ zurück.
build-ressource-uri
Mit einem Aufruf von uri-utility/build-ressource-uri wird der URI der Ressource, auf die
zugegriffen werden soll, zusammengesetzt. Der Quellcode der Methode lautet:
(defn build-ressource-uri
"[… docstring …]"
([type]
(connect-url-parts (get-safe-connection-url) "_api/" type))
([type ressource-key]
(connect-url-parts (get-safe-connection-url) "_db/" (get-default-db) "_api/"
type (get-default-collection-or-graph type) ressource-key))
([type ressource-key collection-name]
(connect-url-parts (get-safe-connection-url) "_db/" (get-default-db) "_api/"
type collection-name ressource-key))
([type ressource-key collection-name db-name]
(connect-url-parts (get-safe-connection-url) "_db/" db-name "_api/" type
collection-name ressource-key)))
Es handelt sich um eine Methode mit verschiedenen Implementierungen für jeweils eine
unterschiedliche Anzahl an Argumenten. In jedem Fall gefordert ist der type Parameter.
Hierbei handelt es sich im Allgemeinen um den Namen des Interfaces der ArangoDB
HTTP-API, auf das zugegriffen werden soll. Zum Beispiel „document“ oder „graph“. Als
zweiter Parameter kann der Key einer Ressource übergeben werden. Hier wird
üblicherweise der Key eines Dokumentes übergeben. Oft wird hier aber auch nil
übergeben, wenn ein URI ohne Key gebildet werden soll. Dies ist auch bei der Methode
get-by-example der Fall, da hier nach Dokumenten gesucht werden soll, deren Keys noch
unbekannt sind.
Für das oben genannte Beispiel wird die build-ressource-uri Methode mit vier Parametern
aufgerufen. Für den ersten Parameter type wird der String „simple/by-example“ übergeben;
74
„simple“, da es sich um das API-Interface der Simple-Queries101 handelt; „by-example“ ist die
API-Methode, auf die zugegriffen werden soll. Als Ressource-Key wird nil übergeben (da
die Keys noch unbekannt sind) und ebenso auch als Collection-Name, da dieser bei der byexample Methode im Body der HTTP-Anfrage übergeben werden muss. Als vierter
Parameter wird der von der Methode flter-out-collection-name zurückgegebene Name der
Default-Datenbank „test-DB“ übergeben.
Der Rückgabewert der build-ressource-uri Methode lautet somit:
http://localhost:8529/_db/test-DB/_api/simple/by-example
put-uri
Auf der äußersten Ebene der Methodenimplementierung von get-by-example steht ein
Aufruf der Methode http/put-uri. Diese sendet eine PUT-Anfrage an die Ressource, deren
URI vorher von der build-ressource-uri zusammengesetzt wurde. Die Implementierung der
Methode im Namespace http-utility lautet:
(defn put-uri
([response-keys uri]
(send-request :put response-keys uri nil nil))
([response-keys uri body]
(send-request :put response-keys uri body nil))
([response-keys uri body params]
(send-request :put response-keys uri body params)))
Hierbei handelt es sich nur um eine Hüllmethode für die Methode, die die eigentlichen
HTTP-Anfragen sendet: send-request. Die Hüllmethode put-uri wurde geschaffen, damit
sich send-request auf einfache Art mit einer verschiedenen Anzahl an Parametern aufrufen
lässt. In den Implementierungen der Clarango API-Methoden reduziert sich so die Anzahl
der nils, die übergeben werden müssen wenn einzelne Parameter weggelassen werden sollen.
Auch die Auswahl der HTTP-Methode wird hier gleich mit vorgenommen: :put. Die
Implementierung der send-request Methode lautet:
(defn- send-request
[method response-keys uri body params]
(if (console-output-activated?)
(println (get-uppercase-string-for-http-method method) " connection address: "
uri))
(try
(let [map-with-body (if (nil? body) {} {:body (generate-string body)})
response (http/request (merge {:method method :url uri :debug (httpdebugging-activated?) :query-params params} map-with-body))
filtered-response (filter-response response response-keys)]
(if (type-output-activated?) (println (type filtered-response)))
101http://www.arangodb.org/manuals/1.4.5/HttpSimple.html
75
;; append the original server response (filtered only one level, usually :body)
as metadata
(with-meta filtered-response
(filter-response response [(first response-keys)])))
(catch Exception e (handle-error e))))
Die Methode beginnt mit einer Konsolen-Ausgabe, bei der der URI der Ressource und die
HTTP-Methode ausgegeben werden, sofern ein globaler Switch im Namespace (consoleoutput-activated?) auf true gesetzt wurde. Danach beginnt die eigentliche Implementierung
der Methode. Diese ist vollständig in einen try-catch Block gehüllt. Hier werden
Verbindungsfehler und Fehler, die vom Datenbank-Server kommen, abgefangen. Zur
Behandlung der Fehler wurde eine separate Methode handle-error geschaffen. Bei dieser
handelt es sich um eine Clojure-Multimethode, die verschiedene Fehlerarten unterschiedlich behandelt.
Innerhalb des try Blocks findet das eigentliche Senden der Server-Anfrage statt. Hierzu wird
Gebrauch von der request Methode aus der clj-http Library gemacht. Diese Methode
unterstützt alle HTTP-Verben. Aus dem Rückgabewert der request Methode werden dann
die gewünschten Teile herausgefiltert, die der send-request Methode im Parameter responsekeys als Vektor übergeben wurden. Im Falle von get-by-example lautet dieser Vektor [:body
„result“]. Das bedeutet, dass aus dem Server-Rückgabewert zunächst der Teil herausgefiltert
wird, der unter dem Key :body zu finden ist, und dann aus diesem der Teil, der unter dem
Key „result“ zu finden ist. Der ungefilterte Rückgabewert der Server-Anfrage wird dann
noch zusätzlich an den Rückgabewert von send-request mittels with-meta als Clojure Metadaten angehängt.
Zusammenfassung
Im oben genannten Beispiel wird die HTTP-Anfrage an den folgenden URI gesendet:
http://localhost:8529/_db/test-DB/_api/simple/by-example
Der Body der HTTP-Anfrage enthält das Beispiel-Dokument
:example {:name „some test document“}
und den Namen der Collection
:collection „test-collection“
sowie den optionalen Parameter
„limit“ 2
Durch die Verwendung der Cheshire Library werden alle Parameter vor dem Senden in das
JSON-Format gebracht, wobei unter anderem alle Clojure Keywords in einfache Strings
umgewandelt werden.
76
Zur Verdeutlichung des Ablaufs wird dieser nochmals in einem Diagramm dargestellt. Hier
sind nun zusätzlich die aufgerufenen Methoden und, falls nicht zu lang, deren Rückgabewerte dargestellt:
Abbildung 5: Ablaufdiagramm: Aufruf einer Methode aus dem document Namespace (speziell)
77
5. Testing / Qualitätssicherung
Um die ordnungsgemäße Funktion von Clarango sicherzustellen, wurde damit begonnen,
eine Reihe von Tests für die Library zu implementieren. Diese können in Leiningen dann
automatisch mit dem Kommando „lein test“ ausgeführt werden. Der Test-Code ist auf
GitHub hier einsehbar:
https://github.com/edlich/clarango/tree/master/test/clarango/test
Für jeden Namespace der Clarango Library soll eine Datei mit Testcode erstellt werden.
Diese enthält dann jeweils einen Aufruf des Makros deftest, welches eine „Test-Suite“ für
den jeweiligen Namespace definiert. Bei der „Suite“ handelt es sich um eine Funktion ohne
Argumente und mit entsprechenden Metadaten, die sie als Test kennzeichnen. Diese
Funktion enthält wiederum Aufrufe der Methoden der Clarango API. Es wird dann jeweils
überprüft, ob die API Methoden die erwarteten Ergebnisse zurückliefern bzw. ob die
erwarteten Fehler auftreten. Alle in den Tests verwendeten Funktionen und Makros
stammen aus dem standardmäßig in Clojure integrierten Test-Framework clojure.test102.
Zum Zeitpunkt der Fertigstellung dieser Arbeit wurden die drei Test-Suites core-test,
database-test und document-test erstellt. Hier soll einmal beispielhaft der Code für den Test
des core Namespaces abgebildet werden:
(ns clarango.test.core
(:use clojure.test clarango.core))
(deftest core-test
(testing "Check the correct connection settings"
(is (nil? (get-connection)) "con must be nil initially")
(is (false? (connection-set?)) "con must be nil initially")
(set-connection!) ;; call without arguments
(is (= {:db-name "_system",
:connection-url "http://localhost:8529/"}
(get-connection)) "Mandatory default values!")
(set-connection-url! "http://localhost:9999/")
(set-default-db! "another-db")
(set-default-collection! "another-collection")
(is (= {:db-name "another-db",
:connection-url "http://localhost:9999/",
:collection-name "another-collection"}
(get-connection)) "obvious receive what has been set")))
102http://clojuredocs.org/clojure_core/clojure.test
78
Zusätzlich zur Möglichkeit des lokalen Ausführens der Tests mit Leiningen wurde auch von
der Möglichkeit Gebrauch gemacht, den Build- und Testing-Dienst Travis CI103 in das
Clarango GitHub-Repository einzubinden. Der Dienst wird unter anderem durch das
Einfügen einer Datei .travis.yml104 in das Repository aktiviert. Bei jedem Commit, der auf
GitHub hochgeladen wird, werden dann alle definierten Tests ausgeführt
[wwwTravisGuide]. Sollte dies ohne Fehler erfolgen, so wird im GitHub-Repository ein
grüner Button mit der Aufschrift „build passing“ angezeigt. Über die Konfigurationsdatei
.travis.yml wurde außerdem ein Installationsscript für ArangoDB in das Repository
eingebunden105, sodass in der Travis CI-Testumgebung eine lauffähige Version von
ArangoDB zur Verfügung steht.
103https://travis-ci.org/
104https://github.com/edlich/clarango/blob/master/.travis.yml
105Hier einsehbar:
https://github.com/edlich/clarango/blob/df75ac49d5b0c527b668ef75c135c914fa64c95b/setup_arango
db_1.4.sh
79
6. Anwendungsdemo
Im Folgenden soll ein Beispiel zur Benutzung der Clarango Library gegeben werden. Dabei
sollen möglichst alle Methoden der Clarango API abgedeckt werden. In Fällen, wo sich
API-Methoden sehr ähnlich sind, wurde aber unter Umständen auf den zusätzlichen Aufruf
einer weiteren Methode verzichtet. Außerdem sollen die verschiedenen Arten der Angabe
von Verbindungsdaten demonstriert werden. Das hier abgebildete Codebeispiel wird
abschnittweise abgedruckt und kommentiert und ist in ähnlicher Form auch im Branch
development im Namespace main zu finden:
https://github.com/edlich/clarango/blob/8829d7f351a8679b112c28ffdd6a9fb6ff54dcf5/src
/clarango/main.clj
1. Erstelle eine Datenbank, sowie eine Collection und führe einige Document-CRUD
Operationen durch:
;; connect to defaults: localhost and port 8529
(cla-core/set-connection!)
;; create Database "test-DB"
(database/create "test-DB" [{:username "test-user"}])
;; create Collection "test-collection" in DB "test-DB"
(collection/create "test-collection" "test-DB")
;; document CRUD
(document/create {:_key "test-doc" :name "some test document"} "test-collection"
"test-DB")
(document/update-by-key {:additional "some additional info"} "test-doc" "testcollection" "test-DB")
(document/get-by-key "test-doc" "test-collection" "test-DB")
(document/replace-by-example {:name "new version of our test document"}
{:additional "some additional info"} "test-collection" "test-DB")
2. Benutze die „clojuresquen“ Methoden aus dem Namespace collection-ops, um weitere
Dokumente zur bereits existierenden Collection hinzuzufügen, zu lesen und wieder zu
löschen:
;; set default DB; this database will be used in the following methods without
explicitely having to pass it
(cla-core/set-default-db! "test-DB")
;; collection ops : assoc, dissoc, conj
(cla-assoc! "test-collection" "new-document-1" {:description "some test document to
test the clojure idiomatic collection methods" :key-type "given key"})
(cla-conj! "test-collection" {:description "some test document to test the clojure
idiomatic collection methods" :key-type "auto generated key"})
(cla-get! "test-collection" "new-document-1")
(cla-dissoc! "test-collection" "new-document-1")
80
3. Nehme Änderungen an der Collection vor und gebe einige ihrer Eigenschaften aus.
Entferne sie zuletzt aus dem Arbeitsspeicher des Datenbank-Servers und lösche sie dann
ganz:
;; get information about the collection and a list of all documents inside it
(collection/get-info "test-collection")
(collection/get-all-documents "test-collection")
;; rename the collection and modify it's properties
(collection/rename "new-name-test-collection" "test-collection")
(collection/modify-properties {"waitForSync" true} "new-name-test-collection")
(collection/get-extended-info-figures "new-name-test-collection")
;; unload and delete collection
(collection/unload "new-name-test-collection")
(collection/delete "new-name-test-collection")
4. Erstelle einen Graphen sowie die dazu notwendigen Collections für Knoten und Kanten.
Führe einige Graph-Operationen wie das Erstellen, Löschen und Ausgeben von Knoten
durch. Führe eine Graph-Traversierung durch. Suche weiterhin in der Vertex-Collection
mittels einer AQL-Abfrage nach Knoten mit bestimmten Eigenschaften. Gebe außerdem
alle verfügbaren Datenbanken aus, sowie alle Collections in der gerade neu erstellten
Datenbank. Benutze die with-db und with-graph Methoden zum Festlegen eines Kontextes
für die weiteren Operationen. Lösche zuletzt alle Datenbanken:
;; first create another Database "GraphTestDB"
(database/create "GraphTestDB" [])
;; now list all available databases
(database/get-info-list)
;; perform next operations in the context of "GraphTestDB"
(with-db "GraphTestDB"
;; create vertex and edge collections "people" and "connections"
(collection/create "people" {"type" 2})
(collection/create "connections" {"type" 3})
;; now list all available collections, excluding the system collections
(database/get-collection-info-list {"excludeSystem" true})
;; create graph "test-graph"
(graph/create "test-graph" "people" "connections")
;; now get all available graphs
(database/get-all-graphs)
;; perform next operations in the context of the graph "test-graph"
(with-graph "test-graph"
;; create vertices "Peter", "Bob", "Clara", "Jessica", "Alice" with :ages
(graph/create-vertex {:_key "peter" :name "Peter" :age 25})
(graph/create-vertex {:_key "bob" :name "Bob" :age 28})
(graph/create-vertex {:_key "clara" :name "Clara" :age 29})
(graph/create-vertex {:_key "jessica" :name "Jessica" :age 23})
(graph/create-vertex {:_key "alice" :name "Alice" :age 20})
81
;;; perform query: find all people who are older than 24
;; first validate the query
;; then explain (how the query would be executed on the server)
;; then actually execute it
(query/validate "FOR p IN people FILTER p.age > 24 RETURN p")
(query/explain "FOR p IN people FILTER p.age > 24 RETURN p")
(query/execute "FOR p IN people FILTER p.age > 24 RETURN p")
;; create edges with labels "friend", "boyfriend", "girlfriend"
;; save one key to use this edge later
(let [edge-key (get (graph/create-edge {:$label "friend"} "peter" "alice")
"_key")]
(graph/create-edge {:$label "friend"} "alice" "clara")
(graph/create-edge {:$label "friend"} "clara" "jessica")
(graph/create-edge {:$label "boyfriend"} "alice" "bob")
(graph/create-edge {:$label "girlfriend"} "bob" "alice")
;; get vertices that have connections going from the vertex "peter"
(graph/get-vertices "peter" 10 10 true nil)
;; update one edge
(graph/update-edge {:description "Peter and Alice have been friends for
over 6 years"} edge-key)
;; get all edges that are outgoing from the vertex "peter"
(graph/get-edges "peter" 10 10 true nil)
;; execute a graph traversal
(graph/execute-traversal "peter" "people" "connections" "inbound")
;; delete one edge
(graph/delete-edge edge-key)
;; delete one vertex
(graph/delete-vertex "peter")))
;; delete the graph
(graph/delete "test-graph"))
;; delete databases
(database/delete "GraphTestDB")
(database/delete "test-DB")
Im Anhang unter 10.1 finden sich zusätzlich die Ergebnisse des hier gezeigten Beispiels in
Form von Konsolenausgaben.
82
7. Fazit und Ausblick
Im Verlaufe dieser Arbeit wurde eine funktionierende Version eines Clojure-Treibers für
ArangoDB entwickelt. Dabei wurden zwar nicht alle Funktionen, die das ArangoDB
HTTP-Interface bietet, in Clarango umgesetzt. Es wurde jedoch ein Großteil der
Funktionen implementiert, die ArangoDB als Multimodel-Datenbank charakterisieren:
Document-CRUD und Graph-Funktionen; außerdem weitere Funktionen, die im Rahmen
dessen benötigt werden, wie das Management von Collections und Datenbanken und die
Verbindungsdatenspeicherung; außerdem wurden Query-Funktionalitäten implementiert.
Eine genaue Checkliste der in Clarango implementierten ArangoDB Interfaces findet sich
im Anhang unter 10.3.
Beim Design des Treibers wurde versucht, eine möglichst konsistente und intuitive API zu
entwerfen. Um dies zur erreichen, wurden verschiedene Clojure-Treiber für andere NoSQL
Datenbanken untersucht. Beim Design der Clarango API wurden dann Eigenschaften, die
bei mehreren Treibern übereinstimmten, sowie Ideen, die sinnvoll erschienen,
übernommen. Es konnte aber nur eine begrenzte Anzahl an Treibern untersucht werden
und auch nicht alle Aspekte von allen Treibern konnten gleichzeitig mit in die Clarango
API einfießen.
Bei der Auseinandersetzung mit der ArangoDB HTTP-API wurde festgestellt, dass diese
nicht einhundertprozentig konsistent ist. Am Beispiel der Collection-Namen hat man
gesehen, dass diese bei verschiedenen API-Methoden an ganz unterschiedlichen Stellen
übergeben werden. Dies hat vermutlich den Grund, dass die API zusammen mit ArangoDB
über den Zeitraum von mehr als einem Jahr gewachsen ist. So wurden andere
Vorgehensweisen bei neu hinzugefügten API-Methoden vermutlich für besser befunden als
die bereits existierenden. Um die Abwärtskompatibilität zu wahren, wurden jedoch die
älteren, schon bestehenden Methoden nicht mehr geändert.
Hier zeigt sich, wie schwer es ist, eine konsistente API zu entwerfen. Auch bei sorgfältiger
Planung am Anfang werden sich im Laufe der Weiterentwicklung einer Software die
Anforderungen ändern oder auch die Ansichten der Entwickler darüber, was gutes und was
schlechtes Design ist. Zusätzlich wird es, je größer eine API wird, immer schwerer, einen
konsistenten Stil zu wahren. Da Clarango vollständig innerhalb von ein paar Monaten
umgesetzt wurde, wurde bisher gut das Ziel erreicht, eine konsistente API zu schaffen. Im
Laufe der weiteren Entwicklung von Clarango wird dies allerdings vermutlich immer
schwerer werden, besonders wenn sich die API von ArangoDB einmal ändern sollte.
83
Es wurde weiterhin angestrebt, beim Design der Clarango API ein möglichst „clojuresques“
Design umzusetzen. Hier wurde versucht, sich an den Standards und Best-Practises der
Sprache zu orientieren. Es zeigte sich deutlich das Spannungsfeld zwischen einer
zustandshaften Datenbank auf der einen Seite und dem zustandslosen Ansatz von REST
und Clojure auf der anderen Seite. Diese beiden „Pole“ sollten so gut es geht miteinander
vereinbart werden. Hier musste abgewogen werden zwischen vollständiger Zustandslosigkeit bzw. funktionalem Design und der Zwischenspeicherung von Verbindungsdaten.
Letztere erhöht nach Meinung des Autors erheblich den Benutzungskomfort von Clarango.
Daher wurde ein Kompromiss eingegangen und eine Variable eingeführt, die
Verbindungsdaten zwischenspeichert und die mit verschiedenen Methoden manipulierbar
ist. Trotzdem lässt sich wohl sagen, dass Clarango gegenüber „klassischen“ Datenbanktreibern in objektorientierten Sprachen sehr viel weniger zustandshaft ist.
Um dennoch einen Schritt mehr in Richtung „clojuresquem“ Design zu gehen, wurden
testweise einige Methoden implementiert, die sich die Methoden zur Manipulation von
Collections in Clojure zum Vorbild nehmen. Hier wurden jedoch einige Kritikpunkte
gefunden, da sich eine zustandshafte Collection in einer Datenbank grundlegend anders
verhält als die Collections in Clojure. Aufgrund dieser Unterschiede ist der Sinn dieser
Methoden zweifelhaft, vor allem da diese ein gleiches Verhalten beider Arten von
Collections suggerieren.
Mit der agilen Herangehensweise bei der Entwicklung von Clarango wurden gute
Erfahrungen gemacht. Es konnte schon mit der Entwicklung begonnen werden, während
gleichzeitig noch das ArangoDB HTTP-Interface umfassend untersucht wurde und dessen
Anforderungen an den Clojure-Treiber noch nicht vollständig klar waren. Dies war
einerseits motivierend, da schnell erste Ergebnisse sichtbar waren und nach jedem
Entwicklungsschritt eine lauffähige Version von Clarango verfügbar war; andererseits
konnten die bisher umgesetzten Features schon evaluiert werden und die daraus gezogenen
Schlüsse in das weitere Design einfießen.
Was die weitere Entwicklung von Clarango angeht, sind in Zukunft noch einige
Verbesserungen denkbar:
•
Das Error-Handling könnte verbessert werden: Es könnten eigene Java ErrorKlassen entwickelt werden, die bei HTTP-Fehlern (also auch bei Error-Meldungen
von ArangoDB) geworfen werden, anstatt hier auf Default-Typen zurückzugreifen.
So wäre schon vom Namen der auftretenden Fehler her klar, dass es sich um Fehler
handelt, die in Clarango aufgetreten sind. Dies wäre vor allem sinnvoll, wenn
84
Clarango in einer komplexeren Anwendung verwendet wird, die noch andere
Libraries verwendet. So kann in der Entwicklung der Anwendung auf einen Blick
festgestellt werden, ob der Fehler in einer der Libraries aufgetreten ist, und in
welcher, oder ob er vom eigenen Code hervorgerufen wurde.
•
Andererseits könnte in vielen Fällen die Aussagekraft von Clojure-Fehlermeldungen,
die in Clarango auftreten, deutlich verbessert werden, wenn eine Typ-Überprüfung
der Eingabeparameter durchgeführt würde. Beim Testen der Clojure API-Methoden
passierte es häufiger, dass diese versehentlich mit Parametern eines falschen Typs
aufgerufen wurden. Dies war auch häufig der Fall, wenn eigentlich nur die
Reihenfolge der Parameter versehentlich vertauscht wurde. Da die Typen hier nicht
überprüft werden, werden die Werte einfach an die Funktionen der unteren Level
„durchgereicht“ und es tritt ein Fehler bei der ersten Methode auf, die diesen Typ
nicht mehr verarbeiten kann. Entsprechend irreführend sind oft die Fehlermeldungen, die in solchen Fällen auftreten. Würden dagegen die Typen überprüft
werden, so würden direkt aussagekräftige Fehler an der Quelle des Fehlers auftreten.
•
Das Testing sollte vervollständigt werden, sodass alle Namespaces von Clarango
einen korrespondierenden Test-Namespace erhalten und alle API-Methoden
umfassend getestet werden.
•
Batch-Requests sollten implementiert werden. Dies wurde zwar begonnen, aber
nicht fertiggestellt, da hier wie erläutert auf der Ebene der Apache HttpComponents Library gearbeitet werden muss, was die Entwicklungszeit deutlich
erhöht. Die Batch-Requests machen aber in der Praxis viel Sinn, besonders als
Alternative zu vielen ähnlichen und kurz hintereinander gesendeten DatenbankAnfragen. Die Batch-Funktionalität könnte dann nahtlos in die Clarango API
integriert werden, indem man beim Einfügen oder Löschen mehrerer Dokumente
statt einem einzelnen Key oder Beispiel-Dokument einen Vektor mit mehreren
Dokumenten bzw. mehreren Keys übergibt. So müssten nicht einmal zusätzliche
API-Methoden eingeführt werden.
•
Um den Benutzungskomfort von Clarango zu erhöhen, könnte man es möglich
machen, beim Erstellen von Dokumenten und anderen Ressourcen, an den Stellen,
die die Namen bzw. Keys von anderen Ressourcen als Parameter erwarten, auch
direkt den Rückgabewert der Methode anzugeben, die die entsprechende Ressource
erstellt hat. So muss aus dem Rückgabewert letzterer Methoden nicht das :_key
Attribut herausgefiltert werden. Wird also statt einem Keyword bzw. einem String
eine Map übergeben, sucht Clarango automatisch in dieser Map nach dem :_key
Attribut. Dies würde unter anderem die Erstellung von Graphen deutlich
85
vereinfachen. Aus gerade erstellten Knoten müssten nicht mehr die generierten Keys
herausgefiltert werden, sondern es könnte gleich der ganze Rückgabewert der
graph/create-vertex Methode an die graph/create-edge Methode übergeben werden,
um eine Kante zu erstellen106. Diese Option zur Verbesserung des Benutzungskomforts steht jedoch in Konfikt mit der oben erwähnten möglichen Überprüfung
der Eingabeparameter der Clarango API-Methoden. Wenn statt Strings auch an
jeder Stelle alternativ Maps übergeben werden können, so kann kein Fehler mehr
aufgrund eines falschen Typs geworfen werden.
•
Die Query API von Clarango könnte in einer mehr „clojuresquen“ Variante
umgesetzt werden, nach dem Vorbild wie zum Beispiel bei clj-gremlin und bei der
Query DSL von Monger. So könnten komplexe Queries auch durch das
Ineinanderschachteln von Methodenaufrufen zusammengesetzt werden.
•
Zusätzlich könnte zur Geschwindigkeitserhöhung von ArangoDBs Unterstützung
asynchroner Requests („Fire and Forget“ Strategie 107) Gebrauch gemacht werden.
Damit können mehrere Anfragen auf einmal an die Datenbank gesendet werden,
ohne dass der Client auf die Antwort des Servers warten muss. Somit wird dieser
nicht blockiert, während die Anfrage auf dem Server bearbeitet wird. Stattdessen
wird der Request auf dem Datenbank-Server zunächst in einer Queue gespeichert
und die Antwort kann dann nach der Bearbeitung durch den Client „abgeholt“
werden. Hier könnte man sich dann auch evtl. Clojures besondere Stärken im
Bereich der nebenläufigen Programmierung zunutze machen, die bisher gar nicht
für Clarango genutzt wurden. Zu beachten ist jedoch, dass für das Abholen der
Antworten ein Polling notwendig ist und außerdem der Server bei jeder gesendeten
Anfrage zunächst eine inhaltslose Antwort mit dem HTTP-Statuscode 202 zurücksendet. Die Anzahl der gesendeten HTTP-Requests würde sich somit bei dieser
Technik stark erhöhen und somit auch der Gesamt-Overhead des Treibers, der ja
ursprünglich möglichst gering gehalten werden sollte. Der Nutzen eines solchen
Features müsste somit sorgfältig abgewogen werden.
Wie bereits am Anfang dieser Bachelorarbeit erwähnt, arbeiten die Entwickler von
ArangoDB kontinuierlich an der Datenbank und ihren Funktionen weiter. Mit der Version
2108 von ArangoDB wird ein weiteres bedeutendes Feature hinzugefügt: das sogenannte
Sharding. Mit diesem ist es möglich, eine Collection, sollte sie sehr groß sein, über mehrere
Datenbank-Server aufzuteilen. Damit wird in ArangoDB auch erstmals eine horizontale
106Vgl. hierzu auch das Codebeispiel in Neocons aus Abschnitt 4.4.6: „Create Vertices und Edge ...“
107Vgl. http://www.arangodb.org/manuals/current/HttpJob.html
108https://www.arangodb.org/2014/03/05/arangodb-sharding-release-2-0-0-rc1
86
Skalierung in Dimensionen wie zum Beispiel bei MongoDB möglich. Da ArangoDB auch
heute schon über eine Vielzahl an Funktionen verfügt und ein wirkliches Allround-Talent
unter den NoSQL Datenbanken darstellt, ist zu erwarten, dass sich die Nutzerzahl
vermutlich bald deutlich erhöhen wird. Vielleicht wird gar irgendwann das erklärte Ziel der
Entwickler erreicht und ArangoDB wird zum „MySQL in NoSQL“. In diesem Fall würde
möglicherweise auch der im Rahmen dieser Bachelorarbeit entwickelte Treiber Clarango zu
einer breiteren Nutzung gelangen, da die Zukunft der noch jungen Sprache Clojure
ebenfalls vielversprechend aussieht.
87
8. Abbildungsverzeichnis
•
Abbildung 1: Diagramm eines funktionalen Programms; nach Abbildung 2-2 auf
S. 81 in [Emerick2012] – S.11
•
Abbildung 2: Clarango Source Dateistruktur; Screenshot aus dem Mac Finder;
– S.70
•
Abbildung 3: Clarango Architektur und Abhängigkeiten der Namespaces
– S.72
•
Abbildung 4: Ablaufdiagramm: Aufruf einer Methode aus dem document
Namespace (allgemein) – S.73
•
Abbildung 5: Ablaufdiagramm: Aufruf einer Methode aus dem document
Namespace (speziell) – S.78
Sofern nicht anders angegeben, handelt es sich um selbst erstellte Grafiken.
88
9. Quellenverzeichnis
9.1 Buchquellen
[Edlich2011]
Stefan Edlich, Achim Friedland, Jens Hampe, Benjamin Brauer, Markus Brückner:
NoSQL: Einstieg in die Welt nichtrelationaler Web 2.0 Datenbanken,
2., aktualisierte und erweiterte Aufage, 1. September 2011,
Hanser, München
[Emerick2012]
Chas Emerick, Brian Carper, Christophe Grand:
Clojure Programming,
erste Aufage, 28. März 2012,
O'Reilly Media, Sebastopol
[Fielding2000]
Roy Thomas Fielding:
Architectural Styles and the Design of Network-based Software Architectures, Dissertation,
2000, University of California, Irvine
[Redmond2012]
Eric Redmond, Jim R. Wilson:
Seven Databases in Seven Weeks: A Guide to Modern Databases and the NoSQL Movement,
Juni 2012,
Pragmatic Programmers, LLC.
[Richardson2007]
Leonard Richardson, Sam Ruby:
Web Services mit REST,
erste Aufage, 2007,
O'Reilly Verlag, Köln
[Sadalage2013]
Pramodkumar J. Sadalage, Martin Fowler:
NoSQL Distilled: A Brief Guide to the Emerging World of Polyglot Persistence
2013,
Addison-Wesley, Upper Saddle River, New Jersey
89
[Tate2011]
Bruce A. Tate:
Sieben Wochen, sieben Sprachen: Verstehen Sie die modernen Sprachkonzepte,
erste Aufage, 2011,
O'Reilly Verlag, Köln
[Tilkov2009]
Stefan Tilkov:
REST und HTTP,
erste Aufage, 2009,
dpunkt.verlag, Heidelberg
9.2 Internetquellen
[wwwArangoAPIAQL]
ArangoDB: HTTP Interface for AQL Queries
http://www.arangodb.org/manuals/current/HttpQuery.html
(abgerufen am 21.01.2014)
[wwwArangoAPIColl]
ArangoDB: HTTP Interface for Collections
http://www.arangodb.org/manuals/current/HttpCollection.html
(abgerufen am 15.01.2014)
[wwwArangoAPIDB]
ArangoDB: HTTP Interface for Databases
http://www.arangodb.org/manuals/current/HttpDatabase.html
(abgerufen am 18.01.2014)
[wwwArangoAPIDoc]
ArangoDB: HTTP Interface for Documents
http://www.arangodb.org/manuals/current/RestDocument.html
(abgerufen am 04.01.2014)
[wwwArangoAPIEdge]
ArangoDB: HTTP Interface for Edges
http://www.arangodb.org/manuals/current/RestEdge.html
(abgerufen am 14.01.2014)
[wwwArangoAPIGraph]
ArangoDB: HTTP Interface for Graphs
http://www.arangodb.org/manuals/current/HttpGraph.html
90
(abgerufen am 14.01.2014)
[wwwArangoAPINaming]
Naming Conventions in ArangoDB
http://www.arangodb.org/manuals/current/NamingConventions.html
(abgerufen am 05.01.2014)
[wwwArangoAPITrav]
ArangoDB: HTTP Interface for Traversals
http://www.arangodb.org/manuals/current/HttpTraversals.html
(abgerufen am 16.01.2014)
[wwwArangoBlog1]
ArangoDB Blog: 7 reasons why ArangoDB is the world‘s best nosql database (or even better
than that ;-))
https://www.arangodb.org/2012/03/07/7-reasons-why-avocadodb-is-the-worlds-best-nosqldatabase-or-even-better-than-that
(abgerufen am 27.01.2014)
[wwwArangoBlog2]
ArangoDB Blog: ArangoDB’s design objectives
https://www.arangodb.org/2012/03/07/avocadodbs-design-objectives
(abgerufen am 27.01.2014)
[wwwArangoBlog3]
ArangoDB Blog: Infographic – comparing the disk space usage of MongoDB, CouchDB and
ArangoDB
https://www.arangodb.org/2012/07/11/infographic-comparing-space-usage-mongodb-couchdbarangodb
(abgerufen am 28.01.2014)
[wwwArangoBlog4]
ArangoDB Blog: Benchmarking ArangoDB's networking and HTTP-layer
https://www.arangodb.org/2012/07/02/benchmarking-arangodbs-networking-http-layer
(abgerufen am 29.01.2014)
[wwwArangoBlog5]
ArangoDB Blog: Gain factor of 5 using batch requests
https://www.arangodb.org/2012/10/04/gain-factor-of-5-using-batch-updates
(abgerufen am 29.01.2014)
91
[wwwArangoBlog6]
ArangoDB Blog: Bulk inserts in MongoDB, CouchDB, and ArangoDB
https://www.arangodb.org/2012/09/04/bulk-inserts-mongodb-couchdb-arangodb
(abgerufen am 29.01.2014)
[wwwArangoFAQ]
ArangoDB FAQ
http://www.arangodb.org/faq
(abgerufen am 26.01.2014)
[wwwArangoFoxx]
ArangoDB Foxx
http://www.arangodb.org/foxx
(abgerufen am 28.01.2014)
[wwwArangoFS]
First Steps with ArangoDB
http://www.arangodb.org/manuals/current/FirstStepsArangoDB.html
(abgerufen am 28.01.2014)
[wwwArangoManAuth]
ArangoDB's User Manual: Authentication and Authorisation
https://www.arangodb.org/manuals/current/DbaManualAuthentication.html
(abgerufen am 27.01.2014)
[wwwArangoManIndex]
ArangoDB's User Manual: Handling Indexes
https://www.arangodb.org/manuals/current/HandlingIndexes.html
(abgerufen am 27.01.2014)
[wwwArangoManRep]
ArangoDB's User Manual: Replication
http://www.arangodb.org/manuals/current/UserManualReplication.html
(abgerufen am 28.01.2014)
[wwwArangoTalk1]
Lucas Dohmen: ArangoDB – a different approach to NoSQL,
Vortrag bei der NoSQL matters Conference 2013 in Barcelona,
Vortrag: http://www.youtube.com/watch?v=eB-YHgMT2D0,
Slides: http://2013.nosql-matters.org/bcn/wp-content/uploads/2013/12/ArangoDB.pdf
(abgerufen am 25.01.2014)
92
[wwwArangoTalk2]
Martin Schönert: AvocadoDB109 explained,
Vortrag bei der NoSQL matters Conference,
http://vimeo.com/36411892
(abgerufen am 25.01.2014)
[wwwCarmine]
Carmine Github Repository und Readme
https://github.com/ptaoussanis/carmine
(abgerufen am 14.02.2014)
[wwwClj-Orient]
clj-orient Github Repository und Readme
http://www.github.com/eduardoejp/clj-orient
(abgerufen am 16.02.2014)
[wwwClojure]
Clojure.org Hauptseite
http://clojure.org
(abgerufen am 16.01.2014)
[wwwClojureRatio]
Clojure.org Rationale
http://clojure.org/rationale
(abgerufen am 07.01.2014)
[wwwClutch]
Clutch Github Repository und Readme
https://github.com/clojure-clutch/clutch
(abgerufen am 16.02.2014)
[wwwClutchGroup]
Google Groups: Clojure Clutch > Working on a CouchDB type
https://groups.google.com/forum/#!topic/clojure-clutch/RSBxbrN6kMw
(abgerufen am 17.02.2014)
[wwwElasticGlossary]
Elasticsearch reference: glossary of terms
http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/glossary.html
(abgerufen am 18.11.2013 )
109„AvocadoDB“ war der frühere Name von ArangoDB
93
[wwwElastischDocs1]
Elastisch Doc Guides: Mappings and Indexing
http://clojureelasticsearch.info/articles/indexing.html
(abgerufen am 18.11.2013 )
[wwwElastischDocs2]
Elastisch Doc Guides: Getting Started
http://clojureelasticsearch.info/articles/getting_started.html
(abgerufen am 18.11.2013 )
[wwwHTTP1999]
Fielding, et al.: Hypertext Transfer Protocol – HTTP/1.1 (RFC 2616), Juni 1999,
http://tools.ietf.org/rfc/rfc2616.txt
(abgerufen am 03.12.2013 )
[wwwHTTP2005]
M. Nottingham, J. Mogul: HTTP Header Field Registrations (RFC 4229), Dezember 2005,
http://tools.ietf.org/rfc/rfc4229.txt
(abgerufen am 03.12.2013 )
[wwwHTTP2010]
L. Dusseault, Linden Lab, J. Snell: PATCH Method for HTTP (RFC 5789), März 2010,
http://tools.ietf.org/html/rfc5789
(abgerufen am 07.01.2014 )
[wwwMongerAPI]
Monger API Documentation
http://reference.clojuremongodb.info/
(abgerufen am 18.11.2013 )
[wwwMongerDocs1]
Monger Doc Guides: Getting Started
http://clojuremongodb.info/articles/getting_started.html
(abgerufen am 18.11.2013 )
[wwwMongerDocs2]
Monger Doc Guides: Querying: finders and query DSL
http://clojuremongodb.info/articles/querying.html
(abgerufen am 18.11.2013 )
94
[wwwMongerDocs3]
Monger Doc Guides: Indexing and other collection operations
http://clojuremongodb.info/articles/collections.html
(abgerufen am 18.11.2013 )
[wwwMongerDocs4]
Monger Doc Guides: Querying
http://clojuremongodb.info/articles/querying.html
(abgerufen am 16.02.2013 )
[wwwMongoDBCRUD]
MongoDB CRUD Introduction
http://docs.mongodb.org/manual/core/crud-introduction/
(abgerufen am 18.11.2013 )
[wwwNeoconsGuide]
Neocons Guides: Getting started
http://clojureneo4j.info/articles/getting_started.html
(abgerufen am 13.03.2014)
[wwwOrientDB1]
Tutorial: Document and graph model
https://github.com/orientechnologies/orientdb/wiki/Tutorial%3A-Document-and-graph-model
(abgerufen am 16.02.2014)
[wwwTravisGuide]
Travis CI Guides: Getting started
http://docs.travis-ci.com/user/getting-started/
(abgerufen am 09.03.2014)
[wwwVersioning]
Semantic Versioning 2.0.0
http://semver.org/
(abgerufen am 06.03.2014)
[wwwW3CArchitecture]
W3C Recommendation: Architecture of the World Wide Web, Volume One,
W3C Technical Architecture Group, 15.12.2004,
http://www.w3.org/TR/2004/REC-webarch-20041215
(abgerufen am 07.12.2013)
95
10. Anhang
10.1 Ausgaben des Anwendungsbeispiels aus Kapitel 6
Hier sollen noch einmal zur Verdeutlichung die Ausgaben des Anwendungsbeispiels aus
Kapitel 6 gegeben werden. Die Ausgaben entsprechen exakt den Ausgaben der MainMethode, die zu finden ist unter110:
https://github.com/edlich/clarango/blob/1196614025ef32be4ace5f2fc91f4cf968f8c825/src
/clarango/main.clj
Zusätzlich wurde noch die Ausgabe der Ressourcen-URIs im Namespace http-utility
aktiviert111.
---- first create a database and a collection and make some document CRUD ----
connect to defaults: localhost and port 8529
create Database 'test-DB'
POST
connection address:
http://localhost:8529/_db/_system/_api/database
{"result" true, "error" false, "code" 200}
create Collection 'test-collection' in DB 'test-DB'
POST
connection address:
http://localhost:8529/_db/test-DB/_api/collection
{"isVolatile" false,
"error" false,
"name" "test-collection",
"code" 200,
"waitForSync" false,
"status" 3,
"isSystem" false,
"type" 2,
"id" "1108492711"}
document CRUD
POST
connection address:
http://localhost:8529/_db/test-DB/_api/document/?
collection=test-collection
{"error" false,
"_id" "test-collection/test-doc",
"_rev" "1109344679",
110Zu beachten ist hier, dass bei wiederholten Aktionen wie zum Beispiel dem Erstellen mehrerer
Collections oder Dokumente hintereinander teilweise auf Ausgaben verzichtet wurde, um
Wiederholungen zu vermeiden.
111Die Ausgabe wird mittels eines Boolean-Werts hier aktiviert:
https://github.com/edlich/clarango/blob/master/src/clarango/utilities/http_utility.clj#L22
96
"_key" "test-doc"}
PATCH
connection address:
http://localhost:8529/_db/test-DB/_api/document/test-
collection/test-doc
{"error" false,
"_id" "test-collection/test-doc",
"_rev" "1109737895",
"_key" "test-doc"}
GET
connection address:
http://localhost:8529/_db/test-DB/_api/document/test-
collection/test-doc
{"name" "some test document",
"additional" "some additional info",
"_id" "test-collection/test-doc",
"_rev" "1109737895",
"_key" "test-doc"}
PUT
connection address:
http://localhost:8529/_db/test-DB/_api/simple/replace-by-
example
{"replaced" 1, "error" false, "code" 200}
---- now make use of the clojure idiomatic methods available in the namespace
collection-ops to add and delete more content in the collection ----
set default DB; this database will be used in the following methods without
explicitely having to pass it
collection ops : assoc, dissoc, conj
POST
connection address:
http://localhost:8529/_db/test-DB/_api/document/?
collection=test-collection
{"error" false,
"_id" "test-collection/new-document-1",
"_rev" "1110589863",
"_key" "new-document-1"}
POST
connection address:
http://localhost:8529/_db/test-DB/_api/document/?
collection=test-collection
{"error" false,
"_id" "test-collection/1110786471",
"_rev" "1110786471",
"_key" "1110786471"}
GET
connection address:
http://localhost:8529/_db/test-DB/_api/document/test-
collection/new-document-1
{"key-type" "given key",
"description"
"some test document to test the clojure idiomatic collection methods",
"_id" "test-collection/new-document-1",
"_rev" "1110589863",
"_key" "new-document-1"}
DELETE
connection address:
http://localhost:8529/_db/test-DB/_api/document/test-
collection/new-document-1
97
{"error" false,
"_id" "test-collection/new-document-1",
"_rev" "1110589863",
"_key" "new-document-1"}
---- modify the collection ----
get information about the collection and a list of all documents inside it
GET
connection address:
http://localhost:8529/_db/test-DB/_api/collection/test-
collection
{"id" "1108492711",
"name" "test-collection",
"status" 3,
"type" 2,
"error" false,
"code" 200}
GET
connection address:
http://localhost:8529/_db/test-DB/_api/document/?
collection=test-collection
["/_api/document/test-collection/test-doc"
"/_api/document/test-collection/1110786471"]
rename the collection and modify it's properties
PUT
connection address:
http://localhost:8529/_db/test-DB/_api/collection/test-
collection/rename
{"id" "1108492711",
"name" "new-name-test-collection",
"status" 3,
"type" 2,
"error" false,
"code" 200}
PUT
connection address:
http://localhost:8529/_db/test-DB/_api/collection/new-name-
test-collection/properties
{"isVolatile" false,
"error" false,
"name" "new-name-test-collection",
"code" 200,
"waitForSync" false,
"status" 3,
"doCompact" true,
"journalSize" 33554432,
"isSystem" false,
"type" 2,
"id" "1108492711",
"keyOptions" {"type" "traditional", "allowUserKeys" true}}
GET
connection address:
http://localhost:8529/_db/test-DB/_api/collection/new-name-
test-collection/figures
98
{"isVolatile" false,
"error" false,
"name" "new-name-test-collection",
"code" 200,
"count" 2,
"figures"
{"alive" {"count" 2, "size" 302},
"dead" {"count" 3, "size" 451, "deletion" 1},
"datafiles" {"count" 0, "fileSize" 0},
"journals" {"count" 1, "fileSize" 33554432},
"compactors" {"count" 0, "fileSize" 0},
"shapefiles" {"count" 1, "fileSize" 2097152},
"shapes" {"count" 9},
"attributes" {"count" 4}},
"waitForSync" false,
"status" 3,
"doCompact" true,
"journalSize" 33554432,
"isSystem" false,
"type" 2,
"id" "1108492711",
"keyOptions" {"type" "traditional", "allowUserKeys" true}}
unload and delete collection
PUT
connection address:
http://localhost:8529/_db/test-DB/_api/collection/new-name-
test-collection/unload
{"id" "1108492711",
"name" "new-name-test-collection",
"status" 4,
"type" 2,
"error" false,
"code" 200}
DELETE
connection address:
http://localhost:8529/_db/test-DB/_api/collection/new-
name-test-collection
{"id" "1108492711", "error" false, "code" 200}
---- now create a graph, query it's vertices and perform some graph operations
including a traversal ----
first create another Database 'GraphTestDB'
POST
connection address:
http://localhost:8529/_db/_system/_api/database
now list all available databases
GET
connection address:
http://localhost:8529/_db/_system/_api/database
["GraphTestDB" "_system" "test-DB"]
perform next operations in the context of 'GraphTestDB'
99
create vertex and edge collections 'people' and 'connections'
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/collection
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/collection
now list all available collections, excluding the system collections
GET
connection address:
http://localhost:8529/_db/GraphTestDB/_api/collection
[{"id" "1124417959", "name" "connections", "status" 3, "type" 3}
{"id" "1123762599", "name" "people", "status" 3, "type" 2}]
create graph 'test-graph'
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph
{"_id" "_graphs/test-graph",
"_rev" "1125859751",
"_key" "test-graph",
"vertices" "people",
"edges" "connections"}
now get all available graphs
GET
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph
[{"_id" "_graphs/test-graph",
"_rev" "1125859751",
"_key" "test-graph",
"vertices" "people",
"edges" "connections"}]
perform next operations in the context of the graph 'test-graph'
create vertices 'Peter', 'Bob', 'Clara', 'Jessica', 'Alice' with :ages
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/vertex
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/vertex
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/vertex
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/vertex
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/vertex
---- perform query: find all people who are older than 24
first validate the query, then explain (how the query would be executed on the
server), then actually execute it ---POST
connection address:
http://localhost:8529/_api/query
{"bindVars" [], "collections" ["people"], "error" false, "code" 200}
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/explain
100
[{"id" 1,
"loopLevel" 1,
"type" "for",
"resultVariable" "p",
"expression"
{"type" "collection",
"value" "people",
"extra" {"accessType" "all"}}}
{"id" 2,
"loopLevel" 1,
"type" "filter",
"expression" {"type" "expression", "value" "p.age > 24"}}
{"id" 3,
"loopLevel" 1,
"type" "return",
"expression" {"type" "reference", "value" "p"}}]
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/cursor
{"result"
[{"_id" "people/peter",
"_rev" "1126580647",
"_key" "peter",
"age" 25,
"name" "Peter"}
{"_id" "people/bob",
"_rev" "1126973863",
"_key" "bob",
"age" 28,
"name" "Bob"}
{"_id" "people/clara",
"_rev" "1127301543",
"_key" "clara",
"age" 29,
"name" "Clara"}],
"hasMore" false,
"error" false,
"code" 201}
create edges with labels 'friend', 'boyfriend', 'girlfriend'; save one key to use
this edge later
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/edge
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/edge
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/edge
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/edge
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/edge
101
get vertices that have connections going from the vertex 'peter'
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/vertices/peter
{"result"
[{"_id" "people/alice",
"_rev" "1128022439",
"_key" "alice",
"age" 20,
"name" "Alice"}],
"hasMore" false,
"count" 1,
"error" false,
"code" 201}
update one edge
PATCH
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/edge/1128874407
{"_id" "connections/1128874407",
"_rev" "1132413351",
"_key" "1128874407",
"_from" "people/peter",
"_to" "people/alice",
"$label" "friend",
"description" "Peter and Alice have been friends for over 6 years"}
get all edges that are outgoing from the vertex 'peter'
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/edges/peter
{"result"
[{"_id" "connections/1128874407",
"_rev" "1132413351",
"_key" "1128874407",
"_from" "people/peter",
"_to" "people/alice",
"$label" "friend",
"description"
"Peter and Alice have been friends for over 6 years"}],
"hasMore" false,
"count" 1,
"error" false,
"code" 201}
execute a graph traversal
POST
connection address:
http://localhost:8529/_db/GraphTestDB/_api/traversal
{"vertices"
[{"_id" "people/peter",
"_rev" "1126580647",
"_key" "peter",
"age" 25,
"name" "Peter"}],
102
"paths"
[{"edges" [],
"vertices"
[{"_id" "people/peter",
"_rev" "1126580647",
"_key" "peter",
"age" 25,
"name" "Peter"}]}]}
delete one edge
DELETE
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/edge/1128874407
{"deleted" true, "error" false, "code" 202}
delete one vertex
DELETE
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph/vertex/peter
{"deleted" true, "error" false, "code" 202}
delete the graph
DELETE
connection address:
http://localhost:8529/_db/GraphTestDB/_api/graph/test-
graph
{"deleted" true, "error" false, "code" 200}
delete databases
DELETE
connection address:
http://localhost:8529/_db/_system/_api/database/GraphTestDB
{"result" true, "error" false, "code" 200}
DELETE
connection address:
http://localhost:8529/_db/_system/_api/database/test-DB
103
10.2 Vollständige Clarango API Dokumentation
10.2.1 Core API
connection-set?
(connection-set?)
Returns true if a connection is set.
get-connection
(get-connection)
Returns the db server connection map to other namespaces.
set-connection!
(set-connection!)
(set-connection! connection-map)
Connects permanently to an ArangoDB host by setting the connection map as a
global variable.
If called without arguments set default connection at localhost:8529 with
_system db.
set-connection-url!
(set-connection-url! connection-url)
Sets the server url.
set-default-collection!
(set-default-collection! collection-name)
Sets a default collection.
set-default-db!
(set-default-db! database-name)
Sets a default database.
set-default-graph!
(set-default-graph! graph-name)
Sets a default graph.
with-collection
(with-collection collection-name & body)
Dynamically rebinds the default collection value.
Takes a body of code which will be executed in the context of this collection.
104
with-connection
(with-connection connection & body)
Dynamically rebinds the global connection map.
Takes a body of code which will be executed in the context of this connection.
with-db
(with-db database-name & body)
Dynamically rebinds the default database value.
Takes a body of code which will be executed in the context of this database.
with-graph
(with-graph graph-name & body)
Dynamically rebinds the default graph value.
Takes a body of code which will be executed in the context of this graph.
10.2.2 Document API
create
(create document & args)
Creates a document.
First argument: A map that represents the document.
If you want to specify a key by yourself, add it as the :_key parameter to the
document map.
If you would like the key to be created automatically, just leave this
parameter out.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
Also optional as argument is another map containing further options:
{'createCollection' true/false, 'waitForSync' true/false} (replace the single
quotes with double quotes)
- createCollection meaning if the collection should be created if it does not
exist yet;
- waitForSync meaning if the server response should wait until the document is
saved to disk;
The option map might be passed in an arbitrary position after the first
argument.
create-multi
(create-multi documents & args)
Creates multiple documents at a time.
First argument is a vector of documents.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
105
delete-by-example
(delete-by-example example & args)
Deletes a document or a number of documents out of a collection by giving an
example to match.
Takes the example as a map as first argument.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
Also optional as argument is another map containing further options:
{'waitForSync' true/false, 'limit' limit} (replace the single quotes with
double quotes)
- waitForSync meaning if the server response should wait until the document is
saved to disk
- limit meaning the maximum amount of documents that will be deleted
The option map might be passed in an arbitrary position after the first two
arguments.
delete-by-key
(delete-by-key & args)
Deletes a document by its id.
Takes the document key as first argument.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
Also optional as argument is another map containing further options:
{'waitForSync' true/false, 'rev' revision_id, 'policy' 'error/last'} (replace
the single quotes with double quotes)
- waitForSync meaning if the server response should wait until the document is
saved to disk;
- rev is the document revision
- policy meanins the desired behaviour in case the given revision number does
not match the latest document revision
-> 'error' meaning that an error is thrown if the given revision_id does not
match the revision_id in the document
-> 'last' meaning the document is still deleted even if the given
revision_id does not match the revision_id in the document
The option map might be passed in an arbitrary position after the first
argument.
get-by-example
(get-by-example example & args)
Gets a document or a number of documents out of a collection by giving an
example to match.
Takes the example as a map as first argument.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
106
Also optional as argument is another map containing further options:
{'skip' skip, 'limit' limit} (replace the single quotes with double quotes)
- skip meaning the (number of?) documents to skip in the result
- limit meaning the maximum amount of documents to return
The option map might be passed in an arbitrary position after the first two
arguments.
get-by-key
(get-by-key & args)
Gets a document by its key.
Takes the document key as first argument.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
Also optional as argument is another map containing further options:
{'rev' revision_id} (replace the single quotes with double quotes)
- rev is the document revision; if the current document revision_id does not
match the given one, an error is thrown
The option map might be passed in an arbitrary position after the first two
arguments.
get-first-by-example
(get-first-by-example example & args)
Gets the first document out of a collection that matches an example.
Takes the example as a map as first argument.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
get-info
(get-info & args)
Gets information about a document by its key.
Takes the document key as first argument.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
Also optional as argument is another map containing further options:
{'rev' revision_id, 'policy' 'error/last'} (replace the single quotes with
double quotes)
- rev is the document revision
- policy meaning the desired behaviour in case the given revision number does
not match the latest document revision
-> 'error' meaning that an error is thrown if the given revision_id does not
match the revision_id in the document
-> 'last' meaning the document is still returned even if the given
revision_id does not match the revision_id in the document
107
The option map might be passed in an arbitrary position after the first two
arguments.
replace-by-example
(replace-by-example new-document example & args)
Replaces a document or a number of documents out of a collection by giving an
example to match.
First argument: A map representing the new document.
Second argument: The example map.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
Also optional as argument is another map containing further options:
{'waitForSync' true/false, 'limit' limit} (replace the single quotes with
double quotes)
- waitForSync meaning if the server response should wait until the document is
saved to disk
- limit meaning the maximum amount of documents that will be replaced
The option map might be passed in an arbitrary position after the first two
arguments.
replace-by-key
(replace-by-key new-document & args)
Replaces a document with a map representing the new document.
First argument: A map representing the new document.
Second argument: The document key.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
Also optional as argument is another map containing further options:
{'waitForSync' true/false, 'rev' revision_id, 'policy' 'error/last'} (replace
the single quotes with double quotes)
- waitForSync meaning if the server response should wait until the document is
saved to disk
- rev is the document revision
- policy meanins the desired behaviour in case the given revision number does
not match the latest document revision
-> 'error' meaning that an error is thrown if the given revision_id does not
match the revision_id in the document
-> 'last' meaning the document is still replaced even if the given
revision_id does not match the revision_id in the document
The option map might be passed in an arbitrary position after the first two
arguments.
update-by-example
(update-by-example document-properties example & args)
Updates a document or a number of documents out of a collection by giving an
example to match.
108
First argument: A map containing the new key/value pairs.
Second argument: The example map.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
Also optional as argument is another map containing further options:
{'waitForSync' true/false, 'limit' limit, 'keepNull' true/false} (replace the
single quotes with double quotes)
- waitForSync meaning if the server response should wait until the document is
saved to disk
- limit meaning the maximum amount of documents that will be updated
- keepNull meaning if the key/value pair should be deleted in the document
The option map might be passed in an arbitrary position after the first two
arguments.
update-by-key
(update-by-key document-properties & args)
Updates a document with a number of key value pairs. Inserts them into the
existing document.
First argument: A map containing the new key/value pairs.
Second argument: The document key.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
Also optional as argument is another map containing further options:
{'waitForSync' true/false, 'keepNull' true/false, 'rev' revision_id, 'policy'
'error/last'} (replace the single quotes with double quotes)
- waitForSync meaning if the server response should wait until the document is
saved to disk;
- keepNull meaning if the key/value pair should be deleted in the document
if the argument map contains it with a null as value;
- rev is the document revision
- policy meanins the desired behaviour in case the given revision number does
not match the latest document revision
-> 'error' meaning that an error is thrown if the given revision_id does not
match the revision_id in the document
-> 'last' meaning the document is still updated even if the given
revision_id does not match the revision_id in the document
The option map might be passed in an arbitrary position after the first two
arguments.
10.2.3 Collection API
create
(create collection-name & args)
Creates a new collection.
Takes the name of the new collection as first argument.
Takes optionally a database name and a map containing options as further
arguments.
109
These arguments may be passed in arbituary order.
If the database name is omitted by the user, the default db will be used.
Possible options in the options map are:
{'waitForSync' true/false, 'doCompact' true/false, 'journalSize' journal_size,
'isSystem' true/false,
'isVolatile' true/false, 'type' 2/3, 'keyOptions' [...see below...]} (replace
the single quotes with double quotes)
- waitForSync meaning if the server response should wait until the document is
saved to disk
- doCompact meaning whether of not the collection will be compacted (default
is true)
- journalSize is the maximum size of a journal or datafile; must at least be 1
MB; this can limit also the maximum size of a single object
- isSystem meaning if a system collection should be created (default is false)
- isVolatile meaning if the collection should only be kept in-memory and not
made persistent
--> keeping the collection in-memory only will make it slightly faster, but
restarting the server will cause full loss
- type is the type of the collection: 2 = document collection (default), 3 =
edges collection
- keyOptions: a JSON array containing the following options for key
generation:
- type is the type of the key generator (currently available are
'traditional' and 'autoincrement')
- allowUserKeys true/false means if true the user can supply his own keys
on creating a document;
when set to false only the key generator will be responsible for
creating the keys;
- increment is the increment value for the autoincrement key generator
(optional)
- offset is the initial offset value for the autoincrement key generator
(optional)
delete
(delete collection-name & args)
Deletes a collection.
Takes the name of the collection to be deleted as first argument.
Optionally you can pass a database name as second argument.
get-all-documents
(get-all-documents & args)
Returns a list with the URIs of all documents in the collection.
Can be called without arguments. In that case the default collection from the
default database will be used.
Optionally you can pass a collection name as first and a database name as
second argument.
110
get-extended-info
(get-extended-info & args)
Returns extended information about a collection. Forces a load of the
collection.
Can be called without arguments. In that case the default collection from the
default database will be used.
Optionally you can pass a collection name as first and a database name as
second argument.
get-extended-info-count
(get-extended-info-count & args)
Returns extended information about a collection including the number of
documents in the collection.
Forces a load of the collection.
Can be called without arguments. In that case the default collection from the
default database will be used.
Optionally you can pass a collection name as first and a database name as
second argument.
get-extended-info-figures
(get-extended-info-figures & args)
Returns extended information about a collection including detailed information
about the documents in the collection.
Forces a load of the collection.
Can be called without arguments. In that case the default collection from the
default database will be used.
Optionally you can pass a collection name as first and a database name as
second argument.
get-info
(get-info & args)
Returns information about a collection.
Can be called without arguments. In that case the default collection from the
default database will be used.
Optionally you can pass a collection name as first and a database name as
second argument.
load
(load & args)
Loads a collection into the memory. Returns the collection on success. (?)
Can be called without arguments. In that case the default collection from the
default database will be loaded.
Optionally you can pass a collection name, a database name and a map with
options as arguments.
111
Possible options in the options map are:
{'count' true/false}
- count meaning if the return value should contain the number of documents in
the collection
-> the default is true, but setting it to false may speed up the request
The option map might be passed in an arbitrary position between the other
arguments.
modify-properties
(modify-properties properties & args)
Modifies
the properties of a collection.
As first argument expects a map with options.
Takes optional a collection name and a db name as further arguments.
If omitted by user, the default db and collection will be used.
Possible options in the options map are:
{'waitForSync' true/false 'journalSize' size}
- waitForSync meaning if the server response should wait until the document is
saved to disk
- journalSize is the size (in bytes) for new journal files that are created
for the collection
rename
(rename new-name collection-name & args)
Renames a collection. On success return a map with properties.
First argument: The new collection name
Second argument: The old collection name
Takes optional a db name as further argument.
If omitted by user, the default db will be used.
rotate
(rotate & args)
Rotates the journal of a collection.
This means the current journal of the collection will be closed and all
data made read-only in order to compact it. New documents will be stored in a
new journal.
Can be called without arguments. In that case the default collection from the
default database will be rotated.
Optionally you can pass a collection name as first and a database name as
second argument.
truncate
(truncate & args)
Removes all documents from a collection, but leaves the indexes intact.
112
Can be called without arguments. In that case the default collection from the
default database will be truncated.
Optionally you can pass a collection name as first and a database name as
second argument.
unload
(unload & args)
Removes a collection from the memory. On success a map containing collection
properties is returned.
Can be called without arguments. In that case the default collection from the
default database will be truncated.
Optionally you can pass a collection name as first and a database name as
second argument.
10.2.4 Datenbank API
create
(create database-name users)
Creates a new database.
First argument: the name of the new database
Second argument: a vector specifying users to initially create for the new
database;
can be empty; in this case a default user 'root' with an empty password will
be created;
if not empty, it must contain user objects which may contain the following
options:
- username: the user name as a string
- passwd: the user password as a string; if omitted, an empty password
will be set
- active: boolean flag indicating whether the user accout should be
actived or not; default is true;
- extra: an optional map of user information that will be saved, but not
interpreted by ArangoDB
delete
(delete database-name)
Deletes a database.
Expects the database name of the database to be dropped as argument.
get-all-graphs
(get-all-graphs & args)
Gets a list of all existing graphs within the database.
Can be called without arguments. In that case the default database will be
used.
Optionally you can pass a database name as argument.
113
get-collection-info-list
(get-collection-info-list & args)
Returns information about all collections in a database as a list.
Can be called without arguments. In that case the default database will be
used.
Optionally you can pass a database and a map with options as arguments.
Possible options in the options map are:
{'excludeSystem' true/false}
- excludeSystem meaning whether or not the system collections should be
excluded from the result.
get-info-current
(get-info-current)
Returns information about the current database.
get-info-list
(get-info-list)
Returns a list of all existing databases.
get-info-user
(get-info-user)
Returns a list of all databases the current user can access.
Note: this might not work under Windows.
10.2.5 Query API
delete-cursor
(delete-cursor cursor-id & args)
This method deletes a cursor on the server.
If you don't intend to make further use of a cursor, you should always delete
it to free resources on the server.
If all available documents of the query were already retrieved by the client,
the cursor was already destroyed automatically.
Takes as first argument the id of the cursor to be deleted.
The id was returned by the execute and the get-more-results method.
Optionally you can pass a database name. If omitted, the default db will be
used.
execute
(execute query-string & args)
Executes a query.
First argument must be the query string to be executed.
114
If the query references any bind variables, you must additionally pass these
in a map as the second argument like this:
{ 'id' 3 } (replace the single quotes with double quotes)
If you don't use any variables, you can leave this out.
Optionally you can pass a database name as third (or second) argument. If
omitted, the default db will be used.
The actual result of the query will be contained in the attribute 'result' as
a vector.
For more options see the method execute-count.
execute-count
(execute-count query-string batch-size count & args)
Executes a query. Takes also the options 'batch-size' and 'count'.
First argument must be the query string to be executed.
Second argument must be the batch size. This is the amount of documents that
will be returned in the first answer of the
server. In case there are more documents, in the server answer there will be
the attribute 'hasMore' set to true.
In this case you can then use the returned cursor 'id' with the method getmore-results to get the remaining results.
Third argument is 'count', a boolean flag indicating whether or not the number
of documents that were found for the query
should be included in the result of the query as 'count' attribute. This is
turned off by default because it might have
an influence on the performance of the query.
If the query references any bind variables, you must additionally pass these
in a map as the fourth argument like this:
{ 'id' 3 } (replace the single quotes with double quotes)
If you don't use any variables, you can leave this out.
Optionally you can pass a database name as fifth or fourth argument. If
omitted, the default db will be used.
The actual result of the query will be contained in the attribute 'result' as
a vector.
explain
(explain query-string & args)
Explains how a query would be executed on the server. Returns an execution
plan for the query.
First argument must be the query string to be evaluated.
If the query references any bind variables, you must pass these in a map as
second argument like this:
{ 'id' 3 } (replace the single quotes with double quotes)
If you don't use any variables, you can leave the second argument out.
115
Optionally you can pass a database name as third or second argument. If
omitted, the default db will be used.
get-more-results
(get-more-results cursor-id & args)
This method gets the remaining results of a query. More results to a query are
available if the return value of the
execute method contained an attribute 'hasMore' set to true.
If after the execution of this method there are still more results to the
query, the return value of this method will
also contain an attribute 'hasMore' that is set to true.
Takes as first argument the id of the cursor that was returned by the execute
method.
Optionally you can pass a database name. If omitted, the default db will be
used.
validate
(validate query-string)
Validates a query without executing it.
As a return value you get a map containing the names of the collections and
the vars used in the query.
If the query is not valid also an error will be thrown including an error
message with the problem found in the query.
Takes as only argument the query string to be evaluated.
10.2.6 Graph API
create
(create graph-name vertices-collection edges-collection & args)
Creates a new graph.
First argument: The name of the graph to be created.
Second argument: The name of the collection containing the vertices.
Third argument: The name of the collection containing the edges.
The ladder two collections must already exist.
Optionally you can pass a database name as fourth argument. If omitted, the
default db will be used.
Also optional as argument is another map containing further options:
{'waitForSync' true/false} (replace the single quotes with double quotes)
- waitForSync meaning if the server response should wait until the graph has
been to disk;
116
create-edge
(create-edge edge edge-name vertex-from-name vertex-to-name & args)
Creates a new edge.
First argument: A map that represents the edge.
If you optionally want to specify a label for the edge, you can add it as
the :$label parameter to the edge map.
Second argument: The name of the edge to be created.
Third argument: The name of the from vertex.
Fourth argument: The name of the to vertex.
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
Also optional as argument is another map containing further options:
{'waitForSync' true/false} (replace the single quotes with double quotes)
- waitForSync meaning if the server response should wait until the edge is
saved to disk;
The option map might be passed in an arbitrary position after the first four
arguments.
create-vertex
(create-vertex vertex & args)
Creates a vertex.
First argument: A map that represents the vertex.
If you want to specify a key by yourself, add it as the :_key parameter to the
vertex map.
If you would like the key to be created automatically, just leave this
parameter out.
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
Also optional as argument is another map containing further options:
{'waitForSync' true/false} (replace the single quotes with double quotes)
- waitForSync meaning if the server response should wait until the vertex is
saved to disk;
The option map might be passed in an arbitrary position after the first
argument.
delete
(delete graph-name & args)
Deletes a graph.
Also deletes it's vertex and the edges collection.
Takes the name of the graph as first argument.
Optionally you can pass a database name as second argument. If omitted, the
default db will be used.
117
delete-edge
(delete-edge key & args)
Deletes an edge.
Takes the edge key as first argument.
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
Also optional as argument is another map containing further options:
{'rev' revision_id, 'waitForSync' true/false} (replace the single quotes with
double quotes)
- rev is the document revision; if the current document revision_id does not
match the given one, an error is thrown;
- waitForSync meaning if the server response should wait until the action was
saved to disk;
The option map might be passed in an arbitrary position after the first
argument.
delete-vertex
(delete-vertex key & args)
Deletes a vertex.
Takes the vertex key as first argument.
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
Also optional as argument is another map containing further options:
{'rev' revision_id, 'waitForSync' true/false} (replace the single quotes with
double quotes)
- rev is the document revision; if the current document revision_id does not
match the given one, an error is thrown;
- waitForSync meaning if the server response should wait until the action was
saved to disk;
The option map might be passed in an arbitrary position after the first
argument.
execute-traversal
(execute-traversal start-vertex vertex-collection edges-collection direction &
args)
Sends a traversal to the server to execute it.
First argument: The key of the start vertex.
Second argument: The name of the collection that contains the vertices.
Third argument: The name of the collection that contains the edges.
Fourth argument: The direction of the traversal. Must be either 'outbound',
'inbound' or 'any'.
Can be nil if the 'expander' attribute is set in the additional options.
Takes optionally a database name as further argument.
If omitted by user, the default database will be used.
118
Also optional as argument is another map containing further options for the
traversal:
{'filter' {...}, 'expander' code}
- see
http://www.arangodb.org/manuals/current/HttpTraversals.html#HttpTraversalsPost
The option map might be passed in an arbitrary position after the first four
arguments.
get-edge
(get-edge key & args)
Gets an edge.
Takes the edge key as first argument.
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
Also optional as argument is another map containing further options:
{'rev' revision_id} (replace the single quotes with double quotes)
- rev is the document revision; if the current document revision_id does not
match the given one, an error is thrown;
The option map might be passed in an arbitrary position after the first
argument.
get-edges
(get-edges key batch-size limit count filter & args)
Gets several edges.
Depending on batch size returns a cursor.
First argument: The key of the start edge.
Second argument: The batch size of the returned cursor.
Third argument: The result size.
Fourth argument: An optional filter for the results. If you don't want to use
it, just pass nil here.
For details on the filter see
http://www.arangodb.org/manuals/current/HttpGraph.html#A_JSF_POST_graph_edges
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
get-info
(get-info graph-name & args)
Gets info about a graph.
Returns a map containing information about the graph.
Takes the name of the graph as first argument.
Optionally you can pass a database name as second argument. If omitted, the
default db will be used.
119
get-vertex
(get-vertex key & args)
Gets a vertex.
Takes the vertex key as first argument.
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
Also optional as argument is another map containing further options:
{'rev' revision_id} (replace the single quotes with double quotes)
- rev is the document revision; if the current document revision_id does not
match the given one, an error is thrown;
The option map might be passed in an arbitrary position after the first
argument.
get-vertices
(get-vertices key batch-size limit count filter & args)
Gets several vertices.
Depending on batch size returns a cursor.
First argument: The key of the start vertex.
Second argument: The batch size of the returned cursor.
Third argument: The result size.
Fourth argument: An optional filter for the results. If you don't want to use
it, just pass nil here.
For details on the filter see
http://www.arangodb.org/manuals/current/HttpGraph.html#A_JSF_POST_graph_vertic
es
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
replace-edge
(replace-edge edge-properties key & args)
Replaces an edge.
First argument: A map containing the new edge.
Second argument: The edge key.
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
Also optional as argument is another map containing further options:
{'rev' revision_id, 'waitForSync' true/false} (replace the single quotes with
double quotes)
- rev is the document revision; if the current document revision_id does not
match the given one, an error is thrown;
- waitForSync meaning if the server response should wait until the action was
saved to disk;
The option map might be passed in an arbitrary position after the first
argument.
120
replace-vertex
(replace-vertex vertex-properties key & args)
Replaces a vertex.
First argument: A map containing the new vertex.
Second argument: The vertex key.
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
Also optional as argument is another map containing further options:
{'rev' revision_id, 'waitForSync' true/false} (replace the single quotes with
double quotes)
- rev is the document revision; if the current document revision_id does not
match the given one, an error is thrown;
- waitForSync meaning if the server response should wait until the action was
saved to disk;
The option map might be passed in an arbitrary position after the first
argument.
update-edge
(update-edge edge-properties key & args)
Updates an edge.
First argument: A map containing the new edge properties.
Second argument: The edge key.
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
Also optional as argument is another map containing further options:
{'rev' revision_id, 'waitForSync' true/false, 'keepNull' true/false} (replace
the single quotes with double quotes)
- rev is the document revision; if the current document revision_id does not
match the given one, an error is thrown;
- waitForSync meaning if the server response should wait until the action was
saved to disk;
- keepNull meaning if the key/value pair should be deleted in the edge
if the argument map contains it with a null (nil) as value;
The option map might be passed in an arbitrary position after the first
argument.
update-vertex
(update-vertex vertex-properties key & args)
Updates a vertex.
First argument: A map containing the new vertex properties.
Second argument: The vertex key.
Takes optional a graph name and a db name as further arguments.
If omitted by user, the default graph and collection will be used.
121
Also optional as argument is another map containing further options:
{'rev' revision_id, 'waitForSync' true/false, 'keepNull' true/false} (replace
the single quotes with double quotes)
- rev is the document revision; if the current document revision_id does not
match the given one, an error is thrown;
- waitForSync meaning if the server response should wait until the action was
saved to disk;
- keepNull meaning if the key/value pair should be deleted in the vertex
if the argument map contains it with a null (nil) as value;
The option map might be passed in an arbitrary position after the first
argument.
10.2.7 collection-ops API
cla-assoc!
(cla-assoc! collection-name key val)
Adds one document (val) to a collection (specified by collection-name) with a
given key.
Always uses the default database set in clarango.core.
Modeled on core/assoc (http://clojuredocs.org/clojure_core/clojure.core/assoc)
Does the same, just on an ArangoDB collection. The difference is that you can
currently only pass one key and one document
to add to the collection, not several like in clojure.core/dissoc
cla-conj!
(cla-conj! collection-name x)
Adds one document (x) to a collection (specified by collection-name). The key
for the document is generated by ArangoDB.
Always uses the default database set in clarango.core.
Modeled on core/conj (http://clojuredocs.org/clojure_core/clojure.core/conj)
Does the same, just on an ArangoDB collection. The difference is that you can
currently only pass one element to add
to the collection, not several like in clojure.core/conj
cla-dissoc!
(cla-dissoc! collection-name key)
Removes a document thats identified by the key parameter from a collection.
Modeled on core/dissoc
(http://clojuredocs.org/clojure_core/clojure.core/dissoc)
Does the same, just on an ArangoDB collection. The difference is that you can
currently only pass one key to remove
from the collection, not several like in clojure.core/dissoc
cla-get!
(cla-get! collection-name key)
Gets a document out of a collection by key.
122
Modeled on core/get (http://clojuredocs.org/clojure_core/clojure.core/get)
Does the same, just on an ArangoDB collection.
Currently this method throws an error when used with a key that does not
exist. This should be changed in the future,
also it should be possible to give a value that is returned by the function,
in case the key does not exist.
123
10.3 ArangoDB API Checkliste
Hier folgt eine Aufistung aller Interfaces der ArangoDB HTTP-API112 und welche davon
in Clarango umgesetzt wurden; zusammen mit der Angabe, in welchem Namespace der
Clarango Implementierung sich die Funktionen jeweils befinden.
•
HTTP Interface for Databases
→ umgesetzt in database Namespace
•
HTTP Interface for Documents
→ umgesetzt im document Namespace
•
HTTP Interface for Edges
→ nicht umgesetzt; es befinden sich jedoch nahezu identische Funktionen im
Interface for Graphs, siehe unten
•
HTTP Interface for AQL Query Cursors
→ umgesetzt im query Namespace
•
HTTP Interface for AQL Queries
→ umgesetzt im query Namespace
•
HTTP Interface for AQL User Functions Management
→ nicht umgesetzt
•
HTTP Interface for Simple Queries
→ teilweise umgesetzt (-by-example Methoden) im document Namespace
•
HTTP Interface for Collections
→ umgesetzt im collection Namespace
•
HTTP Interface for Indexes
→ nicht umgesetzt
•
HTTP Interface for Transactions
→ nicht umgesetzt
•
HTTP Interface for Graphs
→ umgesetzt im graph Namespace
•
HTTP Interface for Traversals
→ umgesetzt im graph Namespace
112 Entnommen aus dem „Implementor Manual“:
http://www.arangodb.org/manuals/current/ImplementorManual.html
124
•
HTTP Interface for Replication
→ nicht umgesetzt
•
HTTP Interface for Bulk Imports
→ nicht umgesetzt
•
HTTP Interface for Batch Requests
→ nach einigen Versuchen nicht umgesetzt, da unverhältnismäßig aufwendig
•
HTTP Interface for Administration and Monitoring
→ nicht umgesetzt
•
HTTP Interface for User Management
→ nicht umgesetzt
•
HTTP Interface for Async Results Management
→ nicht umgesetzt
•
HTTP Interface for Endpoints
→ nicht umgesetzt
•
HTTP Interface for Miscellaneous functions
→ nicht umgesetzt
125
Herunterladen