Der Programmierer bei der Arbeit

In Richtung NoSQL mit MongoDB

Ted Neward

Beispielcode herunterladen.

Seit etwa zehn Jahren, ungefähr seit der Ankündigung von Microsoft .NET Framework im Jahr 2000 und der ersten Veröffentlichung im Jahr 2002, versuchen .NET-Entwickler all der Neuerungen von Microsoft Herr zu werden. Und als wenn das noch nicht genug wäre, hat die „Community“ (also Entwickler, die .NET täglich verwenden, sowie andere Entwickler) damit begonnen, die von Microsoft nicht abgedeckten Löcher zu füllen – oder einfach Chaos und Verwirrung zu stiften (Ihre Entscheidung).

Eine dieser Neuerungen, die von der Community nicht unter Federführung von Microsoft entwickelt wurde, ist die NoSQL-Bewegung. Dies ist eine Gruppe von Entwicklern, die offen die Idee in Frage stellt, dass alle Dateien in einer Form von relationalem Datenbanksystem gespeichert werden sollen und müssen. Tabellen, Zeilen, Spalten, Primärschlüssel, Fremdschlüsseleinschränkungen und Auseinandersetzungen über Nullen und darüber, ob ein Fremdschlüssel natürlich sein sollte, sind nicht beängstigend?

In diesem und den folgenden Artikeln werden die wichtigsten Tools untersucht, die von der NoSQL-Bewegung empfohlen werden: MongoDB, dessen Name laut MongoDB-Website vom englischen Wort „humongous“ (auf Deutsch „riesig“) stammt (und das habe ich mir nicht ausgedacht). Fast alles im Zusammenhang mit MongoDB wird behandelt: Installieren, Untersuchen und Arbeiten mit MongoDB über .NET Framework, einschließlich der angebotenen LINQ-Unterstützung, Verwenden von MongoDB über andere Umgebungen (Desktop- und Webanwendungen und -dienste) sowie Einrichten von MongoDB, sodass Windows-Administratoren in der Produktion Sie nicht symbolisch verbrennen.

Problem (oder warum betrifft es mich wieder?)

Bevor wir in die Details von MongoDB einsteigen, ist die Frage erlaubt, warum .NET Framework-Entwickler die nächste halbe Stunde ihres Lebens dafür opfern sollten, diesen Artikel zu lesen und auf ihren Laptops nachzuvollziehen. Immerhin ist SQL Server in einer kostenlosen und verteilbaren Express-Edition erhältlich, die eine einfachere Datenspeicheroption bietet als die herkömmlichen unternehmens- oder datencentergebundenen relationalen Datenbanken. Zudem gibt es bestimmt zahlreiche Tools und Bibliotheken, die einen einfacheren Zugriff ermöglichen, darunter LINQ und Entity Framework von Microsoft.

Das Problem ist, dass die Stärke des relationalen Modells, also das relationale Modell selbst, gleichzeitig auch seine größte Schwäche. Die meisten Entwickler, ob nun für .NET, Java oder etwas Anderes, können nach einigen Jahren Erfahrung in unangenehmen Details beschreiben, dass eben doch nicht alles gut in gleichmäßige Modelle aus Tabellen/Zeilen/Spalten passt. Der Versuch, hierarchische Daten zu modellieren, kann auch sehr erfahrene Entwickler in den Wahnsinn treiben. Deshalb hat Joe Celko das Buch „SQL for Smarties, Third Edition“ (Morgan-Kaufmann, 2005) sogar nur über das Konzept, hierarchische Daten in einem relationalen Modell zu modellieren, geschrieben. Wenn Sie dann noch die Tatsache ergänzen, dass relationale Datenbanken eine nicht flexible Struktur für die Daten annehmen (das Datenbankschema), wird der Versuch, kurzfristige Erweiterungen der Daten zu unterstützen, schwierig. (Bitte einmal melden: Wie viele von Ihnen arbeiten mit Datenbanken, die eine Spalte für Notizen aufweisen, oder besser noch Notiz1, Notiz2, Notiz3 …?).

Niemand in der NoSQL-Bewegung würde behaupten, dass das relationale Modell keine Stärken hätte oder dass die relationalen Datenbanken verschwinden würden, es ist aber in den letzten beiden Jahrzehnten eine elementare Tatsache im Leben eines Entwicklers, dass häufig Daten in relationalen Datenbanken gespeichert wurden, die von Natur aus nicht relational sind (manchmal nicht einmal ansatzweise).

Die dokumentorientierte Datenbank speichert Dokumente (eng verknüpfte Datensammlungen, die im Allgemeinen mit anderen Datenelementen im System nicht verbunden sind) anstelle von Relationen. Blogeinträge in einem Blogsystem sind beispielsweise vollständig unzusammenhängend, und auch wenn ein Eintrag auf einen anderen verweist, ist die Verbindung meist ein Hyperlink, der vom Browser des Benutzers aufgelöst wird und nicht intern. Kommentare zu einem Blogeintrag beziehen sich nur auf diesen Blogeintrag, und nur selten möchten die Benutzer die Aggregation aller Kommentare ansehen, unabhängig vom Eintrag, den sie kommentieren.

Darüber hinaus sind dokumentorientierte Datenbanken besonders für Umgebungen mit hoher Leistung oder vielen gleichzeitigen Zugriffen geeignet. MongoDB ist speziell auf hohe Leistung zugeschnitten, das verwandte Produkt CouchDB zielt dagegen auf Szenarien mit vielen gleichzeitigen Zugriffen ab. Beide verzichten auf jede Art von Unterstützung für Transaktionen mit mehreren Objekten. Obwohl sie also die parallele Bearbeitung eines Objekts in einer Datenbank unterstützen, bleibt bei dem Versuch, mehr als ein Objekt zur Zeit zu ändern, nur ein kleines Zeitfenster, in dem diese Änderungen kurz angezeigt werden. Dokumente werden atomarisch aktualisiert, es gibt aber kein Konzept einer Transaktion, die die Aktualisierung mehrerer Dokumente abdeckt. Dies bedeutet nicht, dass MongoDB nicht über Dauerhaftigkeit verfügt, es bedeutet nur, dass die MongoDB-Instanz einen Stromausfall nicht so gut verkraftet wie eine SQL Server-Instanz. In Systemen, für die die Eigenschaften Atomarität, Konsistenz, Isolation, Dauerhaftigkeit (Atomicity, Consistency, Isolation and Durability, ACID) erforderlich sind, sollten besser herkömmliche relationale Datenbanksysteme eingesetzt werden. Unternehmenswichtige Daten werden also in naher Zukunft nicht in einer MongoDB-Instanz gespeichert werden, höchstens vielleicht, um die Daten auf einem Webserver zu replizieren oder zwischenzuspeichern.

Im Allgemeinen ist MongoDB gut für Anwendungen und Komponenten geeignet, die Daten speichern müssen, auf die schnell zugegriffen werden kann und die häufig verwendet werden. Websiteanalysen, Benutzereinstellungen und jedes System, in dem die Daten nicht komplett strukturiert sind und von der Struktur her flexibel sein müssen, bieten sich für MongoDB an. Dies bedeutet nicht, dass MongoDB nicht vollständig darauf vorbereitet ist, als primärer Datenspeicher für betriebliche Daten zu dienen. Es bedeutet lediglich, dass MongoDB in Bereichen gut eingesetzt werden kann, die für herkömmliche RDBMS nicht so gut geeignet sind, sowie für eine Reihe von Bereichen, für die beide passen.

Erste Schritte

Wie bereits erwähnt, ist MongoDB ein Open-Source-Softwarepaket, dass problemlos von der MongoDB-Website mongodb.com heruntergeladen werden kann. Es sollte ausreichen, die Website in einem Browser zu öffnen, um die Links zu den herunterladbaren Binärdateien für Windows zu finden. Suchen Sie auf der rechten Seite nach den Downloadlinks. Alternativ können Sie auch diese direkten Links nutzen: https://www.mongodb.com/lp/download/mongodb-enterprise. Als der Artikel geschrieben wurde, war die stabile Version die Version 1.2.4. Es ist einfach nur eine gebündelte ZIP-Datei. Die Installation ist als vergleichsweise lächerlich einfach: Entzippen Sie einfach den Inhalt an den gewünschten Ort.

Im Ernst, das wars.

Die ZIP-Datei enthält drei Verzeichnisse: „bin“, „include“ und „lib“. Nur das Verzeichnis „bin“ ist interessant. Es enthält acht ausführbare Dateien. Keine anderen Abhängigkeiten zwischen Binärdateien (oder der Laufzeit) sind notwendig. Tatsächlich sind im Moment nur zwei der ausführbaren Dateien von Interesse. Das sind „mongod.exe“, der MongoDB-Datenbankprozess, und „mongo.exe“, der Befehlszeilen-Shellclient, der normalerweise wie der alte Befehlszeilen-Shellclient „isql.exe“ von SQL Server verwendet wird. Durchsuchen Sie die Daten direkt, und führen Sie Verwaltungsaufgaben aus, um die Installation zu überprüfen.

Sie können die Installation überprüfen, indem Sie „mongod“ über einen Befehlszeilenclient ausführen. Normalerweise möchte MongoDB Daten im standardmäßigen Dateisystempfad „c:\data\db“ speichern. Sie können den Pfad jedoch mit einer Textdatei konfigurieren, die mit dem Namen an der Befehlszeile mit „--config“ übergeben wird. Vorausgesetzt, dass ein Unterverzeichnis namens „db“ überall da vorhanden ist, wo „mongod“ gestartet wird, kann die Funktionsfähigkeit überprüft werden wie in Abbildung 1 veranschaulicht.

image: Firing up Mongod.exe to Verify Successful Installation

Abbildung 1 Ausführen von „Mongod.exe“ zur Überprüfung der Installation

Ist das Verzeichnis nicht vorhanden, wird es von MongoDB nicht erstellt. Auf meinem Windows 7-Computer wird beim Start von MongoDB das übliche Dialogfeld angezeigt, dass die Anwendung einen Port öffnen möchte. Stellen Sie sicher, dass der Port (standardmäßig 27017) verfügbar ist, andernfalls ist es im besten Falle schwierig, eine Verbindung herzustellen. (Weitere Informationen dazu bekommen Sie in einem folgenden Artikel, wenn der Einsatz von MongoDB in einer Produktionsumgebung erläutert wird.)

Wenn der Server ausgeführt wird, kann er ganz leicht mit der Shell verbunden werden: Die Anwendung „mongo.exe“ startet eine Befehlszeilenumgebung, die die direkte Interaktion mit dem Server ermöglicht, wie in Abbildung 2 veranschaulicht.

image: Mongo.exe Launches a Command-Line Environment that Allows Direct Interaction with the Server

Abbildung 2 „Mongo.exe“ startet eine Befehlszeilenumgebung für die direkte Interaktion mit dem Server

Standardmäßig stellt die Shell eine Verbindung mit der Testdatenbank her. Da hier nur die Funktionsfähigkeit überprüft werden soll, ist dies ausreichend. Jetzt ist es natürlich recht einfach, einige Beispieldaten zu erstellen, um mit MongoDB zu arbeiten, beispielsweise ein schnelles Objekt, mit dem eine Person beschrieben wird. Es ist ein kurzer Einblick, wie MongoDB Daten zum Start anzeigt, wie wir in Abbildung 3 sehen.

image: Creating Sample Data

Abbildung 3 Erstellen von Beispieldaten

Im Wesentlichen nutzt MongoDB JavaScript Object Notation (JSON) als Datennotation, was sowohl die Flexibilität und die Art der Interaktion mit Clients erklärt. Intern speichert MongoDB Daten in BSON, ein binäre Obermenge von JSON, um die Speicherung und Indizierung zu vereinfachen. JSON bleibt jedoch das bevorzugte Ein- und Ausgabeformat für MongoDB und ist normalerweise auch das dokumentierte Format, das für MongoDB-Websites und -Wikis verwendet wird. Wenn Sie mit JSON nicht vertraut sind, sollten Sie sich damit beschäftigen, bevor Sie MongoDB intensiv nutzen. Sehen Sie sich einfach einmal das Verzeichnis an, in dem Daten von „mongod“ gespeichert werden, und Sie werden sehen, dass eine Reihe von Dateien mit dem Namen „test“ jetzt enthalten sind.

Genug gespielt, jetzt schreiben wir Code. Die Shell wird einfach mit dem Befehl „exit“ beendet. Um den Server herunterzufahren, müssen Sie in dem Fenster nur STRG+C drücken oder das Fenster schließen. Der Server erfasst das Signal zum Schließen, und fährt alles ordnungsgemäß herunter, bevor der Prozess beendet wird.

Der Server für MongoDB (und die Shell, auch wenn sie das kleinere Problem ist) ist als systemeigene C++-Anwendung geschrieben. Kennen Sie die noch? Für den Zugriff darauf ist also eine Form von .NET Framework-Treiber erforderlich, mit dem eine Verbindung über den offenen Socket hergestellt werden kann, um Befehle und Daten einzuspeisen. Zum Bereitstellungspaket von MongoDB gehört kein .NET Framework-Treiber, zum Glück hat aber die Community einen zur Verfügung gestellt. In diesem Fall ist die Community ein Entwickler namens Sam Corder, der einen .NET Framework-Treiber und LINQ-Unterstützung für den Zugriff auf MongoDB erstellt hat. Seine Arbeit ist in Quell- und Binärform unter github.com/samus/mongodb-csharp verfügbar. Laden Sie entweder die Binärdateien auf der Seite (oben rechts) oder die Quellen für die Erstellung herunter. In beiden Fällen haben Sie zwei Assemblys: „MongoDB.Driver.dll“ und „MongoDB.Linq.dll“. Fügen Sie dem Knoten „Reference“ eine Funktion zum Hinzufügen von Referenzen hinzu, und schon kann .NET Framework genutzt werden.

Schreiben von Code

Im Grunde unterscheidet sich das Öffnen einer Verbindung mit einem aktiven MongoDB-Server nicht sehr vom Öffnen einer Verbindung mit einer anderen Datenbank, wie in Abbildung 4 gezeigt.

Abbildung 4 Öffnen einer Verbindung mit einem MongoDB-Server

using System;
using MongoDB.Driver; 

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      Mongo db = new Mongo();
      db.Connect(); //Connect to localhost on the default port
      db.Disconnect();
    }
  }
}

Das Ermitteln des zuvor erstellten Objekts ist nicht schwierig, es unterscheidet sich nur von dem, was .NET Framework-Entwickler früher verwendet haben (siehe Abbildung 5).

Abbildung 5 Ermitteln eines erstellten Mongo-Objekts

using System;
using MongoDB.Driver; 

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      Mongo db = new Mongo();
      db.Connect(); //Connect to localhost on the default port.
      Database test = db.getDB("test");
      IMongoCollection things = test.GetCollection("things");
      Document queryDoc = new Document();
      queryDoc.Append("lastname", "Neward");
      Document resultDoc = things.FindOne(queryDoc);
      Console.WriteLine(resultDoc);
      db.Disconnect();
    }
  }
}

Falls der Code ein wenig überwältigend aussieht, entspannen Sie sich: Der Code ist auf die „lange Art“ geschrieben, da MongoDB Daten anders speichert als herkömmliche Datenbanken.

Denken Sie für den Anfang daran, dass die zuvor eingefügten Daten drei Felder aufgewiesen haben (für den Vornamen, den Nachnamen und das Alter), und diese Felder stellen Elemente dar, nach denen die Daten abgerufen werden können. Wichtiger ist jedoch, dass die Zeile, mit der die Daten gespeichert wurden und die einfach verworfen wurde, „test.things.save()“ war. Sie impliziert, dass die Daten in etwas namens „things“ gespeichert werden. In der MongoDB-Terminologie ist „things“ eine Sammlung, und alle Daten werden implizit in einer Sammlung gespeichert. Sammlungen können wiederum Dokumente enthalten, die Schlüssel/Wert-Paare enthalten, bei denen die Werte weitere Sammlungen sein können. In diesem Fall ist „things“ eine Sammlung, die in einer Datenbank gespeichert ist. Die Datenbank ist die bereits erwähnte Testdatenbank.

Das Abrufen von Daten bedeutet deshalb, dass zuerst eine Verbindung mit dem MongoDB-Server und dann mit der Testdatenbank hergestellt wird. Anschließend wird nach der Sammlung „things“ gesucht. Dazu werden die ersten vier Zeilen in Abbildung 5 verwendet: Ein Mongo-Objekt erstellen, das die Verbindung darstellt, eine Verbindung mit dem Server herstellen, eine Verbindung mit der Testdatenbank herstellen und dann die Sammlung „things“ abrufen.

Sobald die Sammlung zurückgegeben wurde, kann der Code eine Abfrage ausgeben, um ein bestimmtes Dokument über den FindOne-Aufruf zu suchen. Aber wie bei allen Datenbanken möchte der Client nicht alle Dokumente in der Sammlung abrufen und dann nach einem bestimmten Dokument suchen. Die Abfrage muss also eingeschränkt werden. In MongoDB wird dazu ein Dokument erstellt, das die Felder und die Daten, nach denen in diesen Feldern gesucht werden soll, enthält. Ein Konzept, das als Query By Example (QBE) bezeichnet wird. Da es das Ziel ist, das Dokument mit einem lastname-Feld mit dem Wert „Neward“ zu finden, wird ein Dokument erstellt, das ein lastname-Feld mit dem Wert enthält, und als Parameter von „FindOne“ übergeben. Ist die Abfrage erfolgreich, wird ein anderes Dokument zurückgegeben, dass alle fraglichen Daten (und ein zusätzliches Feld) enthält, andernfalls wird null zurückgegeben.

Die kurze Version dieser Beschreibung kann wie folgt aussehen:

Document anotherResult = 
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward"));
       Console.WriteLine(anotherResult);

Bei der Ausführung werden nicht nur die angegebenen Originalwerte angezeigt, sondern auch ein neuer, ein _id-Feld, das ein ObjectId-Objekt enthält. Dies ist der eindeutige Bezeichner für das Objekt, und er wurde automatisch beim Speichern der neuen Daten von der Datenbank eingefügt. Bei jedem Versuch, dieses Objekt zu ändern, muss das Feld erhalten bleiben. Andernfalls nimmt die Datenbank an, dass ein neues Objekt angegeben wurden. Üblicherweise wird dazu das Dokument geändert, das von der Abfrage zurückgegeben wurde:

anotherResult["age"] = 39;
       things.Update(resultDoc);
       Console.WriteLine(
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward")));

Es ist jedoch immer möglich, eine neue Dokumentinstanz zu erstellen und das _id-Feld so auszufüllen, dass es zum Wert für „ObjectId“ passt, falls das sinnvoller erscheint:

Document ted = new Document();
       ted["_id"] = new MongoDB.Driver.Oid("4b61494aff75000000002e77");
       ted["firstname"] = "Ted";
       ted["lastname"] = "Neward";
       ted["age"] = 40;
       things.Update(ted);
       Console.WriteLine(
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward")));

Falls „_id“ bereits bekannt ist, kann natürlich auch dieser Wert als Abfragekriterium verwendet werden.

Beachten Sie, dass das Dokument effektiv typlos ist. Fast alles kann mit dem Namen in einem Feld gespeichert werden, auch einige grundlegende .NET Framework-Werttypen wie „DateTime“. Wie bereits erwähnt, speichert MongoDB technisch gesehen BSON-Daten, zu denen einige Erweiterungen der herkömmlichen JSON-Typen gehören (string, integer, Boolean, double und null, wobei null nur in Objekten, nicht aber in Sammlungen zulässig ist), wie die genannten ObjectId-Werte, Binärdaten, reguläre Ausdrücke und eingebetteten JavaScript-Code. Für den Moment vernachlässigen wir die beiden letzten Typen. Die Tatsache, dass BSON Binärdaten speichern kann, bedeutet, dass alles, was auf ein Bytearray reduziert werden kann, gespeichert werden kann. Effektiv bedeutet dies, dass MongoDB alles speichern kann, es ist aber möglicherweise nicht möglich, die Binärdaten abzufragen.

Weitere Informationen

Zu MongoDB kann noch viel mehr gesagt werden, dazu gehört die LINQ-Unterstützung, die Ausführung komplexerer Serverabfragen, die mehr Funktionen als die bisher gezeigten QBE-Abfragen aufweisen, und die Einrichtung von MongoDB in einer Produktionsserverfarm. Dieser Artikel und die genaue Untersuchung von IntelliSense sollte aber zunächst ausreichen, damit der Programmierer mit der Arbeit beginnen kann.

Wenn Sie sich für ein bestimmtes Thema interessieren, können Sie mir das gerne mitteilen. Dies ist schließlich Ihre Kolumne. Viel Spaß beim Programmieren!

Ted Neward ist ein Geschäftsführer von Neward & Associates, einer unabhängigen Firma, die sich auf .NET Framework- und Java-Plattformsysteme für Unternehmen spezialisiert hat. Er hat mehr als 100 Artikel geschrieben, ist ein C#-MVP und ein INETA-Sprecher und hat mehrere Bücher allein und in Zusammenarbeit mit anderen geschrieben, darunter das in Kürze erscheinende „Professional F# 2.0“ (Wrox). Er berät und hilft regelmäßig. Sie können Ihn unter ted@tedneward.com erreichen, oder lesen Sie seinen Blog unter blogs.tedneward.com.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Kyle Banker und Sam Corder