3

Click here to load reader

Versionsvielfalt von NoSQL-Dokumenten (windows.developer 8.2012)

  • Upload
    zuehlke

  • View
    505

  • Download
    0

Embed Size (px)

DESCRIPTION

Ein großer Vorteil vieler NoSQL-Datenbanken ist die Flexibilität des Datenformats. Doch wie lässt sich eine schemalose Dokumentenstruktur in ein typisiertes Klassensystem überführen und erweiterbar halten? Dieser Artikel diskutiert Praxiserfahrungen aus einem größeren Entwicklungsprojekt. Artikel im windows.developer 8.2012 von Christian Heger und Daniel Weber (beide auf Xing).

Citation preview

Page 1: Versionsvielfalt von NoSQL-Dokumenten (windows.developer 8.2012)

20

datenbanken . MongoDB

8.2012

von Christian Heger und Daniel Weber

Im Gegensatz zu einer relationalen Datenbank werden in der NoSQL-Datenbank MongoDB Daten als Doku-mente abgelegt, die in so genannten Collections orga-nisiert sind. Dokumente sind hierbei Strukturen, die Attribute enthalten und somit mit relationalen Tabellen zu vergleichen sind. Ein Dokument kann aber auch Ar-rays und weitere Unterdokumente enthalten. Die Do-kumente in einer einzigen Collection haben dabei nicht notwendigerweise die gleichen Eigenschaften, es gibt kein festes Schema. Obwohl technisch gesehen gar kein Zusammenhang zwischen den Dokumenten einer Coll-ection bestehen muss, macht es meist Sinn, gleichartige Dokumente in einer Collection mit einem sinnvollen Namen zusammenzufassen. Anderenfalls könnte man ja genauso gut nur eine einzige große Collection für al-les verwenden. Meist besitzen alle Dokumente in einer Collection dasselbe Format.

In .NET wiederum werden Daten in Objekten abgebil-det, die durch ihre Klasse typisiert sind. Um Daten aus MongoDB in eine .NET-Applikation einlesen beziehungs-

weise später wieder „wegschreiben“ zu können, muss also eine Umwandlung vom Dokument zum Objekt statt�n-den. Diese Umwandlung geschieht mithilfe der Serialisie-rung von Objekten durch den MongoDB-Treiber (mehr dazu im Kasten „Was tut der MongoDB-Treiber?“).

Ein Problem der Serialisierung stellt sich meist erst dann ein, wenn eine Applikation weiter entwickelt oder um-strukturiert wird. Neue Features ziehen meist neue Daten nach sich, die auch persistent abgelegt werden sollen. Da-mit passen bei naiver Implementierung die gespeicherten Dokumente nicht mehr zur aktualisierten Anwendung. Eine naheliegende, aus der klassischen SQL-Welt entlehn-te Lösung besteht darin, die Daten über ein Updateskript in die neue Dokumentenstruktur zu überführen. Bei einer großen Datenbank kann das aber einige Zeit dauern und verursacht hohe Last auf den Servern. Bei Anwendungen, die permanent verfügbar sein müssen, stellt dieser Ansatz deshalb ein echtes Problem dar.

Weiterhin kann es sein, dass auf einen Teil der Doku-mente nur selten zugegriffen wird. Als Beispiel denke man sich ein Dokument, das ein Nutzerpro�l in einem Online-shop abbildet. Dieses Dokument wird nur gelesen, wenn dieser spezielle Benutzer sich anmeldet, also eventuell nur alle paar Monate. Andererseits kann es sehr viele dieser Dokumente geben. Hier bietet es sich an, die Überfüh-rung in ein neues Format erst dann vorzunehmen, wenn das unbedingt erforderlich ist. Dafür muss man aber da-für sorgen, dass verschiedene Versionen eines Dokuments gelesen werden können. Dieses Vorgehen soll anhand ei-nes kleinen Beispiels illustriert werden.

Mehrere Versionen desselben DokumentsSehen wir uns eine Collection aus einer einfachen Ad-ressdatenbank an. Das ursprüngliche Format entspricht Listing 1. Um die einzelnen Adressinformationen zu ei-nem Typen zusammenzufassen, wurde das Dokumen-

Was tut der MongoDB-Treiber?

Im Gegensatz zu vielen anderen NoSQL-Datenbanken verwendet Mon-goDB keine REST-Schnittstelle, die Daten in JSON-Notation via HTTP überträgt. Aus Ef�zienzgründen wird eine TCP-Verbindung verwendet, und die Daten werden im BSON-(Binary-JSON-)Format übertragen. BSON ist auf schnelle Verarbeitung hin optimiert, zum Beispiel ist ein Integer im-mer 32 Bit lang und muss nicht aus einem String interpretiert werden wie bei JSON. In der Sprachregelung von MongoDB ist ein Treiber eine Client-bibliothek, mit der die Applikation auf MongoDB zugreift. Es gibt mehrere Implementierungen von MongoDB-Treibern für .NET. Dieser Artikel bezieht sich auf den „of�ziellen” Treiber, der von 10gen entwickelt wird.

MongoDB: Herausforderungen und Lösungsansätze für die .NET-Serialisierung

Versionsvielfalt von NoSQL-Dokumenten Ein großer Vorteil vieler NoSQL-Datenbanken ist die Flexibilität des Datenformats. Doch wie lässt sich eine schemalose Dokumentenstruktur in ein typisiertes Klassensystem überführen und erweiterbar halten? Dieser Artikel diskutiert Praxiserfahrungen aus einem größeren Entwicklungsprojekt.

Page 2: Versionsvielfalt von NoSQL-Dokumenten (windows.developer 8.2012)

21

MongoDB . datenbanken

www.windowsdeveloper.de8.2012

tenformat refaktoriert, sodass es dem Format in Listing 2 entspricht. Da die Dokumente in einer Collection keinem festen Schema unterworfen sind, können jetzt beide Versio-nen in derselben MongoDB Collection vorkommen. In einer C#-Applikation, die auf die Adressdatenbank zugreift, ist eine Klasse Person de�niert (Listing 3). Wie soll jetzt aber eine Abfrage auf Adressen in eine Liste von Objekten überführt werden, wenn jedes Dokument andere Eigenschaften aufweist? Wird versucht Dokumente in dem neuen For-mat einzulesen, tritt eine Ausnahme auf: Das Dokument enthält Felder, für die es keine Entsprechung im Objekt gibt. Das erkennt der Serializer als Fehler.

Zusätzliche Felder ignorierenDer einfachste Weg, alle Dokumente aus der Collection lesen zu können, besteht darin, unbekannte Felder zu ignorieren. Entscheidet man sich für diese Option, werden alle Eigenschaften verworfen, die nicht auf Objekte übertragen werden können. Dazu kann man auf der entsprechenden Klasse das BsonIgnoreExtraElements-Attribut setzen:

[BsonIgnoreExtraElements]public class Person{...

Nach dieser Änderung treten keine Ausnahmen mehr auf. Allerdings werden die Adress-daten nicht richtig aus Dokumenten im neuen Format gelesen. Solange die Applikation mit diesen zusätzlichen Daten nicht arbeitet, wäre dieses Verhalten ja noch akzeptabel. Wird ein so gelesenes Dokument allerdings gespeichert, gehen die zusätzlichen Eigen-schaften allerdings auch in der Datenbank verloren. Beim Speichern von Objekten ersetzt der MongoDB-Treiber nämlich das gesamte Dokument mit der passenden ID. Und da es kein festes Schema gibt, wird das Format des aktuell vorhandenen Objekts verwendet – und das kennt die zusätzlichen Felder nicht mehr.

Nicht nur AttributeNeben der Verwendung von Attributen gibt es die Möglichkeit, Serialisierungsoptionen bei der Initialisierung des MongoDB-Treibers in Code zu setzen. Auf diese Art tritt keine Vermischung von Domänenobjekten und Persistenzlogik auf.

Zusätzliche Felder auffangenAlle Felder, die nicht Eigenschaften der Klasse zugeordnet werden, können in der Form eines BsonDocument aufgefangen werden. Dazu de�niert man eine Eigenschaft vom Typ Bson-Document, die mit dem Attribut BsonExtraElements versehen wird. Der wichtigste Effekt ist, dass keine Daten aus dem Originaldokument verloren gehen, selbst wenn sich zusätzli-che und unbekannte Felder in dem Dokument be�nden. Eine verlustfreie Umwandlung vom Dokument zum Objekt und zurück wird ermöglicht.

Listing 2: Dokument in Version 2

{ "_id" : "mmustermann", "FirstName" : "Max", "LastName" : "Mustermann", "Address" : { "Street" : "Musterstr. 1", "ZipCode" : "12345", "City" : "Musterstadt", "Country" : "Germany" }}

Listing 3: C#-Klassepublic class Person{ public string Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Address { get; set; } public string ZipCode { get; set; } public string City { get; set; } public string Country { get; set; }}

Listing 1: Dokument in Version 1

{ "_id" : "mmustermann", "FirstName": "Max", "LastName" : "Mustermann", "Address" : "Musterstr. 1", "ZipCode" : "12345", "City" : "Musterstadt", "Country" : "Germany"}

Listing 4: BsonExtraElementspublic class Person { private string _city;

[BsonExtraElements] public BsonDocument CatchAll { get; set; }

private BsonDocument Address { get { var address = CatchAll.GetElement("Address"); return address.Value.AsBsonDocument; }

}

public string City { get { return _city ?? Address.GetElement("City").Value.AsString; } set { _city = value; } }// more properties ...}

Page 3: Versionsvielfalt von NoSQL-Dokumenten (windows.developer 8.2012)

22

datenbanken . MongoDB

8.2012

BsonDocument ist eine direkte Repräsentation eines MongoDB-Dokuments. Es ermöglicht den Zugriff auf die Felder eines Dokuments, ohne dass dieses in eine .NET-Klasse serialisiert wird. Das macht die Arbeit mit ihm recht unkomfortabel, aber �exibel. Das Beispiel in Listing 4 zeigt, wie ein BsonDocument verwendet wer-den kann, um im Bedarfsfall aus einer der beiden Versio-nen des Person-Dokuments auszulesen. Solange ein Feld City vorhanden ist, wird dieses direkt eingelesen. Sollte dieses Feld allerdings leer sein, wird auf das BsonDocu-ment zugegriffen, um die Eigenschaft des eingebetteten Address-Dokuments zu lesen.

Ein wesentlicher Nachteil bei dieser Lösung ist aller-dings, dass das Person-Objekt mit Code belastet wird, der spezi�sch für die Persistenz ist. Die Objekte werden kom-

plexer, und der Fokus verschiebt sich von der Darstellung eines Geschäftsobjekts zur Lösung datenbankbezogener Probleme. Besser wäre es, die Versionierung von Doku-menten in einer ausgelagerten Klasse zu behandeln.

Benutzerde�nierte SerializerIndem man einen angepassten Serializer für eine Klasse zur Verfügung stellt, kann man vollständig die Kontrolle darüber übernehmen, wie sich diese als Dokument dar-stellt. Um verschiedene Versionen von Dokumenten zu unterstützen, reicht es, die Deserialisierung anzupassen, also die Umwandlung des Dokuments zum Objekt. Auf dem umgekehrten Weg soll gerade keine Anpassung statt-�nden, da immer die aktuellste Version des Dokuments in die Datenbank geschrieben werden soll (Abb. 1).

Sobald auf ein Dokument mit der alten Struktur zu-gegriffen wird, kann der Serializer dafür sorgen, dass das Dokument in die neue Dokumentenstruktur über-führt wird. Der Serializer aktualisiert immer nur ein Dokument. Wird ein altes Dokument nie gespeichert, verbleibt es in seinem originalen Format. Es wird also innerhalb einer Collection verschiedenste Versionsstän-de geben. Ein Serializer muss mit jeder Version umgehen können. Am besten lässt sich das durch die Verkettung mehrerer Serializer erreichen, von denen jeder für eine bestimmte Dokumentenversion verantwortlich ist. Kommt eine neue Version hinzu, wird ein weiterer Schritt hinzugefügt.

Dem Serializer sind aber auch Grenzen gesetzt. Möchte man die Dokumentenstruktur so ändern, dass ein Doku-ment auf mehrere Collections aufgeteilt wird, lässt sich das durch einen Serializer nicht bewerkstelligen. Hier muss man die entsprechende Logik in ein Repository einbauen.

FazitMithilfe der Serialisierung des MongoDB-Treibers wird die Brücke zwischen der schemalosen NoSQL-Welt und der typisierten objektorientierten Welt geschlagen. Der gut kon�gurierbare Serializer erlaubt die einfache und gut kontrollierbare Überführung �exibler Dokumente in Objektstrukturen. Benutzerde�nierte Serializer (Lis-ting 5) bieten ein mächtiges Werkzeug, um komplexe Umformungen vorzunehmen.

Christian Heger ist Lead Software Architect bei der Zühlke Engi-neering GmbH. Dort baut er großartige Anwendungen mit agilen Teams. Er programmiert in C# und JavaScript. Sein Hauptinteres-se sind responsive und skalierbare Webanwendungen.

Daniel Weber (B. Eng.) arbeitet seit 2010 bei der Firma Zühlke Engineering GmbH als Software Engineer. Er arbeitet vorwiegend mit .NET-Technologien.

Beide arbeiten derzeit zusammen an einer Neuentwicklung eines Internet-marktplatzes, der MongoDB und ASP.NET MVC einsetzt.

Listing 5: Benutzerde�nierter Serializerpublic class MigratingCustomerSerializer : ICustomerSerializerHandler { public ICustomerSerializerHandler Next { get; set; } public Customer HandleObject(BsonDocument document) { BsonElement addressElement; if (!document.TryGetElement("Address", out addressElement)) { return new Person { _id = document.GetElement("_id").Value.ToString(), FirstName = document.GetElement("FirstName").Value.ToString(), LastName = document.GetElement("LastName").Value.ToString(), Address = new Address { Street = document.GetElement("Street").Value.ToString(), City = document.GetElement("City").Value.ToString(), Country = document.GetElement("Country").Value.ToString(), ZipCode = document.GetElement("ZipCode").Value.ToInt32(), } }; } return this.Next.HandleObject(document); }}

Abb. 1: Versions-umwand-

lung durch benutzer-

de�nierten Serializer