Joins und Schema-Validierung mit MongoDB 3.2

Keine Kommentare

Mit Version 3.2 der dokumentenorientierten NoSQL-Datenbank MongoDB werden u.a. zwei lange vermisste(?) Features eingeführt, auf die ich im Folgenden näher eingehen möchte.

Joins

Die logischen Namensräume, in denen man seine Dokumente ablegt, werden in MongoDB Collections genannt. Diese waren bislang völlig isoliert voneinander. Jeder Art von Query, Aggregation und auch MapReduce-Job operierte auf den Daten genau einer Collection.

Ab Version 3.2 kann nun im Aggregation Frameworks ein Art Fetch Join verwendet werden, um Dokumente aus anderen Collections mitzuladen. Nehmen wir an, wir haben folgendes Schema …

customersorders
{
_id: ObjectId(...),
vorname: "...",
nachname: "...",
...
}
{
_id: ObjectId(...),
customer_id: ObjectId(...), // Foreign Key zu customers
total: ...,
items: [ ... ],
...
}

… und wollen bei der Suche nach Kunden (customers) direkt seine Bestellungen (orders) abfragen. Dazu legen wir zunächst folgende Testdaten ein:

db.customers.insert( {_id: "c1", vorname: "Tobias", nachname: "Trelle"} );
db.orders.insert( {_id:"o1", customer_id:"c1", total: 11.50, items:[{desc: "Item 1"}, {desc: "Item 2"}]} );
db.orders.insert( {_id:"o2", customer_id:"c1", total: 42.95, items:[{desc: "Item 2"}, {desc: "Item 3"}]} );

Den Join können wir nun mit einer neuen Pipeline-Operation $lookup des Aggregation Frameworks ausführen:

db.customers.aggregate( [
   {$match: {_id:"c1"}}, 
   {$lookup: {
       from: "orders", 
       localField: "_id", 
       foreignField: "customer_id", 
       as: "orders"}
   }
] )

Als Ergebnis erhält man den Kunden samt seiner Bestellungen im ge-join-ten Feld
orders:

{ 
"_id" : "c1", 
"vorname" : "Tobias", 
"nachname" : "Trelle", 
"orders" : [ 
   { 
   "_id" : "o1", 
   "customer_id" : "c1", 
   "total": 11.5, 
   "items" : [ { "desc" : "Item 1" }, { "desc" : "Item 2" } ] 
   }, 
   { 
   "_id" : "o2", 
   "customer_id" : "c1", 
   "total" : 42.95, 
   "items" : [ { "desc" : "Item 2" }, { "desc" : "Item 3" } ] 
   } 
] 
}

Zurzeit scheinen nur Joins über ein Feld pro Collection möglich zu sein, vielleicht ändert sich das auch noch in kommenden Versionen.

Schema-Validierung

Eine grundlegende Eigenschaft der Dokumentenorientierung war in MongoDB bislang die Schema-Freiheit, d.h. die Abwesenheit einer prüfenden Instanz beim Schreiben von Dokumenten, die bestimmte Schemata erzwingt. Konkret hieß dies, dass es weder Pflichtfeld- noch Typprüfungen auf Dokumenten gab.

Nun kann auf Ebene einer Collection ein sogenannter Validator definiert werden, der bei schreibenden Operationen Typ- und sogar inhaltliche Prüfungen ausführt:

db.createCollection("customers", {
   validator: { 
      nachname: {$type: "string"}, 
      alter: {$type: "int", $gte: 18 }
   }
})

Wir definieren bestimmte erwartete Typen für die Felder nachname und alter. Das macht diese auch direkt zu Pflichtfelder. Für das Feld alter definieren wir darüber hinaus noch die Einschränkung, dass der Wert >= 18 sein muss. Hier können nahezu alle Einschränkungen definiert werden, die auch bei der Formulierung von find-Queries verwendet werden dürfen. Ein invalides Dokument wird mit folgender Meldung abgewiesen:

db.customers.insert({_id:"c2", nachname: "Trelle", alter: NumberInt(8)})
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 121,
                "errmsg" : "Document failed validation"
        }
})

Erst ein Alter >= 18 führt in unserem Shop-System zum gewünschten Erfolg:

db.customers.insert({_id:"c1", nachname: "Trelle", alter: NumberInt(25)})
WriteResult({ "nInserted" : 1 })

Fazit

Mit den Fetch-Joins erhält man deutlich mehr Freiheit beim Schema-Design als bisher. Man ist nicht mehr so stark gezwungen, primär Query-orientiert zu planen und Daten redundant vorzuhalten. Natürlich sollte man auch jetzt im Einzelfall die Performance im Auge behalten. Joins werden auch in MongoDB nicht ohne Impact sein.

Die Schema-Validierung ist ein Feature, das die Verantwortung der Daten aus der Ebene der Applikation zurück in die Datenbank holt und somit zur (inhaltichen) Konsistenz des Datenbestandes beiträgt. Aber auch Validierung wird ein wenig Performance kosten, allerdings eher auf CPU-Ebene.

Mit diesen beiden Neuerungen macht MongoDB einen weiteren Schritt in Richtung Enterprise-Readiness. Ziel dürfte sein, weiter in Konkurrenz zu relationalen Systemen zu treten, die solche Features bereits seit Urzeiten anbieten. Man will endlich auch eine Allzweck-Datenbank werden. Ob das der Grundidee von NoSQL (nimm das passende Tool für Dein Problem) noch genügt, sein mal dahingestellt.

Alle Details und weitere neue Features können Sie den Release Notes für Version 3.2 entnehmen.

Tobias Trelle

Diplom-Mathematiker Tobias Trelle ist Senior IT Consultant bei der codecentric AG, Solingen. Er ist seit knapp 20 Jahren im IT-Business unterwegs und interessiert sich für Software-Architekturen und skalierbare Lösungen. Tobias hält Vorträge auf Konferenzen und Usergruppen und ist Autor des Buchs „MongoDB: Ein praktischer Einstieg“.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.