Entwurf und Implementierung eines Clojure-Treibers für ArangoDB



Ähnliche Dokumente
Fachbericht zum Thema: Anforderungen an ein Datenbanksystem

Überblick und Vergleich von NoSQL. Datenbanksystemen

ANALYTICS, RISK MANAGEMENT & FINANCE ARCHITECTURE. NoSQL Datenbanksysteme Übersicht, Abgrenzung & Charakteristik

Handbuch ECDL 2003 Basic Modul 5: Datenbank Grundlagen von relationalen Datenbanken

Guide DynDNS und Portforwarding

NoSQL HANSER. Einstieg in die Web 2.0 Datenbanken. Stefan Edlich Achim Friedland Jens Hampe Benjamin Brauer Markus Brückner

Suche schlecht beschriftete Bilder mit Eigenen Abfragen

4D Server v12 64-bit Version BETA VERSION

Web-Kürzel. Krishna Tateneni Yves Arrouye Deutsche Übersetzung: Stefan Winter

Inhalt. 1 Einleitung AUTOMATISCHE DATENSICHERUNG AUF EINEN CLOUDSPEICHER

OPERATIONEN AUF EINER DATENBANK

PHP Kurs Online Kurs Analysten Programmierer Web PHP

Übung: Verwendung von Java-Threads

Web Services stellen eine Integrationsarchitektur dar, die die Kommunikation zwischen verschiedenen Anwendungen

OP-LOG

EasyWk DAS Schwimmwettkampfprogramm

pro4controlling - Whitepaper [DEU] Whitepaper zur CfMD-Lösung pro4controlling Seite 1 von 9

schnell und portofrei erhältlich bei beck-shop.de DIE FACHBUCHHANDLUNG mitp/bhv

Ein Ausflug zu ACCESS

12. Dokumente Speichern und Drucken

Virtueller Seminarordner Anleitung für die Dozentinnen und Dozenten

Professionelle Seminare im Bereich MS-Office

Stefan Edlich Achim Friedland Jens Rampe Benjamin Brauer. NoSQL. Einstieg in die Welt nichtrelationaler Web 2.0 Datenbanken HANSER

Man liest sich: POP3/IMAP

Übungen zur Softwaretechnik

mysql - Clients MySQL - Abfragen eine serverbasierenden Datenbank

RESTful Web. Representational State Transfer

Ablaufbeschreibung für das neu Aufsetzen von Firebird und Interbase Datenbanken mit der IBOConsole

Ist Excel das richtige Tool für FMEA? Steve Murphy, Marc Schaeffers

Adminer: Installationsanleitung

ÖKB Steiermark Schulungsunterlagen

Diese Ansicht erhalten Sie nach der erfolgreichen Anmeldung bei Wordpress.

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Registrierung am Elterninformationssysytem: ClaXss Infoline

Arbeiten mit UMLed und Delphi

Barrierefreie Webseiten erstellen mit TYPO3

Kapitel 4 Die Datenbank Kuchenbestellung Seite 1

Dokumentation. Black- und Whitelists. Absenderadressen auf eine Blacklist oder eine Whitelist setzen. Zugriff per Webbrowser

Er musste so eingerichtet werden, dass das D-Laufwerk auf das E-Laufwerk gespiegelt

KURZANLEITUNG CLOUD OBJECT STORAGE

Robot Karol für Delphi

SDD System Design Document

Autorisierung. Sicherheit und Zugriffskontrolle & Erstellen einer Berechtigungskomponente

Microsoft PowerPoint 2013 Folien gemeinsam nutzen

Outlook. sysplus.ch outlook - mail-grundlagen Seite 1/8. Mail-Grundlagen. Posteingang

Installation SQL- Server 2012 Single Node

Web Sockets mit HTML5. Quelle:

Dokumentation von Ük Modul 302

Newsletter. 1 Erzbistum Köln Newsletter

Objektorientierte Programmierung für Anfänger am Beispiel PHP

Zeit bedeutet eine Abwägung von Skalierbarkeit und Konsistenz

Einrichten eines Postfachs mit Outlook Express / Outlook bis Version 2000

Mit der Maus im Menü links auf den Menüpunkt 'Seiten' gehen und auf 'Erstellen klicken.

SEMINAR Modifikation für die Nutzung des Community Builders

2 Die Terminaldienste Prüfungsanforderungen von Microsoft: Lernziele:

.htaccess HOWTO. zum Schutz von Dateien und Verzeichnissen mittels Passwortabfrage

crm-now/ps Webforms Webdesigner Handbuch Erste Ausgabe

(1) Mit dem Administrator Modul werden die Datenbank, Gruppen, Benutzer, Projekte und sonstige Aufgaben verwaltet.

Kurzanleitung zu. von Daniel Jettka

Java Enterprise Architekturen Willkommen in der Realität

Folgende Einstellungen sind notwendig, damit die Kommunikation zwischen Server und Client funktioniert:

Im Original veränderbare Word-Dateien

SEPA Lastschriften. Ergänzung zur Dokumentation vom Workshop Software GmbH Siemensstr Kleve / /

Gesetzliche Aufbewahrungspflicht für s

Einleitung: Frontend Backend

Zur Bestätigung wird je nach Anmeldung (Benutzer oder Administrator) eine Meldung angezeigt:

In diesem Thema lernen wir die Grundlagen der Datenbanken kennen und werden diese lernen einzusetzen. Access. Die Grundlagen der Datenbanken.

Lizenzierung von System Center 2012

Artikel Schnittstelle über CSV

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

WEBSEITEN ENTWICKELN MIT ASP.NET

Database Exchange Manager. Infinqa IT Solutions GmbH, Berlin Stralauer Allee Berlin Tel.:+49(0) Fax.:+49(0)

Klaus Schild, XML Clearinghouse Namensräume

Über die Internetseite Hier werden unter Download/aktuelle Versionen die verschiedenen Module als zip-dateien bereitgestellt.

Software Engineering Klassendiagramme Assoziationen

AGROPLUS Buchhaltung. Daten-Server und Sicherheitskopie. Version vom b

Proseminar: Website-Managment-System. NetObjects Fusion. von Christoph Feller

SANDBOXIE konfigurieren

Datensicherung. Beschreibung der Datensicherung

Der beste Plan für Office 365 Archivierung.

Konfiguration VLAN's. Konfiguration VLAN's IACBOX.COM. Version Deutsch

Leitfaden zur Nutzung von binder CryptShare

Urlaubsregel in David

So richten Sie Ihr Postfach im Mail-Programm Apple Mail ein:

Anforderungen an die HIS

Speicher in der Cloud

Klausur WS 2006/07 Programmiersprache Java Objektorientierte Programmierung II 15. März 2007

ARCO Software - Anleitung zur Umstellung der MWSt

Microsoft Access 2013 Navigationsformular (Musterlösung)

Flash, Network und Facebook. Steven Mohr

Ihre Interessentendatensätze bei inobroker. 1. Interessentendatensätze

SSH Authentifizierung über Public Key

-Inhalte an cobra übergeben

4. Jeder Knoten hat höchstens zwei Kinder, ein linkes und ein rechtes.

AUF LETZTER SEITE DIESER ANLEITUNG!!!

Nutzung von GiS BasePac 8 im Netzwerk

Nun klicken Sie im Hauptfenster der -Vewaltung auf den Schriftzug Passwort. Befolgen Sie die entsprechenden Hinweise: 3.

1. Einschränkung für Mac-User ohne Office Dokumente hochladen, teilen und bearbeiten

Anlegen eines DLRG Accounts

Datenbanken Kapitel 2

Transkript:

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 Web- Anwendung 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 werden 2. 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 Clojure 3. 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 Lisp 6 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 Community 8. 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 Lisp- Dialekte 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 ClojureScript 9, 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 2004 10 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 Web- Startups 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 identifizieren 11 : 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 Join- Operationen leicht kompliziert werden; außerdem ergibt sich eine gewisse Fehleranfälligkeit und Starrheit dadurch, dass die Anfragen in Form von Strings formuliert werden; NoSQL- Lösungen bieten hier häufg eine einfachere API; Beispiel sind etwa Datenbanken, deren Interaktion komplett über eine REST-Schnittstelle läuft 12 ; bei komplexen Datenbank- Anfragen 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 sind 14 ; 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 ID- Felder 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 MongoDB 17 und CouchDB 18. 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 abgespeichert 19, es wird JavaScript als primäre Interaktionssprache eingesetzt und beide bieten Map/Reduce-Funktionen 20. 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 Web- Technologien 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 dazwischen 23. 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 Revisions- Nummer 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 Riak 25, 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 Neo4j 26. 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 DOM- Zugriff 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 OrientDB 30 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/Value- Datenbank verwendet werden 31. 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 Hypertext 33 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 Identifer 35, 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 cacheable 37 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 auf 38. 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 sie 39. 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 HTTP- Header 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 PUT- Methode 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 HTTP- Anfragen 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 (Accept- Language) 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 If- Modifed-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/http- Statuscode 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 Client- Zugriff ü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öffentlicht 45 ). 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 Dokument- Datenbank 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 Web- Anwendung 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]. Datenbank- Anfragen 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 verglichen 47. Es zeigte sich, dass die Performance von ArangoDB im Fileserver- Modus 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 Merge- Prozess 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-Collection 50 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 JSON- Format 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 Worker- Prozessen [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 System- Datenbank 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 Datenbank- Konfiguration 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 String- Verkettungen 53. 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 Indices 54 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 JavaScript- Shell, 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 Kommandozeilen- Interface 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 Browser- Interface 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 ArangoDB 58 dokumentiert ist. Der Treiber soll durch die Nutzung einer HTTP-Library Anfragen an die HTTP- Schnittstelle 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 Hosting- Dienst GitHub 61 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 Leiningen 62 zum Einsatz. Leiningen ist der Quasi- Standard 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 http://git-scm.com/ 61 http://www.github.com 62 http://leiningen.org/ 63 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-http 64 ist eine Clojure-Library, die dazu dient, HTTP-Anfragen zu senden und die Antworten zu empfangen. Die Library funktioniert als Wrapper der Apache HttpComonents Library 65 für Java. 4.2.1 Cheshire Bei Cheshire 66 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 Standard- Datenstrukturen 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 bereitzustellen 67. 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 Graph- Funktionalitä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 Für weitere Informationen siehe auch https://help.github.com/articles/about-releases 68 CRUD ist eine Abkürzung für Create, Read, Update, Delete 69 https://github.com/edlich/clarango/releases/tag/v0.3.2 70 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 Clojure- Datenbanktreiber 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 Query- Sprache einzubeziehen, wird zuletzt noch der Treiber Neocons für die Graph-Datenbank Neo4J untersucht. 4.4.1 Monger für MongoDB Bei Monger 71 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 Beispiel- Dokuments. 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 Operators 72 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 Clutch 73 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 abgelegt 74. 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-mongodb- Guy-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. Elastisch 75 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 Carmine 77 angeschaut werden. Bei Carmine handelt es sich um einen Client für die Key/Value-Datenbank Redis, der zusätzlich Funktionen einer Message- Queue 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 Pipeline 78 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 URL- Parameter /?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 Datenbank- Verbindung, 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 sollten 80. 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 JSON- Library 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 Konvention 81, 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 Clarango- API 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 API- Methoden 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 Example- Ansatz 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-gremlin 83 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 CRUD- Methoden 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 Graph- Ressource 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 API- Methoden 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 Collection- Namen 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 ist 84, wurde ein Try-Catch-Block eingefügt. In diesem findet die gesamte Server- Interaktion 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 Default- Implementierung 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 HTTP- 84 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 Methode 86 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 Datenbank- Methoden 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 Collection- Methoden 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 Clojure- Mengenoperationen 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 Testimplementierung 88 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 assoc 89, dissoc 90, conj 91 und get 92 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 Hier einzusehen: https://gist.github.com/cemerick/1485920 89 http://clojuredocs.org/clojure_core/clojure.core/assoc 90 http://clojuredocs.org/clojure_core/clojure.core/dissoc 91 http://clojuredocs.org/clojure_core/clojure.core/conj 92 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-ops 96. 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: 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 93 http://clojuredocs.org/clojure_core/clojure.core/assoc! 94 http://clojuredocs.org/clojure_core/clojure.core/dissoc! 95 http://clojuredocs.org/clojure_core/clojure.core/conj! 96 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-Requests 97 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 Batch- Interface 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 Form- Posts. Es wurde zunächst angestrebt, diese Möglichkeit auch zu nutzen, um die Batch- Requests 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 Trenn- String 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 mitzusenden 98. 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 Default- Verbindungsdaten. 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 System- Architektur 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