RavenDB: Eine Einführung

RavenDB ist eine dokumentenorientierte Datenbank. Dies bedeutet das man nicht einzelne Zeilen speichert, sondern ganze Dokumente (im Sinne von JSON und nicht eines Word-Dokuments).

RavenDB ist nicht die erste solche Datenbank. Ganz bewusst spricht man hier von einer 2. Generation. Die Vorteile von Produkten die den Begriff Dokumentenorientierte Datenbank begründeten (wie MongoDB) wurden aufgenommen und gleichzeitig hat man die grössten Probleme und Unschönheiten behoben.

Mit RavenDB steht einem ein ausgereiftes Produkt zu Verfügung das hervorragend mit .Net zusammen spielt. Mit zahlreichen Strategien hilft einem RavenDB eine performante Anwendung zu schreiben. Das dazu nötige Wissen sammelte der Hauptentwickler Ayende Rahien bei Projekten wie NHibernate und dem NH- und EF-Profiler.

Für OpenSource-Projekte kann man RavenDB kostenlos nutzen. Das Lizenzierungsschema für ClosedSource-Projekte ist recht umfangreich und hilft einem das zum Projekt passende Modell zu finden. Die Preise starten bei moderaten 7$ pro Monat mit die Basic-Version und steigen je nach Funktionsbedarf an.

 

Installation der Datenbank

Um den Server-Teil von RavenDB zu installieren gibt es 2 Wege:

  1. Der Download einer Zip-Datei
  2. Das NuGet-Paket RavenDB.Server

Für den Server ist die Option 1 der bevorzugte Weg. Nach dem Entpacken der Zip-Datei startet man das Programm start.cmd und RavenDB kümmert sich um den Rest. Die webbasierte Administrationsoberfläche ist von nun an über http://localhost:8080 (oder dem ersten freien Port darüber) erreichbar:

Bevorzugt man lieber die Variante mit NuGet muss man für den Server-Teil ein eigenes Projekt anlegen. Dieses Paket ist nicht dazu gedacht mit Visual Studio verwendet zu werden und man braucht dieses separate Projekt einzig als Installationscontainer. Die Startdatei heisst in diesem Fall Raven.Server.exe und liegt im zur Solution gehörenden Verzeichnis packages\RavenDB.Server.1.0.960\. Abgesehen davon gibt es keine Unterschiede zwischen den beiden Ansätzen.

Versucht man den Server im gleichen Projekt zu installieren wie den Client wird dies in nicht auflösbaren Abhängigkeiten enden. Daher die beiden Teile unbedingt trennen!

 

Installation des Clients

Hier sollte man aufs NuGet-Paket setzen. Neben dem Client selber werden so auch gleich alle Abhängigkeiten aufgelöst. Die Installation des Paketes RavenDB.Client erfolgt entweder über die grafische Oberfläche oder mit diesem Befehl in der Package Manager Console:

Install-Package RavenDB.Client

Sofern man keine speziellen Einstellungen machen will ist die Installation damit abgeschlossen.

 

Daten speichern

Mit RavenDB lassen sich Daten ganz einfach speichern. Damit IntelliSense bei Abfragen helfen kann sollte man erst einmal eine Klasse definieren:

public class Book
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string ISBN { get; set; }
    public int Pages { get; set; }

    public override string ToString()
    {
        return String.Format("{0} '{1}' ({2}) has {3} pages", Id, Title, ISBN, Pages);
    }
}

Um Objekte vom Typ Book zu speichern genügt es die Main-Methode einer Konsolenapplikation mit diesem Code zu erweitern:

using (var ds = new DocumentStore { Url = "http://localhost:8081/" }.Initialize())
using (var session = ds.OpenSession())
{
    var book = new Book() { Title = "RavenDB Intro", ISBN = "123-4-5678", Pages = 200 };
    session.Store(book);
    session.SaveChanges();
    Console.WriteLine(book.Id);
// => books/1 'RavenDB Intro' (123-4-5678) has 200 pages
}

Alles was man braucht um mit RavenDB Objekte zu speichern ist eine laufende Datenbank, eine Session und das zu speichernde Objekt. RavenDB nutzt für alle Operationen das Unit of Work Pattern. Bei so kleinen Demo-Anwendungen mag dies übertrieben erscheinen. Will man RavenDB aber produktiv einsetzen wird man sehr schnell die damit verbundenen Vorteile (wie Batch-Operationen) zu schätzen lernen.

 

Abfragen mit LINQ

RavenDB arbeitet sehr gut mit .Net zusammen. Dies wird nicht zu Letzt bei den Abfragen mittels LINQ deutlich. Möchte man alle Bücher die mit „Raven“ beginnen kann man diese ganz gewöhnliche LINQ-Abfrage schreiben:

using (var ds = new DocumentStore { Url = "http://localhost:8081/" }.Initialize())
using (var session = ds.OpenSession())
{
    var book = session.Load<Book>("books/1");
    Console.WriteLine(book);

    var ravenBooks = from books in session.Query<Book>()
            where books.Title.StartsWith("Raven")
            select books;

    foreach (var foundBook in ravenBooks)
    {
        Console.WriteLine(foundBook.Id + ": " + foundBook.Title);
        // => books/1 : RavenDB Intro
    }
}

Die Abfragen können beliebig verfeinert werden. Alles was man sonst so von LINQ kennt und schätzen gelernt hat funktioniert auch mit dem Provider für RavenDB.

Wenn man die ID eines Objektes kennt kann man auf LINQ verzichten und es direkt über die Session laden:

    var book = session.Load<Book>("books/1");

 

Verändern oder Löschen

Daten können ganz einfach verändert werden. Und werden die Daten nicht mehr benötigt lassen sie sich auch ohne grossen Aufwand löschen. Zum Editieren erzeugt man jeweils erst eine Abfrage die einem die gewünschten Objekte holt. Nach dem Verändern müssen die Änderungen der Session bekannt gemacht werden – auch hier nutzt man ja das Unit of Work Pattern.

using (var ds = new DocumentStore { Url = "http://localhost:8081/"}.Initialize())
using (var session = ds.OpenSession())
{
    var book = session.Load<Book>("books/1");
    Console.WriteLine(book);

    book.Title = "Another Book about RavenDB";
    session.Store(book);
    session.SaveChanges();

    var renamedBook = session.Load<Book>("books/1");
    Console.WriteLine(renamedBook);
    // => books/1 'RavenDB Intro' (123-4-5678) has 200 pages
    // => books/1 'Another Book about RavenDB' (123-4-5678) has 200 pages
}

Zum Löschen eines Objektes beginnt man ebenfalls mit einer Abfrage und markiert das Objekt als gelöscht:

using (var ds = new DocumentStore { Url = "http://localhost:8081/" }.Initialize())
using (var session = ds.OpenSession())
{
    var book = session.Load<Book>("books/1");
    Console.WriteLine(book);

    session.Delete(book);
    session.SaveChanges();

    var renamedBook = session.Load<Book>("books/1");
    if (renamedBook == null)
    {
        Console.WriteLine("Book with id 1 not found");    
    }
    // => books/1 'RavenDB Intro' (123-4-5678) has 200 pages
    // => Book with id 1 not found
}

Vergisst man das Objekt erst zu holen wirft RavenDB eine entsprechende Exception. Der Vorteil davon ist man löscht auch wirklich nur das gewünschte einzelne Objekt.

 

Fazit

Mit RavenDB kommt man ohne grosse Vorbereitung schnell zu einer Dokumentendatenbank. Die Standardeinstellungen sind Ideal um sich ins Thema NoSQL einzuarbeiten. Und auch das Unit of Work Pattern kann man ohne grossen Aufwand in Aktion erleben.

Will man RavenDB produktiv einsetzten wird man aber noch ein wenig mehr Aufwand treiben müssen als für diese Demobeispiele. Zu vielen Bedürfnissen einer Produktionsumgebung bietet einem RavenDB eine optimale Unterstützung. Diese Teile werde ich in einem eigenen Blogeintrag genauer beschreiben.

5 Gedanken zu „RavenDB: Eine Einführung“

  1. Sehr gut. Was ist mit den Probleme und Unschönheiten gemeint? Wäre nicht schlecht wenn man diese auch kennen würde, dann kann man sich bewusst für oder gegen RavenDB entscheiden.

    Gruss
    Daniel

    1. Hallo Daniel,
      Im Gegensatz zu vielen anderen NoSQL-Systemen unterstützt RavenDB volle ACID-konforme Transaktionen. Was man bei relationalen Datenbanken schätzen gelernt hat funktioniert auch hier. Zudem ist man bei RavenDB sicher das die Daten geschrieben wurden wenn man ein OK von der DB bekommen hat. Bei MongoDB muss man explizit nachfragen ob die Daten in der DB sind – das ist schnell einmal ein Problem für Entwickler die sich dies nicht gewohnt sind.

      Zu den Unschönheiten zähle ich JavaScript als Abfragesprache. Wenn man gerne JavaScript hat sieht man dies sicher anders und hat seine Freude mit MongoDB. Bei RavenDB kann ich auch auf DB-Ebene LINQ verwenden. Dies geht auch für die auf Map/Reduce basierenden Indexe, welches etwas ist was RavenDB gleich macht wie CouchDB und MongoDB.

      Soweit mal die Punkte die ich als wichtigste Verbesserungen von RavenDB gegenüber der Konkurrenz sehe. Falls Du mehr wissen willst einfach fragen.

      Gruss Johnny

  2. Hallo Jonny,
    drei Dinge.

    Wie sieht es aus, wenn ich sagen wir 100 Objs aus 20000 gespeicherten Obj selektieren will und dafür einen Teil eines Feldes angebe, also sagen wir es gibt 20k Bücher und ich will die hundert finden die mit Raven beginnen. Wie lange dauert so etwas im Vergleich zu einem RDBS.

    Angenommen ich habe 1M Objs mit Datumsfeld davon 100k mit Datum x.x.2011 und ich will alle löschen die älter als 1.1.2012 sind. Muss ich die alle einzeln ‚in die Hand nehmen‘ oder geht das irgendwie besser?

    Angenommen ich habe Objs die sehr viele Daten enthalten, sagen wir mal, einen ‚Header‘ mit persönlichen Daten und dann hunderte Rechnungen, Angebote, Bilder, was auch immer, richtig viel eben und ich möchte jetzt einmal die Telefonnummer vom Herrn Huber raussuchen, bekomme ich dann massenweise Daten, die ich nicht will oder geht das besser?

    Mit freundlichen Grüßen

    Gerhard

    1. Hallo Gerhard,
      Generelle Performance-Angaben sind immer recht heikel. Grundsätzlich spielt es weniger eine Rolle ob du für den 1. Punkt RavenDB oder ein RDBMS brauchst als vielmehr ob sich der Entwickler für das benutze System interessiert und auch an solche Dinge wie an einen Index denkt. Fehlt der Index macht ein RDBMS einen Full-Table-Scan während RavenDB den Index selbstständig erzeugt. Beides ist nicht optimal, aber RavenDB versucht überall die Best Practices umzusetzen und auf Probleme hinzuweisen bevor die Produktion stillsteht.
       
      Zu Punkt 2: RavenDB unterstützt Set-basierte Befehle wodurch man nicht jedes Dokument einzeln löschen muss: http://ayende.com/blog/4535/set-based-operations-with-ravendb
       
      Wenn Du dich ein wenig mit LINQ auskennst ist es kein Problem einen entsprechenden Map/Reduce basierenden Index zu erstellen der dir die ganze komplexe Objektstruktur auf eine Liste mit Name und E-Mail Adresse reduziert. Dann genügt es nur den Index abzufragen und du erfährst die E-Mail Adresse von Herrn Huber, ganz ohne all die anderen Informationen hin und her zu transferieren.
       
      Gruss
      Johnny 

Kommentare sind geschlossen.