Nicola Zunino

Lesezeit: 13 Minuten

Techblog

Nutzer und Rollen, Transaktionen und APIs in OrientDB

OrientDB ist eine multi-model Datenbank von Orient Technologies. Dieser Artikel ist der letzte Teil meines vierteiligen Deep Dives. Zum Abschluss beschäftige ich mich mit Transaktionen, Nutzer und Rollen, verteilte Architekturen und APIs. Transaktionen Eine Transaktion ist eine Session, deren Befehle entweder ganz oder gar nicht durchgeführt und persistiert werden sollen, also normalerweise, wenn es keine…

Techblog

OrientDB ist eine multi-model Datenbank von Orient Technologies. Dieser Artikel ist der letzte Teil meines vierteiligen Deep Dives.

  • Im ersten Teil dieser Blog-Serie gebe ich eine kurze Einführung in Datenbank-Management-Systeme (DBMS) und zeige Installation und erste Schritte mit OrientDB. Anschließend springe ich ins Arbeiten am konkreten Beispiel und zeige gängige SQL-Abfragen in OrientDB.
  • Im zweiten Teil der Serie geht es weiter mit Insert, SELECT, UPDATE DELETE und Relationen (nein, kein JOIN ;-)).
  • Teil drei dreht sich um Graphen, Klassen, und SQL.

Zum Abschluss beschäftige ich mich mit Transaktionen, Nutzer und Rollen, verteilte Architekturen und APIs.


Transaktionen

Eine Transaktion ist eine Session, deren Befehle entweder ganz oder gar nicht durchgeführt und persistiert werden sollen, also normalerweise, wenn es keine Fehler gibt; falls Befehle nur teilweise oder gar nicht durchgeführt wurden, muss alles muss rückgängig gemacht werden. Bei Datenbanken sagt man, eine Transaktion sei ACID (mit diesen vier Eigenschaften: Atomicity, Consistency, Isolation, Durability):

  • atomicity: die Befehle sind entweder alle und ganz durchgeführt oder nichts wird durchgeführt;
  • consistency: alle Daten und Constraints sind nach der Transaktion noch valide;
  • isolation: bis eine Transaktion nicht komplett durchgeführt wurde, sind die Resultate außerhalb der Transaktion nicht sichtbar;
  • durability: Endresultate werden persistiert.

OrientDB ist ACID.

Eine Transaktion hat drei Phasen:

  • Start: durch den BEGIN-Befehl; die Transaktion hat angefangen und bekommt eine ID;
  • Durchführung: eine Reihe SQL-Befehle;
  • Ende: Um ein Ende einer Transaktion zu erzeugen, kann man die Resultate persistieren mit dem COMMIT-Befehl oder alles rückgängig machen mit den ROLLBACK Befehl.

Es ist nicht möglich, Transaktionen zu verschachteln: Ein zweites BEGIN vor einem COMMIT oder ROLLBACK wirft einen Fehler.

Hook

Hooks sind Befehle, die bei Eintreten eines Ereignisses durchgeführt werden. Mögliche Ereignisse sind die CRUD-Actions. Hooks übernehmen für OrientDB die Funktion, die Trigger in relationalen Datenbanken sind.

Es geben zwei Hook-Typen:

  • Dynamic Hook, in der Datenbank oder in den Dokument definiert, sehr flexibel aber mit schlechter Performance;
  • Native Hook, geschrieben wie Java-Klassen und wesentlich schneller als dynamische.

Dynamic Hooks erzeugt man in drei Schritten. Als erstes wird eine Klasse benötigt, die die OTriggered Superklasse erweitert:

CREATE CLASS Customer EXTENDS OTriggered
 

Die Klasse Customer kann normale Dokumente speichern und gleichzeitig auf Events reagieren. Mögliche Events sind:

  • onBeforeCreate
  • onAfterCreate
  • onBeforeRead
  • onAfterRead
  • onBeforeUpdate
  • onAfterUpdate
  • onBeforeDelete
  • onAfterDelete

Insgesamt acht Events: vier After und vier Before. Jedes After/Before -Paar existiert für eine CRUD-Operation. In Schritt zwei eines Hooks geben wir das Event durch, auf das man reagieren muss:

ALTER CLASS Customer CUSTOM onAfterDelete = onDocumentDeletion
 

Durch den ALTER-Befehl wird jedes Mal, wenn ein Dokument der Klasse Customer gelöscht wird, die Funktion onDocumentDeletion aufgerufen. OrientDB erlaubt es, Funktionen in der Programmiersprache zu schreiben, die in der eigenen Engine oder in einer Java Virtual Machine durchgeführt werden können.

Als dritter Schritt könnte man zum Beispiel eine kurze JavaScript Function schreiben:

CREATE FUNCTION onDocumentoDeletion
 "print('Ein Kunde wurde gelöscht!!!')"
LANGUAGE Javascript
 

Dynamic Hook sind leicht konfigurierbar. Die Performance hingegen sind suboptimal. Bessere Performance hat man mit Native Hooks, die in Java kodiert werden müssen. Mehr dazu gibt es hier.

User und Rollen

OrientDB hat ein eigenes Sicherheitssystem. Dieses System basiert auf zwei Konzepten: Benutzer (User) und Rolle (Role). User sind alle Benutzer, die Zugriff auf die Datenbank haben. Role ist ein Zugriffsprofil, das aus zwei Elementen besteht: Working Mode und Regeln.

Der Working Mode definiert die generelle Erlaubnis, die einem User zugeteilt ist. Es gibt zwei Arten von Working Mode:

  • allow all but: der User kann alles machen, außer was explizit untersagt wird;
  • deny all but: der User kann nichts machen, außer was explizit erlaubt wird.

Das erste Working Mode ist für Superuser, der zweite für reguläre User mit weniger Privilegien konzipipert. Die Regeln sind spezifisch auf den Ressourcen geschnitten und regeln, was der User mit diesen Ressourcen anfangen kann. Ressourcen sind alle Elemente der Datenbank, zum Beispiel, Klasse, Cluster, Hook, Query usw..

Jede Datenbank hat drei Default User:

  • admin: Superuser mit allen Rechten;
  • reader: User, der alles sehen kann, aber nichts schreiben;
  • writer: User, der reader gleicht aber auch insert, update und delete der Records machen kann.

Es gibt auch drei gleichnamige Rollen mit den selben Privilegien. Das Default-Passwort für die User ist nur für dieses Beispiel gleich zu deren Namen, bitte nicht nachmachen ;-). Ein OrientDB User kann in nur zwei Stati sein: suspended oder active.

User und Rollen sind in zwei Clusters der Klassen OUser und ORole memorisiert, das bedeutet, dass man die User mit normalen SQL-Befehlen verwalten kann.

User-Verwaltung mit SQL

Um alle User einer Datenbank zu sehen:

SELECT FROM OUser
 

Im Output erfährt man die vier Elemente, die einen User charakterisieren: Name, Passwort durch SHA-256 (oder PBKDF2 ab Version 2.2) kodiert, Status (suspended oder active) und die Rollen. Mit dem INSERT Befehl wird ein neuer User eingetragen, der die selben Privilegien eines Writer hat:

INSERT INTO OUser SET name = 'newUser', password = 'myNotSmartPassword', STATUS = 'ACTIVE', roles = (SELECT FROM ORole WHERE name = 'writer')
 

Man kann einen User editieren, indem man den Befehl UPDATE eingibt:

UPDATE OUser SET name='mustermann' WHERE name='reader'
 

Auch Passwörter können so gewechselt werden. Der neue Wert muss nicht kodiert sein, ein Hook kodiert es korrekt:

UPDATE OUser SET password='thatsASecret!' WHERE name='reader'
 

Um neue Rollen zu erzeugen, muss man als erstes einen Eintrag in der ORole-Klasse schreiben, den passenden Name und Working Mode eingeben. Erst dann kann man die einzelnen Regeln setzen. Der Parameter mode der Klasse ORole ist 0 für deny all but und 1 für allow all but:

// working mode deny-all-but
INSERT INTO ORole SET name = 'newUserRole', mode = 0
// working mode allow-all-but
INSERT INTO ORole SET name = 'newSuperUserRole', mode = 1
 

Die Regeln sind dann tatsächlich Ausnahmen im Working Mode. Jede Regel enthält eine Zahl als Code, der die Privilegen für die Ressource regelt. Die Privilegien und die Codes sind:

  • NONE: 0
  • CREATE: 1
  • READ: 2
  • UPDATE: 4
  • DELETE: 8
  • ALL: 15

Die Ressourcen sind durch Konstanten definiert. Der Name der Ressourcen ist case-sensitive. Alles in den DBMS ist eine Ressource, zum Beispiel:

  • database: generischer Zugriff;
  • database.class._KlassenName_: eine bestimmte Klasse, zum Beispiel steht database.class.Customer steht für die Klasse Customer als Ressourcen-ID. Durch database.class.* wird generisch die Klassenverwaltung angesprochen;
  • database.cluster._ClusterName_: bestimmt ein Cluster für die Speicherung der Werte. Funktioniert analog zu database.class;
  • es gibt Ressourcen für Befehle wie database.query für Queries, database.command für alle Befehle, database.function für Funktionen.

So geben wir einer Rolle die Möglichkeit, Records in der Klasse Customer zu speichern:

UPDATE ORole PUT rules="database.class.Customer", 1 WHERE name="newUserRole"
 

newUserRole ist der Name der Rolle und 1 setzt das CREATE Privileg.

Eine Rolle kann sehr kompliziert sein und ähnelt mitunter einer schon existierenden Rolle. Es ist deswegen möglich, eine Rolle zu definieren, die eine bestehende Rolle erweitert. Zum Beispiel könnte man eine Rolle developer definieren, die auf der Rolle writer basiert:

UPDATE ORole SET inheritedRole = (SELECT FROM ORole WHERE name = 'writer') WHERE name = 'developer'
 

GRANT und REVOKE

Mit den SQL-Befehlen und OUser und ORole Klassen kann man User definieren. Es gibt zwei Sonderbefehle, die diese Arbeit erleichtern: GRANT und REVOKE; GRANT gibt Privilegien und REVOKE nimmt sie zurück.

GRANT permit ON resource TO ROLE
REVOKE permit ON resource TO ROLE
 

Zum Beispiel:

GRANT CREATE ON DATABASE.class.Customer TO developer
 

oder:

REVOKE CREATE ON DATABASE.class.Customer TO developer
 

Distributed Architectures

OrientDB ist wie alle NoSQL DBMS auf die Verarbeitung von großen Datenmengen ausgelegt, schon deswegen erlaubt es auch eine Multi-Node-Architektur mit sehr guter Performance, Skalierbarkeit und Fehlertoleranz.

Konfiguration

Bei einer Multi-Node-Konfiguration in OrientDB sind alle physikalischen Maschinen Master Nodes des Clusters. Das bedeutet, dass alle Maschinen die Master Rolle übernehmen können. Um eine solche Umgebung zu starten, muss man den Server durch dserver.sh (bzw. dserver.bat) starten. Der Server sucht dann autonom im Netz, ob es schon ein OrientDB Cluster existiert; falls nicht, startet er ein neues. In einem Netz können auch mehrere OrientDB Cluster koexistieren, die aber nicht miteinander kommunizieren, weil sie voneinander durch den group name getrennt sind.

Diese Cluster basieren auf einem Framework, Hazelcast Open Source Project, das schon in OrientDB integriert ist. Zur Konfiguration der Umgebung braucht man drei Dateien:

  • orientdb-server-config.xml, wo das Plugin OHazelcastPlugin aktiviert wird. Folgendes XML ist zu finden:
<handler class="com.orientechnologies.orient.server.hazelcast.OHazelcastPlugin">

            <parameters>

                        ...

                        ...

                        </parameters>

</handler>

Die wichtigsten parameters sind: enabled, falls true; aktiviert das Plugin und so die verteilte Architektur; configuration.hazelcastfür den Namen der Konfigurationsdatei von Hazelcast (per Default hazelcast.xml im config Ordner);

  • default-distributed-db-config.json, JSON-Datei für die tatsächliche Konfiguration;

  • hazelcast.xml, mit name und password für die Cluster-Verwaltung und andere Werte für das Discovering usw.

Neue Nodes, die dazukommen, oder alte Nodes, die gelöscht sind aus dem Cluster, sind unsichtbar für den Benutzer. Das System zeigt eine sehr hohe Fehlertaoleranz, da alles automatisiert und sehr schnell geht.

Replication

Replication ist typisch in Multi-Node-Umgebungen. Man hat so mehrere Kopien einer Datenbank im selben Cluster, um keine Daten zu verlieren und die Queries zu optimieren. OrientDB hat eine Multi-Master Replication, in der alle Nodes Master sind. Alle Knoten können so Records schreiben und an andere Knoten weitergeben.

Distributed Transitions sind selbstverständlich in OrientDB verwaltet. In einer Distributed-Architecture-Umgebung funktioniert dann die Transaktion ein bisschen anders: sobald eine Transaktion committed wird, werden alle Daten an alle Knoten geschickt, und jeder Knoten muss selbst committen. Einzelne Commits könnten auch fehlerhaft sein, aber es wird kein Rollback geben: Falls die korrekten Commits eine minimale, selbst definierte Schwelle erreichen, gilt das Commit für den ganzen Cluster. Ansonsten gilt ein globales Rollback.

APIs

Protokolle und Treiber

Alle wichtigsten Drivers kommunizieren mit OrientDB durch dasselbe Basisprotokoll, das binary protocol von OrientDB.

OrientDB bietet Support für drei Driver-Typen:

  • Binary native, ein Byte Protokoll;
  • HTTP REST in JSON Format;
  • Java wrapped für alle Programmiersprachen, die auf Java Virtual Machine basieren (wie Scala, Gremlin und JRuby).

Programmieren mit OrientDB

  • Java: native APIs stehen zur Verfügung, ebenso ein JDBC Driver, der typischen Funktionen der relationalen Datenbanken entspricht;
  • Node.js: OrientDB verfügt mit OrientJS über ein sehr gutes Tool für die Integration mit Node.js, inklusive Support und guter Dokumentation;
  • Javascript;
  • PHP und Microsoft .NET: Für PHP gibt es PhpOrient, für Microsoft .NET gibt es die .NET library for OrientDB. Beides sind offizielle Treiber, die auf dem Byte-Protokoll basieren;
  • Python: PyOrient;
  • C und C++: OrientDB selbst bietet keinen Support für diese Sprachen, die bekanntesten von anderen Playern sind LibOrient und OrientDB-C;
  • R: OrientR.

Die komplette Liste für alle APIs ist hier zu finden.

Programmieren mit Java API

OrientDB ist in Java geschrieben, entsprechend sind die Java APIs auch gut ausgearbeitet. Es gibt prinzipiell drei API-Modelle, eine für jede Spielart des DBMS:

  • Document API: am leichtesten zu benutzen, da sie an die typischen NoSQL-Datenbanken anknüpft; dadurch kann man Java Objekte in OrientDB Dokumente verwandeln und speichern;
  • Object API: basieren auf Document APIs und auf Reflection;
  • Graph API: um mit Graphen umzugehen; sind mit TinkerPOP kompatibel.

Dependencies

Um die Java APIs benutzen zu können, muss man die nötigen .jar Libraries (in OrientDB zu finden) einbinden. Das Haupt-Package heißt orientdb-core-*.jar, es reicht nur für eine Document-Umgebung. Für das Object Model muss man auch orientdb-object-*.jar einbinden. Für Graph API sind orientdb-core-*.jar, blueprints-core-*.jar, orientdb-graphdb-*.jar, jna-*.jar, jna-platform-*.jar und concurrentlinkedhashmap-lru-*.jar nötig.

Falls die Datenbank nicht lokal ist, kommen orientdb-client-*.jar und orientdb-enterprise-*.jar dazu.

Graphen programmieren

Hier ein Beispiel für Graphen APIs: es werden drei Knoten, also drei verschiedene Städte, und zwei Kanten mit der Entfernung erzeugt.

OrientGraphFactory factory = new OrientGraphFactory("plocal:C:/tempdb").setupPool(1,10);
		OrientGraph graph = factory.getTx();
		try {
			Vertex berlin = graph.addVertex(null);
			berlin.setProperty("name", "Berlin");
			Vertex hamburg = graph.addVertex(null);
			hamburg.setProperty("name", "Hamburg");
			Vertex muenchen = graph.addVertex(null);
			muenchen.setProperty("name", "München");
			Edge fromBerlinToHamburg = graph.addEdge(null, berlin, hamburg, "Berlin-Hamburg");
			fromBerlinToHamburg.setProperty("distance", Integer.valueOf(296));
			Edge fromMuenchenToHamburg = graph.addEdge(null, muenchen, hamburg, "München-Hamburg");
			fromMuenchenToHamburg.setProperty("distance", Integer.valueOf(792));
			for (Vertex v : graph.getVertices()) {
				String s=v.getProperty("name");
			    System.out.println(s);
			}
			for (Edge e : graph.getEdges()) {
				Integer s=e.getProperty("distance");
			    System.out.println(s);
			}
		} finally {
		   graph.shutdown();
		}
	}
 

Mit der Factory ist ein Graph erzeugt worden. Dieser basiert auf einer Datenbank, die passende URL wird durchgegeben. Es kann local (plocal:), remote (remote:) oder in-memory (memory:) gespeichert werden. shutdown schließt den Graph. Mit Graphen werden Transaktionen benutzt und die einzelnen Transaktionen werden mit shutdown oder commit beendet.

Drei Knoten und zwei Kanten werden instanziert. Die Felder werden mit der Methode setProperty eingegeben. Dabei werden nur zwei Parameter gegeben: Name der Property und Wert. Ein Kante braucht die beiden Knoten, die verbunden werden sollen.

Dokumente programmieren

Document APIs sind haben ein ähnliches Verhalten wie oben: Datenbank-Verbindung erzeugen, Interactions und Beendigung mit close(), um alle Ressourcen korrekt zu verwalten. In Java könnte man auch direkt die Datenbank instanzieren:

ODatabaseDocumentTx db = new ODatabaseDocumentTx("plocal:telefonbuch").create();
 

ODatabaseDocumentTx db = new ODatabaseDocumentTx("plocal:telefonbuch").open("admin", "admin");
       try {
            ODocument eintrag = new ODocument("Person")
               .field("name", "Max")
               .field("surname", "Mustermann")
               .field("city", "Berlin");
            eintrag.save();
       } finally {
            db.close();
       }
 

Dieser Code macht einen INSERT des Records in der nicht strukturierten Klasse Person. Der Record hat drei Felder, die mit der Methode field() eingegeben werden, ähnlich wie eine Map. Die Connection muss immer durch close() im finally-Block geschlossen werden.

Daten, die zu einer Klasse bzw. einem Cluster gehören, kann man mit den Methoden browseClass() bzw. browseCluster() abrufen.

for (ODocument doc:db.browseClass("Person"))
	           System.out.println(doc);
 

Die Daten werden im JSON-Format zurückgegeben.

Javascript API

OrientDB kann durch Javascript APIs in HTML verwendet werden. Es handelt sich um REST Calls, die in Javascript Functions eingekapselt sind.

Zum Beispiel:

var db = new ODatabase('http://localhost:2480/mydb');
 
db.open('admin', 'admin');
 
let results = database.query('select from Customers');
 
db.close();
 

Die vier Zeilen entsprechen vier dezidierten Phasen:

  • Verbindung: ein ODatabase Objekt wird instanziert durch eine URL mit der gängigen Struktur;
  • Authentifizierung durch die open-Methode. Falls keine Parameter durchgegeben werden, handelt es sich um eine Browser Authentication (der Browser fragt Username und Passwort) oder man gibt die zwei Parameter an. Ein JSON-Objekt mit allen Infos zur Datenbank wird zurückgegeben;
  • Queries und Commands: Beide werden auf das bestehenden ODatabase-Objekt aufgerufen. Mit query und der passenden SQL-Befehl ist es möglich Daten abzufragen. executeCommand benutzt man zum Beispiel, um DDL-Befehle auszuführen. In beiden Fälle ist die Antwort als JSON kodiert.
  • Verbindung schließen mit close.

Vorteil: Subsysteme

OrientDB zeigt sich als komplettes Database-Management-System mit allen Features für den produktiven Einsatz. Seine größen Vorteile sind aus meiner Sicht: Es ist leicht integrierbar mit allen gängigen Programmiersprachen. Außerdem erlaubt es durch seine Flexibilität einen einfachen Übergang von relationalen Datenbanken zu NoSQL-Datenbanken mit Dokument- oder Graphen-Subsystem.


Über den Autor

Nicola Zunino

WE, Web Engineering

Nicola arbeitet seit 2017 bei MaibornWolff. Zuerst war er als Frontend Entwickler tätig, mit Schwerpunkte JavaScript und Frameworks wie Angular und React, entdeckte dann jedoch, dass ihm Architekturen und Analysen, also designen, mehr Spaß macht. Als ehemaliger Dozent, unterstützt er interne Schulungen (Clean Code) und Entwicklung der Kollegen. Als Doktor Ingenieur hat er sich in Daten Optimierung und Datenbanken spezialisiert: Datenanalyse, Pattern Matching und Clustering sind noch seine Leidenschaften. 

LinkedIn: https://www.linkedin.com/in/nicola-zunino-22852037/, Twitter: @NicolaZStrong, Instagram: @nicola_zunino, nicola.zunino@maibornwolff.de