Ein Blick auf das CouchDB-Backend für FLOW

NoSQL mit FLOW3
Kommentare

Das Framework FLOW3 verspricht, mit modernen Konzepten und einem Fokus auf „Usability für Entwickler“ zu punkten, und auch NoSQL-Systeme sind modern und einfach zu nutzen. Klingt nach einer guten Kombination. Wir wollen ein wenig hinter die Kulissen blicken und dann der Frage nachgehen, wie NoSQL mit FLOW3 zusammenpasst.

Der aktuelle NoSQL-Begriff ist inzwischen wohl (im Gegensatz zum „ursprünglichen“ NoSQL-System [1]) jedermann bekannt, und viele scheinen NoSQL für die Silver Bullet zur Lösung aller (Persistenz-)Probleme zu halten.

Warum NoSQL?

Der Hauptgrund für den Aufstieg der so genannten NoSQL-Datenbanken ist der Bedarf nach besser skalierbaren Systemen. Im Internet wachsen die Lasten auf Onlineshops, Social-Networking-Sites und ähnlichen Systeme ständig. Um diesem Zuwachs entgegenzuwirken, werden oft die Serversysteme skaliert. Während es jedoch noch relativ einfach ist, hinter einem Load Balancer ein Dutzend Webserver zu betreiben, ist das bei Datenbanken schwieriger. Diese horizontale Skalierbarkeit ist bei den meisten NoSQL-Datenbanken von Anfang an eine zentrale Funktionalität – im Gegensatz zu den bekannten relationalen Datenbanksystemen, bei denen Replikation und Clustering oft ein Zusatzprodukt oder spät hinzugefügtes Feature sind. Um dies zu erreichen, muss man jedoch einige Kompromisse eingehen.

Relationale Datenbanksysteme folgen im Allgemeinen dem ACID-Grundsatz (Atomicity, Consistency, Isolation, Durability), um zuverlässige Transkationen und die Integrität der Daten zu gewährleisten. Das CAP-Theorem [2], von Eric Brewer 2000 vorgestellt [3], zeigt, dass man von den drei Eigenschaften ConsistencyAvailability und Partition Tolerance nur zwei zur gleichen Zeit garantieren kann. Stellt man nun die horizontale Skalierbarkeit in den Vordergrund, muss man sich daher zwischen Konsistenz und Verfügbarkeit entscheiden, denn die Partitionierung ist in verteilten Systemen gegeben. NoSQL-Systeme lockern meist die Anforderungen an die Konsistenz und versprechen „Eventual consistency“ [4]. Für diesen Ansatz gibt es natürlich auch ein Akronym: BASE (Basically Available, Soft-state, Eventually consistent). Die einzelnen Knoten in einem verteilten NoSQL-System sind natürlich konsistent im Sinne des „C“ aus „ACID“, aber das Gesamtsystem kann eben zeitweise inkonsistent sein. Was bleibt, sind also Systeme, die nur selten ACID-konform sind, nur eingeschränkte Transaktionssicherheit bieten und keine JOINS anbieten. Dafür sind sie meist einfach zu nutzen, skalieren horizontal und unterstützen von Haus aus Replikation.

Schemafreiheit als Chance?

Ein weiteres Merkmal der NoSQL-Systeme ist, dass sie schemafrei sind. Das heißt, es muss kein festgelegtes Datenschema genutzt werden, sondern es können beliebige Daten gespeichert werden. Manche Systeme bieten die Definition von Schemata an, können jedoch auch schemafrei genutzt werden. Aus der Sicht von FLOW3 ist das eine Vereinfachung. FLOW3 stellt das Paradigma des Domain-driven-Designs in den Vordergrund und erlaubt es, ohne Datenbankschema oder XML-Dateien Objekte zu persistieren. Grundlage ist die PHP-Klasse, in der die Klassenvariablen über Dokumentations-Annotationen (@var, @identity) mit den nötigen Metadaten angereichert werden. Schemafreie Datenbanken machen es an dieser Stelle einfacher, da nicht zu jeder Klasse ein Schema in der Datenbank angelegt werden muss. Konkret umgeht FLOW3 dies ohnehin, auch in der Standardkonfiguration, die SQLite über PDO nutzt, werden alle Daten in nur vier Relationen abgelegt, deren Struktur sich nicht mit den Klassen ändert.

NoSQL mit FLOW3 – Das CouchDB-Backend nutzen

Für FLOW3 bietet das CouchDB-Paket ein Persistenz-Backend an, das die Datenhaltung in CouchDB ermöglicht. Das Paket ist im Rahmen eines Projekts entstanden, bei dem bereits im Vorfeld die Nutzung von CouchDB eine feste Anforderung war. Die Techniker hatten verschiedene Datenbanken mit großen Datenmengen gefüttert und waren zu dem Schluss gekommen, CouchDB sei das richtige. Der Einsatz von CouchDB ist aus Benutzersicht einfach. Liegt das CouchDB-Paket in einem der Packages-Ordner von FLOW3 (z. B. Packages/Application/) und ist in PackageStates.yamlaktiviert, wird es das aktive Persistenz-Backend, indem es sich als Standardimplementierung für das Persistence-Backend-Interface festlegt (CouchDB/Configuration/Objects.yaml):

F3FLOW3PersistenceBackendBackendInterface:
className: 'F3CouchDBPersistenceBackendCouchDbBackend'

Vor der Nutzung von CouchDB müssen nun noch die Verbindungsdaten in Configuration/Settings.yaml eingetragen werden:

FLOW3:
persistence:
backendOptions:
database: 'myDatabase'
dataSourceName: 'http://127.0.0.1:5984

Beim nächsten Aufruf erzeugt FLOW3 nun die angegebene Datenbank und speichert Objekte darin, als Entwickler ist man an dieser Stelle fertig. Wer noch nie mit FLOW3 gearbeitet hat, findet Informationen und Downloads auf der Projektseite [5]. Ist man neugierig und geht den Dingen gern auf den Grund, kann man nun Futon (das CouchDB-Browserinterface) aufrufen und in die Datenbank hineinsehen.

Abb. 1: Futon erlaubt den Blick hinter die Kulissen

Ein relativ einfaches Objekt (hier eine Rolle aus dem Security-Framework) sieht darin aus wie in Listing 1 dargestellt.

{
"_id": "fb34cbf6-419b-42b6-a6ba-ed8808927ada",
"_rev": "1-090384c9d0e1440723e8a301e9ae081b",
"classname": "F3\FLOW3\Security\Policy\Role",
"properties": {
"identifier": {
"type": "string",
"multivalue": false,
"value": "Customer"
}
},
"parentIdentifier": "2f6cb0fb-aea5-40ae-a4d1-9d72aad68864"
}

Die dazugehörige PHP-Klasse sieht wie folgt aus:

/** * A role (role) for the PolicyService. These roles can be structured in a tree. * * @scope prototype * @entity */ class Role { /** * The string identifier of this role * @var string */ protected $identifier; /** * Constructor. * * @param string $identifier The string identifier of this role * @return void */ public function __construct($identifier) { $this->identifier = $identifier; } }

An diesem Beispiel kann man sehen, dass die in der Klasse festgelegten Properties (in diesem Fall nur $identifier) sowie einige Metadaten gegenüber dem FLOW3-internen Format fast unverändert gespeichert werden. Lediglich _id und _rev sind CouchDB-spezifisch. Da CouchDB, wie fast alle NoSQL-DatenBanken, keine Join-Operationen unterstützt, werden Value-Objekte vom CouchDB-Backend inline gespeichert, d. h. mit der sie referenzierenden Entity zusammen. Das kann man im folgenden Beispiel an der Eigenschaft personalData erkennen, wie in Listing 2 dargestellt.

{
"_id": "5117884658",
"_rev": "1-0237b3c8111f37a1fa5f650ac238c5ec",
"classname": "F3\MyPackage\Domain\Model\Customer",
"properties": {
"customerNumber": {
"type": "string",
"multivalue": false,
"value": "5117884658"
},
"personalData": {
"type": "F3\MyPackage\Domain\Model\PersonalData",
"multivalue": false,
"value": {
"identifier": "2fec5643c6312dc4d1d1d9a2502f6403d689aaf0",
"classname": "F3\Package\Domain\Model\PersonalData",
"properties": {
"salutation": {
"type": "string",
"multivalue": false,
"value": "ms"
},
"firstname": {
"type": "string",
"multivalue": false,
"value": "Lucious"
},
"lastname": {
"type": "string",
"multivalue": false,
"value": "Farrell"
},
"dateOfBirth": {
"type": "string",
"multivalue": false,
"value": "1995-06-22"
}
}
}
},
},
"parentIdentifier": null
}

Obwohl PersonalData ein Objekt ist und auch einen Identifier hat, wird es direkt in personalData abgelegt und kann so beim Laden der Daten für das Customer-Objekt direkt mit abgefragt werden. Eine ähnliche Optimierung ist für Entities möglich. Hier werden beim Rekonstituieren der persistierten Entities Datensätze von referenzierten Objekten nicht direkt geladen, sondern es werden zunächst nur die Identifier gesammelt. Im Anschluss werden alle nötigen Daten in einer Anfrage auf die _all_docs-View von CouchDB abgeholt.

Ein Drop-in Replacement?

Wenn man die Begeisterung für NoSQL nur oberflächlich verfolgt, ist es nicht schwer, die folgende Erwartungshaltung aufzubauen: Wir schreiben ein Backend für NoSQL-Datenbank X, in dem wir unsere Daten als JSON ablegen und die FLOW3-Queries in das passende spezifische Format umwandeln. Dieses Backend nutzen wir dann an Stelle des bisherigen, und schon geht alles viel schneller als mit RDBMS Y. Soweit die Theorie, der Teufel steckt aber wie üblich im Detail. Das Speichern und Laden von Daten war in der Tat recht schnell erledigt. Da FLOW3 intern bereits alle Objekte in ein „objektfreies“ Zwischenformat umwandelt, ist es tatsächlich kein Problem, es als JSON an CouchDB zu verfüttern. Auch das Wiederherstellen von Objekten ist vergleichbar einfach zu bewerkstelligen. Allerdings hat FLOW3 ein zwar recht einfaches, aber dennoch flexibles Query Object Model (QOM) um nach bestimmten Objekten zu suchen. Es ermöglicht beispielsweise beliebige Konjunktionen und Disjunktionen der verfügbaren Operatoren sowie das Sortieren nach beliebigen Properties.

$query = $this->createQuery();
$query->setOrderings(array('index' => F3FLOW3PersistenceQueryInterface::ORDER_ASCENDING));
$query->matching(
$query->logicalAnd(
$query->equals('workspace', $workspace),
$query->equals('depth', $depth),
$query->like('path', $path)
)
);

Eine vergleichbare Funktion bietet CouchDB nicht. Genau genommen gibt es in CouchDB gar keine Abfragesprache – sämtliche Suchanfragen müssen über Views und entsprechende Start- und End-Keys erledigt werden. Auch das Sortieren der Ergebnisse ist nicht wie gewohnt möglich, es kann wieder nur der Key auf- oder absteigend sortiert werden. In der Praxis wird dieses Problem durch zwei Dinge entschärft. Erstens ist es selten, dass Queries vollständig dynamisch aufgebaut werden – nur wenn der Nutzer einer Software selbst Anfragen zusammenstellen kann, ist das nötig. Sind jedoch nur Parameter in einer in ihrer Struktur festgelegten Abfrage vom Nutzer zu verändern, kann man die Abfrage bereits als View für CouchDB vorgeben. Aber selbst das ist nur selten notwendig, denn zweitens lassen sich die häufigsten Abfragen automatisch aus dem QOM von FLOW3 in entsprechende CouchDB-Views umwandeln. Ein Beispiel ist diese Query aus dem AccountRepository von FLOW3:

$result = $query->matching(
$query->logicalAnd(
$query->equals('accountIdentifier', $accountIdentifier),
$query->equals('authenticationProviderName', $authenticationProviderName)
)
);

Diese Anfrage wird für CouchDB in die folgende Map-Funktion umgesetzt:

function(doc) {
if(doc.classname == "F3FLOW3SecurityAccount") {
emit([doc.properties["accountIdentifier"].value, doc.properties["authenticationProviderName"].value], null);
}
}

Aufgerufen mit den passenden Tupeln für Start- und End-Key kann man die passenden Datensätze abrufen, das wird von FLOW3 im Hintergrund erledigt. Alles in allem ist der Umstieg auf CouchDB für den Entwickler, der das FLOW3-Framework nutzt, nicht mit großen Umstellungen verbunden. Es wird allerdings Fälle geben, in denen man die geforderten Queries nur schwer wird umsetzen können.

Ist NoSQL besser als SQL?

Der aktuelle NoSQL-Ansatz dient vor allem der Lösung von Skalierungsproblemen. Dass man hier nur weiterkommt, wenn man Kompromisse eingeht, muss jedem klar sein, der den Einsatz einer Datenbank wie CouchDB in Betracht zieht. Abfragen, die intensiv mit Verknüpfungen arbeiten und mit einem JOIN in SQL recht einfach zu realisieren wären, sind in NoSQL-Systemen unmöglich. Man kann das durch Denormalisierung umgehen oder die entsprechende Logik in seiner Anwendung implementieren, verlagert damit das Problem aber nur. Ähnlich liegt die Sache bei Aggregatsfunktionen, auch wenn hier mit Map/Reduce-Funktionen Lösungen möglich sind. Eine schöne Zusammenfassung dieser Problematik liefert Christian Köhntopp in einem Blogartikel [6].

Arbeitet man jedoch mit einem Datenmodell, in dem die Verbindungen zwischen Objekten weniger im Vordergrund stehen und gleichzeitig die Skalierbarkeit wichtig ist, kann man mit Systemen wie CouchDB richtig liegen. Es kommt also bei der Entscheidung für oder wider NoSQL vor allem auf die konkreten Rahmenbedingungen an, eine definitive Antwort gibt es nicht. Und auch wenn FLOW3 die Unterschiede zwischen RDBMS und NoSQL-Systemen weitestgehend abstrahiert, darf man nicht erwarten, ein komplexes Projekt transparent von einem Persistenz-Backend auf das andere umstellen zu können. Zu unterschiedlich sind an dieser Stelle die Konzepte.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -