Follow my new blog

Posts mit dem Label Persistenz werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Persistenz werden angezeigt. Alle Posts anzeigen

Donnerstag, 22. April 2010

Geshreddert persistent – Gedanken zu einem Datengranulat

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:image

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):

image

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:

image

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:

  1. 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 ;-)
  2. 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.
  3. 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.

image

Etwas weniger bildhaft könnte das so aussehen:

image

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:

image

“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.

image

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.

Mittwoch, 21. April 2010

Datengranulat oder: Mapping als Lust statt Last

Anlässlich einer Diskussion um NoSql in Ayende Rahiens Blog mache ich mir Gedanken darum, was wir eigentlich von einem Persistenzmedium wollen (sollten). Lange haben wir es jetzt als gesetzt angesehen, dass Daten von Geschäftsanwendungen in relationalen Datenbanken gespeichert werden. Man tut das einfach so. Das ist der Default. Die Gründe dafür sind vielfältig. Oft ist schon eine relationale Datenbank vorhanden und es ist einfacher, eine neue Anwendung oder auf die aufzusetzen. Oder das RDBMS XYZ ist Unternehmensstandard, so dass auch bei der nächsten Anwendung XYZ zum Einsatz kommen muss. Oder Entwickler sind schlicht vertraut mit dem relationalen Datenmodell. Oder die sonstigen Tools um das relationale Datenmodell erlauben einfache Eingriffe in persistierte Daten an Anwendungen vorbei.

Gehupft wie gesprungen, Daten werden immer noch vor allem mit RDBMS verwaltet. Ist so. Aber ist das auch gut so?

image Die Diskussion um NoSql zeigt, dass es eine wachsende Gemeinde von Entwicklern gibt, die sagt, dass sei nicht gut so. RDBMS haben ihren Wert – doch der sollte von Anwendung zu Anwendung immer wieder neu festgestellt werden. Für manche Anwendungen sind RDBMS die passende Wahl, für andere nicht. So geht es der NoSql-Bewegung um Alternativen, nicht um Ersatz. RDBMS sollen und werden nicht einfach durch Dokumentendatenbanken o.ä. ersetzt. NoSql will nur dafür sorgen, dass Entwickler mehr als eine Option für die Persistenz haben. Das mag dann mal wie eine Rebellion gegen die RDBMS-Hersteller aussehen… am Ende ist´s aber nicht mehr als eine Ausbalancierung mit Gegengewichten.

Ayende Rahien bastelt nun auch an einer NoSql-Datenbank: RavenDB. Ein interessantes Projekt. Ich bin gespannt. Eine Dokumentendatenbank à la CouchDb oder MongoDb, aber in C# implementiert, tut der .NET-Plattform gut.

Nichtsdestotrotz hat mich sein Posting zu der Frage geführt: Was wollen wir denn eigentlich von einer Datenbank, von einem Persistenzmedium? Ein RDBMS ist nicht per se “gut”. Eine DocDB ist auch nicht per se “gut”. Dasselbe gilt für ODBMS, Graphendatenbanken, assoziative Datenbanken usw.

Warum sollten wir also das eine persistente Datenmodell dem anderen vorziehen? Zur Laufzeit gehen wir in jedem Fall nicht mit den persistenten Daten in ihrem Ursprungsformat um, sondern wünschen uns immer wieder Objekte. Nicht umsonst arbeiten wir uns seit Jahren am Thema O/R Mapping ab. Im Hauptspeicher wollen wir durch Objektgraphen navigieren. Wie die Daten auf der Platte liegen, ist uns letztlich egal.

Als das relationale Datenmodell entwickelt wurde, gab es keine Objektorientierung. Deshalb hat man gleich zum Datenmodell eine passende Sprache erfunden (SQL), mit der die Daten manipuliert/abgefragt werden können. Datenmodell und Sprache haben zueinander gepasst. Heute ist das anders: C# passt nicht zum relationalen Datenmodell. Deshalb gibt es O/R Mapper.

image Passt C# nun aber zum Dokumentendatenmodell? Wenn ich den Code in Ayendes Posting anschaue, dann lautet die Antwort für mich: nein. C# mag hier besser passen, aber es ist keine 100%ige Passgenauigkeit. RavenDB bietet zwar gleich einen objektorientierten API, der durchaus Ähnlichkeiten mit dem meines Lounge Repository hat. Doch wenn Sie genau hinschauen, dann gibt es merkwürdige invertierte Beziehungen: da wird einem Posting ein Blog zugewiesen, obwohl der naheliegende Gedanke doch wohl eher ist, dass ein Blog Postings enthält. Warum Ayende es so macht, können Sie in den Kommentaren zum Blogartikel lesen. Das ist schon verständlich. Doch es zeigt, dass auch zwischen Dokumentendatenbanken und Objektorientierung ein Impedanzunterschied ist, der durch Mapping überwunden werden will. Denn ein Objektmodell wie das im Artikel wollen Sie nicht einfach so in der Anwendung allerorten verwenden, denke ich. Dafür ist es zuwenig intuitiv mit seinem “Beziehungssystem”, das der Skalierbarkeit geschuldet sind.

Mapping ist unvermeidbar

Wie es aussieht, ist das objektorientiere Datenmodell immer irgendwie anders als ein persistentes. Immer existiert ein Impedanzunterschied. Oder? ODBMS versprechen, dass das nicht so sei. Stimmt auch. Mit db4o können Sie Objekte mit ihren Beziehungen “as is” speichern. Kein Mapping ist nötig. Ist das nicht der heilige Gral? Merkwürdig nur, dass die Branche den bisher nicht erkannt hat. Vielleicht ist er gar keiner. Oder liegt das nur an der Übermacht böser RDBMS-Hersteller, so dass das Wahreguteschöne keine Chance hat? Die Antwort ist eine Mischung aus allem, denke ich.

An dieser Stelle möchte ich mich aber auf einen Aspekt konzentrieren: die Abwesenheit eines Mappings. Die scheint mir nämlich ein Problem zu sein.

Ja, genau: das Problem, das ODBMS lösen, vereitelt ihren breiten Erfolg, glaube ich. ODBMS lösen das Mapping-Problem und graben sich damit das Wasser ab. Wie kann das sein? Eine Antwort ist nur möglich, wenn wir erneut die Frage stellen, wozu denn überhaupt Persistenz heute nötig sei?

Früher bzw. dort, wo Daten vor allem mit SQL verarbeitet werden, ist das persistente Datenmodell das Datenmodell, das einzige, das zentrale. Persistenz ist dann kaum zu unterscheiden von Verarbeitung. Mit RDBMS, SQL und Store Procs schlägt man also quasi zwei Fliegen mit einer Klappe.

Heute ist das nicht mehr nötig oder wir wollen es einfach nicht mehr. Das persistente Datenmodell verliert damit einen Zweck. Es muss nicht mehr der Verarbeitung dienen. Damit bricht im Grunde die mathematische Basis von RDBMS weg. Dass RDBMS mit ihrem Datenmodell so wunderbar dem relationalen Kalkül entsprechen, ist uninteressant. Datenbanken sollen heute vor allem eines: Daten persistieren. Fertig, aus, Applaus. Verarbeiten tun wir die Daten in unseren objektorientierten 3GL Hochsprachen. Und dabei sollte uns das Persistenzmedium am besten nicht im Wege stehen.

Jetzt der Trick: Wie wir Daten im Hauptspeicher halten, das ändert sich alle Nase lang. Oder aus unterschiedlichen Perspektiven (Stichwort Bounded Context) sehen dieselben Daten unterschiedlich aus. Wenn also ein ODBMS so wunderbar ein Objektmodell impedanzunterschiedlos gespeichert hat, dann ist das schön für den Augenblick und für den speichernden Kontext. Aber was ist morgen? Was macht ein anderer Kontext damit, der die Daten anders sehen möchte?

ODBMS fixieren ein Datenmodell im Persistenzmedium. Und das ist gut, wenn man das denn will. Wer sicher ist, dass sein Datenmodell so bleibt, wie es ist, der ist mit einem ODBMS wunderbar bedient. Ich möchte ODBMS also nicht abschaffen. Sie sollen zur Artenvielfalt der Persistenzmedien beitragen. Aber sind eben nicht der heilige Gral, sondern haben auch nur einen Bereich von Szenarien, in dem sie mit Gewinn eingesetzt werden sollten.

Durch den Frust, den der Impedanzunterschied zwischen OO-Datenmodell und relationalem in der Branche erzeugt hat, haben wir jedoch falsch eingeschätzt, was ganz breit das wahre Problem heutzutage mit persistenten Daten ist. Deshalb scheinen/schienen ODBMS so vorteilhaft. Ist nicht Mapping das Problem, von dem wir erlöst werden müssen?

Nein.

Mapping ist kein Problem, sondern unvermeidbar, gar eine Tugend.

Nicht immer, aber in vielen, vielen Fällen.

Denn das Problem, von dem wir erlöst werden müssen, ist das Problem des inflexiblen persistenten Datenmodells. Anwendungen leiden heute darunter, dass sie sich frühzeitig auf ein persistentes relationales Datenmodell festlegen müssen, dass einem objektorientierten entspricht. Dann verändert sich das objektorientierte Datenmodell und das relationale muss nachgeführt werden. O/R Mapper sollen das verbergen helfen… aber das klappt nicht so recht. Also pflegen wir zwei Datenmodelle und ein Mapping.

Bei ODBMS muss zwar nur ein Datenmodell gepflegt werden, aber die persistenten Daten sind sehr star. Sie enthalten ja direkte Objektbeziehungen, die morgen anders sein können.

Wir leiden also entweder unter zwei Datenmodellen + Mapping oder einem sehr starren Datenmodell.

NoSql tritt zwar an, mit seinen schemalosen Datenbanken das persistente Datenmodell etwas weniger starr zu machen. Doch ich bin im Zweifel, ob das schon reicht. Derzeit ist meine Vorstellung, dass wir noch radikaler denken sollten. Nicht in allen Persistenzszenarien natürlich; aber in denen, wo Datenmodelle im Fluss sind. Und das ist viel öfter der Fall, als wir wahr haben wollen.

ODBMS sind effizient. Wir sparen uns das Mapping. Der Preis dafür ist Inflexibilität. Das bedeutet für mich im Umkehrschluss: Wenn wir wahrhaft Flexibilität haben wollen, dann kommen wir ums Mapping nicht herum. Wir müssen sogar noch mehr und häufiger mappen als bisher.

Daten als Granulat

Kennen Sie Fabbing? Beim Fabbing wird – zumindest bei einem Verfahren – aus einem Granulat ein Gegenstand “gedruckt”. Fabbing ist “Drucken in 3D”. Wie der “Ausdruck” aussehen soll, definiert ein Modell. Das Material liegt als Granulat vor. Im Grunde ist das ähnlich wie beim Laserdrucker: Da liegt der Ausdruck als Text vor und wird auch Tonerstaubpartikeln “hergestellt”.

Tonerstaub und Fabbing-Granulat sind das Material, aus dem “Ausdrucke” nach immer wieder neuen Modellen produziert werden können. Sie sind die ultimativ flexible Grundsubstanz für “Ausdrucke”.

Wenn wir in unseren Anwendungen nun immer wieder die in-memory Datenmodelle verändern oder neue definieren, also Datenstrukturen ganz unterschiedlicher Form aus den selben Daten herstellen wollen, dann sollten wir anfangen, unsere persistenten Daten als Grundsubstanz anzusehen, denen wir auch eine ultimativ flexible Struktur geben müssen.

Wir brauchen eine Art persistentes Datengranulat, wenn wir hochflexibel bei unseren Laufzeitdatenmodellen sein wollen. Dessen persistentes Datenmodell ist dann natürlich ganz weit weg vom Laufzeitdatenmodell. Noch weiter als relationales oder dokumentenorientiertes. Zwischen Datengranulat und OO-Datenmodell müsste also noch mehr Mapping betrieben werden.

Doch das macht nichts. Das ist vielmehr der Trick an der Sache. Dass wir bereit sind für mehr Mapping ist die Bedingung für die Möglichkeit, eines hochflexiblen persistenten Datenmodells, ein Datengranulat.

Wie das aussehen soll? Keine Ahnung. Wie das dann auch noch skaliert? Keine Ahnung. Daran müsste man wohl mal forschen. Dass RDBMS oder NoSql heute ein solches Datengranulat aber schon bieten, bezweifle ich.

Sicher ist für mich auch, dass so ein Datengranulat einen Preis über das Mapping hinaus hat. Es wird wohl weniger performant und weniger skalierbar sein bzw. weitere Technologieentwicklung wie Mehrkernprozessoren oder Hochgeschwindigkeitsleitungen oder mehr Speicher einfach “aufsaugen”. Der Gewinn, der winkt, ist jedoch – glaube ich – enorm. Höchste Flexibilität und damit eine Befreiung von vergeblicher Planungssicherheit.

Bottom line: Solange wir Mapping zwischen persistentem Datenmodell und objektorientiertem Laufzeitdatenmodell noch als Problem ansehen, suchen wir die Lösung für die Datenpersistenz noch unter der Laterne statt dort, wo sie wirklich liegt. Die Notwendigkeit zum Mapping ist kein Bug. Sie liegt in der Natur der Sache, dass persistente Daten langlebig und weitreichend sind. Deshalb müssen sie flexibel sein. Sie müssen unterschiedlichen Herren dienen können. RDBMS mit dem relationalen Datenmodell haben das schon lange irgendwie geboten. Aber wie NoSql zeigt, ist das nicht genug. Entwickler wollen mehr Flexibilität. Also sollten wir über einen wirklichen Sprung in puncto Flexibilität nach vorn nachdenken. Wie könnte ein Datengranulat als Grundstoff für alle möglichen Datenmodell in Anwendungen aussehen?

Samstag, 29. August 2009

Entspannte Persistenz – The Lounge Repository

Relationale Datenbanken sind nicht die ganze Wahrheit für die Datenspeicherung. Das merken immer mehr Entwickler. Die Zahl der “alternativen Datenbanken” nimmt zu und die RDBMS-Frustrierten formieren sich schon: NOSQL ist der Schlachtruf.

image Einige Prominenz hat bei den “alternativen Datenbanken” nun CouchDB erlangt: eine schemalose Datenbank zur Speicherung von Dokumenten. Der API ist denkbar einfach, Queries sind natürlich auch möglich – aber für viele Anwendungen sind Dokumente eine unpassende Abstraktion ihrer Daten. Ein Kunde mit seinen Adressen mag noch als Dokument durchgehen. Doch wie ist es mit einem Kunden und seinen Rechnungen oder umgekehrt einer Rechnung mit ihrem Kunden? Kunden und Rechnungen als separate Dokumente anzusehen, funktioniert, doch sie müssen ja in Beziehung gesetzt werden. Beziehungen zwischen Dokumenten sind jedoch nicht natürlich. Das macht ja gerade die “Dokumentenhaftigkeit” aus, dass in einem Dokument Daten zusammengefasst sind, die eng zueinander gehören. Ein Dokument ist etwas Abgeschlossenes, es ist self-contained.

Dennoch übt die Einfachheit der Persistenz mit CouchDB Faszination aus. StupidDB versucht z.B. das Persistenz-Paradigma von CouchDB in die .NET-Welt zu bringen und noch “einen oben drauf zu setzen”: StupidDB ist bewusst serverlos, denn “[d]urch Replikation des Filesystems z.B. per Windows-DFS ist […] eine einfache Hochverfügbarkeit und Skalierbarkeit der Datenbasis” herstellbar.

So schön einfach die Persistenz mit StupidDB jedoch auch ist, sie leidet unter demselben Problem wie CouchDB. StupidDB verwaltet Dokumente, die nur als Ganzes gespeichert werden. Objektgraphen werden en bloc in eine Datei serialisiert.

Schemalosigkeit für Geschäftsanwendungen: The Lounge Repository

Motiviert durch diese Ansätze habe ich nun versucht, die Vorteile der Schemalosigkeit auch für Geschäftsanwendungen zu erschließen. Statt stupide auf dem Sofa abzuhängen, finde ich es jedoch zeitgemäß und auch geselliger, zu “loungen”. Deshalb habe ich meinen kleinen Open Source Persistenzframework “The Lounge Repository” genannt.

Der Name ist Programm:

  • “Lounge” soll anzeigen, dass es ein denkbar einfach und intuitiv zu benutzender Framework ist. Entspannt Objektgraphen persistieren: das soll The Lounge Repository möglich machen. Sie müssen das Persistenzmedium (hier: das Dateisystem) nicht mit einem Schema strukturieren, bevor Sie darin etwas speichern können.
  • “Repository” statt des verbreiteten Suffixes “DB” soll einen Hinweis auf die Art der Daten geben, die mit dem Framework verwaltet werden.  Repository ist ein Begriff aus dem Domain Driven Design (DDD) und bezeichnet eine Dienstleistung zur Speicherung von Entities: “Definition: A Repository is a mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects.”

Entspannt Entities persistieren, darum geht es bei The Lounge Repository. Es ist als generisches Repository für alle möglichen Arten von Entities gedacht.

Was ist eine Entity? Ein zustandsbehaftetes Objekt mit einer eigenen, speicherübergreifenden Identität. Sie identifiziert es im Hauptspeicher wie im Persistenzmedium.

Kunde und Rechnung sind naheliegende Entitäten für eine Faktura-Anwendung. Eine Rechnungsposition jedoch nicht, da sie nicht unabhängig von einer Rechnung existiert. Auf Kunden wie Rechnungen möchte man sicher direkt zugreifen, sie müssen unabhängig von einander adressierbar sein; auf Rechnungspositionen kommt man hingegen nur über ihre Rechnung. Rechnungspositionen sind im DDD-Jargon sog. Value Objects.

Wenn Sie Ihre Anwendung auf der Basis von DDD modellieren, dann kommen am Ende sicherlich auch Entities und Value Objects heraus, die Sie persistieren wollen. Entities sollen einzeln geladen und gespeichert werden oder in größeren Zusammenhängen, z.B. eine Rechnung zusammen mit ihrem Kunden. Solche Zusammenhänge (cluster) nennt DDD Aggregate.

image

Wie nun die Persistenz von Éntities bewerkstelligen? Sicherlich können Sie dafür ein RDBMS Ihrer Wahl heranziehen. ADO.NET macht es dann möglich. Oder Sie machen es sich schon etwas einfacher und benutzen einen O/R-Mapper Ihres Geschmacks – von EntityFramework über Open Access und LLBLGENPRO bis zu NHibernate. Aber ich versichere Ihnen, wenn Sie nicht sattelfest mit einem dieser Persistenzframeworks sind, dann werden Sie eine rechte Mühe haben, Ihre Entities zu persistieren. Persistor.Net verspricht zwar, Ihnen einige dieser O/R-Mapping-Mühen abzunehmen, aber auch er setzt auf ein RDBMS, das es dann zu verwalten gilt.

Entity Graphen im Dateisystem speichern

CouchDB & Co. haben es deshalb auch leicht gehabt, bei der Usability zu punkten. Ihre APIs sind allemal “für die schnellere Persistenz zwischendurch” sehr schön einfach. Nur leider passen sie, wie schon erwähnt, nicht so gut zum Datenmodell er üblichen Geschäftsanwendungen. Objektgraphen mit vielen Entities (Aggregate) lassen sich nicht wirklich als Dokument beschreiben. Denn wenn dieselbe Entity in mehreren solchen Aggregaten vorkommt, wird sie in mehreren Dokumenten persistiert. Sie verliert damit ihre zweckstiftende Eigenschaft, ihre Identität, ihre Eindeutigkeit.

image

So sieht Ihr schöner Entity Graph aus, wenn CouchDB oder StupidDB ihn gespeichert haben. Alles ist zu einem Dokumentenganzen zusammengefasst.

The Lounge Repository macht es hingegen anders! Jede Entity wird hier separat in einer Datei gespeichert, egal wie tief eingeschachtelt sie in einem Entity Graphen ist. Das funktioniert natürlich auch mit zyklischen Referenzen:

image

The Lounge Repository erhält die logische Identität durch physische Separierung. Das unterscheidet es wesentlich von StupidDB.

Der Lounge Repository API

The Lounge Repository ist zunächst eine Fingerübung (oder eine umfangreichere Code Kata, wenn Sie so wollen). Mit dem Lounge Repository will ich also zwei Fliegen mit einer Klappe schlagen: Ich möchte ein Werkzeug haben, mit dem ich meine Gedanken zum Thema Schemalosigkeit praktisch ausprobieren kann. Und ich möchte in kontrollierter Umgebung meinen Programmiersüchten nachgehen können ;-) Denn wer würde leugnen wollen, dass die Programmierung von Infrastruktur nicht süchtig macht? Im kleinen Freizeitrahmen ist das aber genauso wenig schlimm, wie ein gelegentliches Bierchen am Abend oder eine Zigarette alle Jahre wieder auf der Wies´n. In den endlich-clean.net Entzug muss ich deshalb jedenfalls noch nicht. Allemal, weil ich mich bemüht habe, den Lounge Repository Code clean zu halten. Doch Vorsicht vor Infrastrukturprogrammierung in Ihren Projekten!

Doch jetzt weiter zu Konkretem, zum Code. Wie sieht der API des Lounge Repository aus? Ein Hello-World-Beispiel zeigt das Grundsätzliche in wenigen Zeilen. Laden Sie den Quellcode von CodePlex herunter und machen Sie mit. Das geht mit einem SVN-Client wie Tortoise ganz schnell. Die Quellen enthalten auch eine Projektmappe mit kleinen Beispielen.

Hier nun ein Beispielprojekt, wie Sie es aufsetzen können, wenn Sie die Lounge Repository Quellen mittels deren Projektmappe einmal übersetzt haben. Es stehen dann im globalen bin-Verzeichnis des Quellbaumes die Assemblies des Frameworks bereit:

image

Davon binden Sie zunächst aber nur zwei ein: LoungeRepo.Core und LoungeRepo.Contracts:

image

Mehr ist als Vorbereitung nicht nötig. The Lounge Repository kennt keine Datenbankdateien und hat (noch) keinen Serverprozess. Es ist eine experimentelle “embedded database”.

Und jetzt der Hello-World-Code:

    1 using System;

    2 using LoungeRepo.Contracts.Core;

    3 using LoungeRepo.Core;

    4 

    5 namespace BlogSample

    6 {

    7     class Program

    8     {

    9         static void Main()

   10         {

   11             using(ILoungeRepository repo = new LoungeRepository())

   12             {

   13                 repo.Store("hello, world!", "1");

   14 

   15                 string greeting = repo.Load<string>("1");

   16                 Console.WriteLine(greeting);

   17             }

   18         }

   19     }

   20 }

Mit Store() speichern Sie Entities, mit Load() laden Sie sie wieder. So einfach ist das mit der Persistenz.

Eine Entity ist jedes Objekt, dem Sie dieses Privileg zugestehen möchten. Sie müssen die Klassen zu persistierender Objekte nicht mit einem bestimmten Attribut kennzeichnen oder von bestimmten Klassen ableiten. Eine Zeichenkette kann genauso gut wie ein Kunde-Objekt eine Entity sein:

    5 namespace BlogSample

    6 {

    7     class Kunde

    8     {

    9         public string Name { get; set; }   

   10     }

   11 

   12     class Program

   13     {

   14         static void Main()

   15         {

   16             using(ILoungeRepository repo = new LoungeRepository())

   17             {

   18                 Kunde k = new Kunde {Name="Peter"};

   19                 repo.Store(k, "2");

   20 

   21                 k = repo.Load<Kunde>("2");

   22                 Console.WriteLine(k.Name);

   23             }

   24         }

   25     }

   26 }

Wichtig ist, dass jede Entity auch wirklich eine Identität hat. Die ist beim Speichern und Laden wichtig. Denn aus ihr “berechnet” das Repository den Namen der Datei, in der es die Entität speichert bzw. aus der es sie lädt.

Ein string oder das obige Kundenobjekt haben keine von ihrer Hauptspeicheradresse unabhängige Identität. Also muss der Code explizit eine beim Speichern angeben.

Die Identität einer Entity besteht aus zwei Teilen: einer Id (eine Zeichenkette Ihrer Wahl) und einer optionalen Partition (ebenfalls eine Zeichenkette Ihrer Wahl).

Partitionen unterteilen den “Persistenzraum” (hier: das Dateisystem) und ermöglichen auf lange Sicht eine Lastverteilung. Entities einer bestimmten Partition könnten von dedizierten Servern verwaltet werden. Auch wenn das Zukunftsmusik ist, habe ich mir gedacht, das grundlegende Konzept der Partitionierung schon jetzt mit in die Funktionalität aufzunehmen. Das mag ein wenig YAGNI sein… aber was soll´s? ;-)

Innerhalb einer Parition muss dann die Id eindeutig sein. Zusammen ergeben sie die Identität, die im “Persistenzraum” eindeutig ist. Ist keine Partition definiert, nimmt das Lounge Repository eine default Partition an.

Wenn Sie nicht wissen, was Sie als Partition angeben sollen, dann lassen Sie sie aus – oder wählen Sie z.B. den Klassennamen einer Entity als Partitionsnamen:

   12 class Rechnung

   13 {

   14     public string Rechnungsnummer;

   15     public Kunde Empfänger;

   16 }

   17 

   18 class Program

   19 {

   20     static void Main()

   21     {

   22         using(ILoungeRepository repo = new LoungeRepository())

   23         {

   24             Kunde k = new Kunde {Name="Maria"};

   25             Rechnung r = new Rechnung

   26                             {

   27                                 Rechnungsnummer = "090829-1",

   28                                 Empfänger = k

   29                             };

   30             repo.Store(r, "3", "Rechnung");

   31 

   32             r = repo.Load<Rechnung>("3", "Rechnung");

   33             Console.WriteLine("#{0} für {1}",

   34                         r.Rechnungsnummer,

   35                         r.Empfänger.Name);

   36         }

   37     }

   38 }

Aber nicht nur die Partition “Rechnung” für die Rechnung-Entity ist bemerkenswert an diesem Stück Code. Bitte beachten Sie auch folgendes:

  • Die Kundin Maria wurde natürlich zusammen mit der Rechnung persistiert.
  • Beim Laden der Rechnung wurde die Empfängerin natürlich auch wieder mit geladen.
  • In diesem Beispiel gibt es nur eine Entity: die Rechnung. Das Kunde-Objekt hat von sich aus keine Identität und wurde nicht ausdrücklich als Entity gespeichert.

Das Repository sieht jetzt so aus:

image 

Die Entities aus den vorangehenden Beispielen hatten keine eigene Partition und wurden daher in der default Partition gespeichert. Entity “3”, die Rechnung, steht in der explizit angegebenen Partition “Rechnung”.

Und wo ist die Kundin Maria? Sie steckt in der Rechnung-Entity mit Namen 3.entity, weil ihr Kunde-Objekt ja nicht als Entity gespeichert wurde. Das ist verständlich, oder? Schön ist es aber nicht. Sie wollen ja nicht alle Entity-Objekte explizit speichern müssen. Außerdem würde das nichts nützen, denn selbst wenn das Objekt zu Kundin Maria als Entity gespeichert worden wäre, würde das Repository das nicht merken während der Speicherung der Rechnung. Die “Entitätshaftigkeit” ist einem Kunde-Objekt ja nicht anzusehen. Bisher.

Um Entities nicht mit expliziter Identitätsangabe speichern zu müssen und auch in Entity Graphen erkennbar zu machen, können Sie sie das Interface ILoungeRepoEntityIdentity implementieren lassen. Das definiert nur zwei Properties: Id und Partition. Es trägt also nicht dick auf Ihre Domänenobjekte auf. Damit wird dann das kleine Szenario wirklich intuitiv:

    7 class Kunde : ILoungeRepoEntityIdentity

    8 {

    9     #region Implementation of ILoungeRepoEntityIdentity

   10     public string Id { get; set; }

   11     public string Partition { get { return "Kunden"; } }

   12     #endregion

   13 

   14     public string Name { get; set; }

   15 }

   16 

   17 

   18 class Rechnung : ILoungeRepoEntityIdentity

   19 {

   20     #region Implementation of ILoungeRepoEntityIdentity

   21     public string Id { get { return this.Rechnungsnummer; } }

   22     public string Partition { get { return "Rechnungen"; } }

   23     #endregion

   24 

   25     public string Rechnungsnummer;

   26     public Kunde Empfänger;

   27 }

   28 

   29 

   30 class Program

   31 {

   32     static void Main()

   33     {

   34         using(ILoungeRepository repo = new LoungeRepository())

   35         {

   36             Kunde k = new Kunde {Id="4", Name="Maria"};

   37             Rechnung r = new Rechnung

   38                             {

   39                                 Rechnungsnummer = "090829-1",

   40                                 Empfänger = k

   41                             };

   42             repo.Store(r);

   43 

   44             r = repo.Load<Rechnung>("090829-1", "Rechnungen");

   45             Console.WriteLine("#{0} für {1}",

   46                         r.Rechnungsnummer,

   47                         r.Empfänger.Name);

   48 

   49             k = repo.Load<Kunde>("4", "Kunden");

   50             Console.WriteLine(k.Name);

   51         }

   52     }

   53 }

Kunde und Rechnung implementieren nun das ILoungeRepoEntityIdentity Interface und liefern dem Repository darüber ihre Identitäten. Den Kunden habe ich zur Demonstration so ausgelegt, dass ihm die Id bei Erzeugung zugewiesen werden muss, die Rechnung entnimmt sie ihrer Rechnungsnummer. Beide enthalten jedoch eine fest verdrahtete Partition.

Die Rechnung wird wie erwartet auch jetzt wieder mit ihrem Kunden geladen, darüber hinaus kann der Code jedoch auf den Kunden auch direkt zugreifen, wie Zeile 49 zeigt.

Das ist im Grunde alles, was es zum Laden und Speichern zu sagen gibt. Sie müssen keine Vorbereitungen treffen, aber Entities sollten gekennzeichnet sein. Alle nicht gekennzeichneten Objekte sind für das Repository Value Objects.

Objektgraphen, d.h. Objekthierarchien und –netzwerke – auch solche mit Zyklen – werden korrekt gespeichert/geladen, d.h. Entitäten wandern in je eigene Dateien. Wie Sie Objektverweise aufbauen, ist Ihnen überlassen. Sie können einzelne Referenzen halten wie die Rechnung auf ihren Empfänger. Oder Sie benutzen Arrays oder Collections, um mehrere Referenzen zu verwalten.

The Lounge Repository persistiert alle Felder der Objekte, die ihm zur Speicherung übergeben werden. Immer. Es findet (derzeit) kein change tracking statt. Wollen Sie ein Feld ausschließen, dann setzen Sie darüber das [NonSerialized] Attribut, das Sie von der .NET-Serialisierung kennen.

Dass Sie Entitäten auch löschen können, ist selbstverständlich. Rufen Sie Delete() auf dem LoungeRepository unter Angabe der Identität auf.

Zum Schluss bleibt nur noch eine Frage: Kann man eigentlich auch Entitäten durch Queries ermitteln? Ja, man kann. Das Lounge Repository sammelt alle Entitäten eines Typs in einem sog. Extent. Das ist nichts weiter als eine lange Liste von Objekten, die das Repository als IEnumerable<T> anbietet. Deshalb können Sie darauf mit Linq in gewohnter Weise zugreifen:

   33 static void Main()

   34 {

   35     using(ILoungeRepository repo = new LoungeRepository())

   36     {

   37         Kunde k = new Kunde {Id="4", Name="Maria"};

   38         Rechnung r = new Rechnung

   39                             {

   40                                 Rechnungsnummer = "090829-1",

   41                                 Empfänger = k

   42                             };

   43         repo.Store(r);

   44 

   45         r = new Rechnung

   46                     {

   47                         Rechnungsnummer = "090715-2",

   48                         Empfänger = k

   49                     };

   50         repo.Store(r);

   51 

   52         k = new Kunde { Id = "5", Name = "Dennis" };

   53         r = new Rechnung

   54                     {

   55                         Rechnungsnummer = "090803-3",

   56                         Empfänger = k

   57                     };

   58         repo.Store(r);

   59 

   60 

   61         var mariasRechnungen =

   62             from rg in repo.GetExtent<Rechnung>()

   63                 where rg.Empfänger.Name == "Maria"

   64                 select rg;

   65 

   66 

   67         foreach (Rechnung mariasRg in mariasRechnungen)

   68             Console.WriteLine("#{0} für {1}",

   69                 mariasRg.Rechnungsnummer,

   70                 mariasRg.Empfänger.Name);

   71     }

   72 }

Die Zeilen 37 bis 58 bauen eine kleine Datenbasis an persistenten Entities auf. Und die Zeilen 61 bis 64 fragen sie mit einer Linq-Query ab. repo.GetExtent<T>() liefert dafür die Grundlage in Form einer Liste aller Rechnung-Entities, die geladen oder gespeichert wurden.

Hier liegt z.Z. noch eine Begrenzung des Lounge Repository: Extents enthalten zunächst nur Objekte, die das Repository “gesehen” hat. Objekte, die auf der Platte im Repository liegen, aber vom Repository weder direkt oder indirekt geladen wurden, sind (noch) nicht in dessen Cache enthalten und tauchen daher nicht im Extent auf.

Das können Sie jedoch ausbügeln, indem Sie zu Beginn einer Sitzung den internen Cache mit allen persistenten Entities populieren. Ja, so laden Sie zwar die ganze Datenbank in den Hauptspeicher, aber das macht nichts. The Lounge Repository ist (zunächst) genau für solche Szenarien gedacht, in denen Sie eben nicht Gigabytes an Daten verwalten. Selbst einige Hundert Megabytes in den Hauptspeicher zu laden auf einem 4 GB Laptop sollte allerdings den Kohl nicht fett machen.

Für einen solchen sog. “prefetch” binden Sie einfach die Assembly LoungeRepo.Core.Extensions ein und importieren Sie den gleichnamigen Namensraum. Dann sind alle Entities über ihre Extents zu erreichen:

    5 using LoungeRepo.Core.Extensions;

    6 

    7 namespace BlogSample

    8 {

    9     …

   32     class Program

   33     {

   34         static void Main()

   35         {

   36             using(ILoungeRepository repo = new LoungeRepository())

   37             {

   38                 repo.PrefetchAllEntities();

   39                 …

   63                 var mariasRechnungen =

   64                     from rg in repo.GetExtent<Rechnung>()

   65                         where rg.Empfänger.Name == "Maria"

   66                         select rg;

Ausblick

The Lounge Repository ist “a work in progress”. Ich habe mir damit eine Spielwiese angelegt, auf der ich Ideen zum schemalosen Umgang mit Daten und anderem ausprobieren kann. Das Projekt ist Open Source und Sie finden es bei CodePlex in seiner vollen “clean beauty”: http://loungerepo.codeplex.com/

Laden Sie den Quellcode runter und spielen Sie damit. Wenn Sie Fragen oder Einfälle haben, lassen Sie uns bei CodePlex darüber diskutieren. In der Projektmappe finden Sie auch eine kleine Aufgabenliste, die ich führe. Da sehen Sie, dass noch einiges zu tun ist am Lounge Repository. Und auch darüber hinaus habe ich schon Ideen, z.B. wie ein solche Repository verteilt und asynchron betrieben werden kann.

Einstweilen mag The Lounge Repository Datenbanken wie SQL Server oder selbst CouchDB nicht ersetzen. Aber ich würde mich freuen, wenn in ihm ein Keim läge, der es in einigen Szenarien zu einer Alternative zum default RDBMS machte. Prototypen, kleine Anwendungen… dort, wo Sie Entitäten identifizieren und “schnell mal persistieren wollen” ohne Schemaaltlasten, hat The Lounge Repository (bzw. eine der anderen “alternativen Datenbanken”) es sicher verdient, berücksichtigt zu werden.

Ich bin gespannt auf Ihr Feedback.