Mapping von Daten zwischen Pesistenzmedium und Objektmodell ist nicht zu vermeiden. Es ist keine Last, sondern pure Notwendigkeit, wenn wir mit persistenten Daten “in Zeit und Raum” integrieren wollen. Denn dann müssen persistente Daten eine sehr flexible Struktur haben, die sich immer wieder neuen Anforderungen an in-memory Datenmodelle anpassen lässt. Zu dieser Erkenntnis hat mich eine Diskussion um Ayende Rahiens Dokumentendatenbank RavenDB gebracht.
Mein Gefühl ist nun, dass solch flexible persistente Struktur weder RDBMS noch DocDBs bieten. Ihre Datenmodelle sind zwar flexibler als die von ODBMS, doch ich glaube, das ist noch nicht genug. Zum Beleg hier Ayendes Dokumente für eine Blog-Datenbank:
Das implizite Schema ist ganz einfach. User, Blog, Post sind Entitäten, die einander referenzieren; ein Post enthält Comments, verweist also nicht weiter auf sie.
Die ist natürlich überschaubarer als ein typisches relationales Schema für das Szenario (ebenfalls von Ayende):
Was habe ich nun daran auszusetzen? Das relationale Schema ist vor allem erstmal ein Schema, das geplant werden muss. Solche Planung ist gerade in der Anfangsphase eines Projektes schwierig und tendiert dazu, immer wieder angepasst werden zu müssen. Häufige Schemaänderungen am Persistenzmedium machen aber keinen Spaß.
Dem hält die NoSql-Bewegung schemalose Datenbank entgegen, von denen RavenDB ein Vertreter ist. Ayende konnte die obigen Dokumente einfach so ohne Vorplanung in seine Datenbank “schmeißen”. Keine Schemadefinition ist nötig. Er kann sie dann über ihre Id oder Queries auf ihren Feldern wieder herausholen. Eine NoSql-Datenbank leistet also nicht weniger als ein RDBMS – spart Ihnen jedoch Planungsaufwand. Ändert sich die Struktur eines Dokuments, speichern Sie es einfach neu. RavenDB ist auch tolerant gegenüber dem Hinzufügen/Löschen von Feldern oder Typänderungen. Indexe für schnelle Suche gibt es ebenfalls.
Sind NoSql-Datenbanken also nicht vielleicht die besseren Datenbanken?
Es gibt keine “absolut guten” Datenbanken, die immer eingesetzt werden sollten. Güte/Qualität/Eignung hängt von Ihren Anforderungen ab. Die unausgesprochene Anforderung, die RDBMS lange erfüllt haben und immer noch erfüllen, war/ist die nach hoher Effizienz gepaart mit einiger Flexibilität. Immerhin sind RDBMS Kinder der 1970er, als Software unter starker Ressourcenknappheit (Speicher, Prozessorgeschwindigkeit, Datenübertragungsrate) gelitten hat. Da war Effizienz in dieser Hinsicht ein hohes Gut.
Heute sind diese Ressourcen in vielen Szenarien kein Problem mehr. Andere Anforderungen gewinnen daher an Bedeutung. Die aus meiner Sicht wesentliche Anforderung ist die nach Flexibilität. Wir haben schon genug Mühe mit unseren Objektmodellen, da wollen wir nicht noch aufwändig ein zweites Schema für die persistenten Daten pflegen.
Dass NoSql-Datenbanken jetzt ein Thema sind, finde ich also ganz verständlich. Sie bieten durch Schemalosigkeit mehr Flexibilität. (Dass sie darüber hinaus auch noch andere Anforderungen besser als RDBMS erfüllen, lasse ich hier undiskutiert. Darum geht es mir nicht.)
Doch warum bei Tupeldatenbanken (z.B. Amazon SimpleDb) oder Dokumentendatenbanken (z.B. RavenDB) stehenbleiben? Geht vielleicht noch mehr Flexibilität? Ich glaub schon.
Beziehungsprobleme
Mein Gefühl ist, dass auch Dokumentendatenbanken noch Flexibilitätspotenzial verschenken. Das Problem steckt aus meiner Sicht im Umgang mit Beziehungen:
Ein Post-Dokument verweist auf ein Blog-Dokument, ein Post-Dokument enthält Felder und insb. mehrere Comment-Strukturen. Das sind zwei Arten von Beziehungen, explizite und implizite. Einmal wird verwiesen, einmal wird enthalten.
Klingt alles so natürlich – doch ich glaube, da steckt der Teufel im Detail. Wir machen unsere Datenmodelle unflexibel, weil wir beide Arten von Beziehungen vermischen. Ein Dokument wie ein Post ist über den Verweis an das Blog-Dokument gekoppelt. Ein Post ist abhängig vom Blog. Und Abhängigkeiten sind unschön; soviel sollte sich herumgesprochen haben.
Dass wir Einheiten definieren und persistieren wollen, ist unzweifelhaft. Ich habe also nichts gegen Dokumente. Solche Einheiten fassen Daten in Form von Feldern (Name-Wert-Paare) zusammen. Welche Felder sollen in einem Dokument stehen? Felder mit hoher Kohäsion, d.h. solche, die einfach eng zusammen gehören.
Dokumente sind damit sozusagen Einheiten mit einer “Single Responsibility”. Die stehen für genau eine Sache, z.B. ein Artikel in einem Blog oder ein Benutzer usw. Das macht sie auch zu Einheiten hoher Effizienz. Denn so ein Dokument kann man “in einem Rutsch” gut aus einem Persistenzmedium laden.
Und solche Einheiten haben eine Identität, d.h. sie lassen sich referenzieren, statt immer wieder in unterschiedlichen Zusammenhängen gespeichert werden zu müssen. Sie manifestieren das DRY-Prinzip in der Datenhaltung. Konsistenz ist leichter herstellbar.
Ob innerhalb eines Dokuments dann ein Feld nur einen Datenwert hat oder mehrere, halte ich für unerheblich. Im obigen Dokument hat tags z.B. mehrere Werte. Das widerspricht der relationalen 1. Normalform – aber was soll´s ;-) Wir reden über Flexibilität und Dokumentendatenbanken. Ganz pragmatisch gesehen sind multiple Werte kein Problem.
Welche Form ein Wert jedoch hat, das halte ich für bedenkenswert. Sollte ein Wert, der innerhalb eines Dokuments steht, selbst wieder eine Struktur haben, so wie die Comment-Werte? Ich glaube, nein. Felder sollten die Atome eines Dokuments sein, ihre Werte unteilbar. Zeichenketten, Zahlen, Datum/Zeit… das war´s.
Das halte ich aus zwei Gründen für vorteilhaft:
- Ob ein Feld einen oder mehrere Werte haben können soll, ist bei der Datenmodellierung meist schnell entschieden. Für die Persistenz als Dokument macht es, wie oben gezeigt, außerdem keinen Unterschied. Auch ob ein Feldwert eine Struktur hat oder nicht, ist meist leicht zu entscheiden. Straße+PLZ+Ort als 3 Felder speichern oder doch besser als eigene Datenstruktur? Kein Problem. Doch dann kommt die knifflige Frage, wenn die Entscheidung für eine Datenstruktur fielt. Soll die in einem Dokument gespeichert werden oder außerhalb nur mit einer Referenz darauf? Darüber kann man lange diskutieren und immer wieder Blicke in die Glaskugel werfen.
Um das zu vermeiden, möchte ich nahelegen, per se alle strukturierten Werte nicht in einem Dokument abzulegen, sondern darauf zu verweisen. Mein erster Antrieb ist also hohe Produktivität. Nicht lang schnacken ;-) - Doch nicht nur die Produktivität steigt, wenn Sie über Inklusion/Exklusion nicht lange diskutieren müssen. Daten, die für sich, d.h. separat in einem (kleinen) Dokument gespeichert sind, können Sie wiederverwenden. Im Hauptspeicher sind Objekte die Einheiten, die referenziert werden können. In Doumentendatenbanken sind es Dokumente. Felder zu Strukturen zusammenfassen und separat speichern spart also Platz und macht es leichter, Konsistenz herzustellen.
Das ist erstmal das Potenzial, das in der Auslagerung von Strukturen liegt. Ob Sie es nutzen und einem Adressdokument wirklich eine eigene Identität geben und es wiederverwenden, ist dann nochmal eine zweite Frage. Die Auslagerung, d.h. Entkopplung von Strukturen macht das jedoch erst möglich. - Auslagerung ist Entkopplung. Oder umgekehrt: Einschachtelung ist eine stärkere Form der Kopplung als Referenzieren. Es ist ja gerade der Zweck eines Dokuments, sich eng an seine Felder zu koppeln. Das führt zu Effizienz. Was zusammengehört, soll man nicht auseinander reißen. So eng koppeln wie (aus Effizienzgründen) nötig, so lose koppeln wie möglich. Das ist nicht für in der Softwarearchitektur wichtig, sondern auch bei der Datenmodellierung eine Tugend, denke ich. Wo Sub-Strukturen auftauchen, scheint mir daher eine Grenze überschritten. Bei ihnen sollte die enge Kopplung durch Inklusion enden. Schachtelung ist also nicht nur bei Programmstrukturen “böse”, sondern auch bei Datenstrukturen.
Unterm Strich bin ich also dafür, Daten zu Dokumenten zusammenzufassen. Eine gute Sache. Daten in einen Sack füllen, zubinden, ab in die Datenbank damit.
Diese “Datensäcke”/Dokumente sind für mich das Granulat flexibler persistenter Datenhaltung. Sie sind die aus Anwendungssicht unteilbaren Dateneinheiten. Die hohe Kohäsion ihrer Felder hält sie zusammen. Und die Felder haben wiederum atomare Werte – allerdings können das mehrere sein.
Soweit zur Modellierung der Kommentare in Ayendes Post-Dokument. Was aber ist mit dem Verweis auf das Blog? Ich glaube, dass Felder keine Beziehungen enthalten sollten. Punkt.
Ja, genau, lassen Sie sich das auf der Zunge zergehen: Dokumente selbst referenzieren keine anderen Dokumente. Referenzen wie das blog-Feld in Ayendes Post-Dokument halte ich für eine zu enge Kopplung. Wie oben schon gesagt: Der Verweis macht das Dokument abhängig. Das ist bei Daten so unschön wie bei Funktionseinheiten. Abhängigkeiten sind “böse”.
Warum steckt der Bezug zum Blog im Post? Aus zwei Gründen: Erstens aus einem Mangel an geeigneten Alternativen und zweitens aus Effizienzgründen. Beides halte ich für überwindbare Hindernisse, wenn persistente Datenmodelle flexibel sein sollen. Der Effizienzgrund verschwindet durch den Wunsch nach Flexibilität. Effizienz und Flexibilität widersprechen sich (meistens). Und der Mangel an Alternativen sollte sich beheben lassen. Hier ist “Tooling” gefragt.
Ohne eingeschachtelte Strukturen (comments) und ohne Beziehungen (blog) halte ich dann das Beziehungsproblem für gelöst. Es gibt dann keine enge Kopplung mehr nach innen und keine Kopplung mehr nach außen. Dokumente stehen dann für sich im “Datenraum” als “Granulatkügelchen”. Mein Post-Dokument würde so aussehen:
// posts/1
{
"title": "RavenDB",
"content": "... content ...",
"categories": ["Raven", "NoSQL"]
"tags" : ["RavenDB", "Announcements"],
}
Beziehungsraum
Wenn Dokumente keine Beziehungen mehr enthalten, wie werden die denn dann aber aufgebaut? Sie sind doch wichtig. Post-Dokumente gehören zu einem Blog und haben Kommentare. Irgendwie muss ein Post-Dokument also auf sein Blog und seine Kommentare verweisen. Doch wo/wie soll das geschehen, wenn es keine Felder mit Referenzen und keine Schachtelung mehr gibt?
Ich schlage einen “Beziehungsraum” vor, einen Bereich/Container, der nur die Beziehungen zwischen Dokumenten verwaltet. Das wäre eine klare Separation of Concerns (SoC). Dokumente enthalten Daten. Der Beziehungsraum enthält Beziehungen. Zusammen ergeben sie das persistente Datengeflecht.
Im Beziehungsraum wären jedem Dokument die Dokumente zugeordnet, mit denen es in Beziehung steht. Der Beziehungsraum bestünde also quasi nur aus Identitäten. Der Identität post/1 wären dann z.B. die Identitäten blog/1 und comment/1, comment/2 zugeordnet.
Etwas weniger bildhaft könnte das so aussehen:
Das wäre dann ein normaler Graph. Die Beziehungen sind bewusst ohne Pfeile, weil ich denke, dass alle Beziehungen grundsätzlich bidirektional persistiert werden sollten. Denn: Wer weiß, ob man nicht irgendwann mal auch “rückwärts” traversieren will? Wer heute ein Blog herauszieht aus der Datenbank, um darüber Postings zu erreichen, der will es morgen vielleicht umgekehrt tun. Immer schön flexibel bleiben.
Ein Graph legt natürlich nahe, eine Graphendatenbank wie Neo4J (http://neo4j.org/) oder InfoGrid (http://infogrid.org/) für das Datengranulat einzusetzen. Leider gibt es anscheinend bisher (Stand April 2010) keine ähnlich leistungsfähigen Angebote auf der .NET Plattform, doch das mag sich ja ändern. Dokumentendatenbanken schießen aus dem Boden, warum nicht auch Graphendatenbanken? Müssen ja nicht gleich Enterprise-Qualität haben. Erstmal geht es auch kleiner, embedded, um mal mit dem Paradigma warm zu werden.
Oder sind Graphendatenbanken es auch noch nicht? Denn lt. deren API sieht es noch so aus, als würden Beziehungen mit den Knoten gespeichert. (Aber vielleicht ist das nur einem einfachen API geschuldet und im Persistenzmedium sieht es dann anders aus.) Denn meine Idee ist ja eben, die Knoten (“Datensäcke”) frei von Beziehungen zu halten. Das Bild dafür sähe eher so aus:
“Irgendwas” zwischen den “Datensäcken”, den Granulatkügelchen setzt sie in Beziehung.
Während ein generischer Graphendatenbank-API Beziehungen so aufbauen würde:
Node post = new Node(…);
Node blog = new Node(…);
post.Relate(blog);
oder
blog.Relate(post);
Wäre ein Granulat-API ehrlich, wenn er es so täte:
RelationSpace rs = …;
rs.Relate(post, blog);
Aber das könnte natürlich auch in einer Node.Relate() Methode versteckt sein. Hm… Ich bin noch unsicher, wie ich es gern hätte.
In jedem Fall scheint mir die deutliche Unterscheidung zwischen Daten und Beziehungen wichtig. So wird das persistente Datenmodell sauber (im Sinne des SoC) und flexibel. Zusätzlich wären Beziehungen immer first class citizens und könnten ebenfalls mit Attributen versehen werden. Sie könnten z.B. den Beziehungsenden “Rollen” zuweisen:
rs.Relate(post, “partOf/contains”, blog);
Aus Sicht eines Posting wäre das Blog das Ganze dessen Teil (“partOf”) es ist. Für ein Posting könnte damit gezielt erfragt werden, wovon es denn Teil ist:
IEnumerable<Node> blogs = rs.Traverse(post, “partOf”);
Oder ein Blog könnte nach seinen Postings befragt werden:
IEnumerable<Node> postings = rs.Traverse(blog, “contains”);
Aber vielleicht ist der Graph-API doch etwas intuitiver?
… = post.Traverse(“partOf”);
Um einem Node Zugriff auf den “Beziehungsbaum” zu geben, müsste er dafür allerdings damit initialisiert worden sein. Hm… warum nicht? Es geht hier ja nicht um einen API/ein Datenmodell, dass in allen Anwendungsschichten und über Remoting-Grenzen hinweg funktionieren soll. Mapping ist notwendig. Die über den API zugänglichen Granulatkügelchen und Beziehungen müssen damit nur gefüllt werden können. Danach kann das objektorientierte Datenmodell allein stehen.
Semantikfreiheit
Das bringt mich zu einem letzten Punkt: Ich glaube immer mehr, dass im Persistenzmedium keine Semantik stecken sollte. Die ist ein Concern, der nicht dahin gehört. Ein Persistenzmedium ist ein Persistenzmedium ist ein Persistenzmedium. Es speichert Daten dauerhaft, so dass aus ihnen später wieder “Werkstücke” gefertigt werden können. Dafür sind in einem Datengranulat “Kügelchen” und Beziehungen festzuhalten. Fertig.
An einem Blog hängen viele Postings. Ein Posting hat viele Kommentare. Dieser Zusammenhang ist zu persistieren. Ob ein Blog-Objekt in einem bestimmten Kontext seine Postings in einer IList<> oder einem Dictionary<,> oder in einem Array halten will, ist nicht Sache des Persistenzmediums. Ein Mapping sorgt dafür, dass die vielen Postings in die kontextspezifische Collection eines Blogs kommen.
Das Persistenzmedium kennt nur atomare Granulatkügelchen mit einem oder mehreren atomaren Werten je Feld. Und es kennt bidirektionale benannte Beziehungen zwischen den Granulatkügelchen.
Das scheint mir die Grundanforderung an ein Datengranulat-Modell. Was in einem Objektmodell für die Verarbeitung “irgendwie verwoben ist”, wird für die Persistenz gründlich geshreddert. Nur so ist es möglich, es später für die Verarbeitung in anderen Kontexten möglichst einfach immer wieder anders zusammenzusetzen.
Der Unterschied zum relationalen Datenmodell besteht dabei darin, dass das relationalen Datenmodell kontextspezifische Schemata nahelegt. Datengranulat hingegen sieht immer gleich aus. Die Granulatkügelchen mögen unterschiedliche “Farben” und Größen haben, doch sie sind in einem immer gleich: es sind Atome ohne inhärente Beziehungen. Beziehungen sind extern zu ihnen und sehen ebenfalls immer gleich aus.
Jetzt die Frage: Wo ist eine Datengranulat-Persistenztechnologie? Dokumentendatenbanken sind es nicht. In ihnen können “Datensäcke” zwar hübsch gespeichert werden, doch für die Beziehungen sind Dokumente untauglich, glaube ich.
RDBMS wiederum könnten für Beziehungen ordentlich sein, aber schemalose “Datensäcke” sind nicht ihr Ding.
Ein ODBMS könnte hier hingegen passen. Das Granulat-Datenmodell ist ja universell und ändert sich nicht. Flexibilität ist nicht nötig. Da ODBMS für starre Datenmodelle geeignet sind und direkte Beziehungen effizient verwalten, könnte darauf eine Granulatdatenbank aufgesetzt werden.
Oder doch besser eine Graphendatenbank? Hm… Ich glaube, damit würde ich am ehesten mal spielen wollen.
Oder selbst bauen? Mal mit F# Infrastruktur basteln? Ein Entwicklertraum würde wahr… :-) Na, mal schauen. Erstmal drüber schlafen. Am Ende gehts ja zunächst nur um einen API. Wie es darunter aussieht, ist egal.