März 2016

Band 31, Nummer 3

Dieser Artikel wurde maschinell übersetzt.

Die arbeiten Programmierer - wie mittlere zu sein: Zuverlässige Validierung mit MongooseJS

Durch Ted Neward | März 2016

Ted NewardIn meinem Artikel vom Februar 2016 (msdn.com/magazine/mt632276), kann ich in einer MongoDB-Datenbank übergehen. Dies war nicht schwer Dank der JSON-basierte Natur von Node.js und MongoDB Charakter JSON-basierte. (Leben ist immer einfacher, beim Arbeiten mit Daten, wenn der Übergang von Äpfel ausgewogenen ist). MongoDB hat einen großen Vorteil, es wird "Skalierung" und "" leicht, skalieren ganz zu schweigen davon, dass sie für den Einstieg trivial ist. Er hat aber auch einen entscheidenden Nachteil: Da MongoDB einer "schemalos"-Datenbank (darin, dass das Schema bereits vordefiniert ist – eine Datenbank enthält die Sammlungen, Sammlungen, enthalten Dokumente und Dokumente sind im Grunde nur JSON-Objekte), in sie Ausgangswerte für den eigenen Zerstörung liegt.

Zunächst enthält erinnern, dass MongoDB Abfragen im Wesentlichen Abfragen von Dokumenten (dieser erste Parameter für den Aufruf der suchen), sind die Felder, um die Auflistung nach Übereinstimmungen zu suchen. Wenn also die Abfrage "{'FristName': "Ted"} "ausgeführt wird anhand der Auflistung"Personen"in der vorhandenen Datenbank nichts immer wieder – der Name des Felds in der Abfragedokument ist falsch geschrieben ("FristName"anstelle von"FirstName"), damit es keine Dokumente in der Auflistung verglichen werden. (Es sei denn ein Tippfehler im Dokument in der Auflistung des Kurses.) Dies ist einer der größten Nachteile der eine schemalose Datenbank – ein einfache Tippfehler im Code oder Eingabe unbeabsichtigte Fehler der meisten aufgebracht Head beträchtliche Art erstellen können. Dies ist, in denen es um einige Language Support, wäre, ob von einem Compiler oder einem Interpreter entspricht.

Zweitens feststellen Sie, dass der Code in meinem letzten Artikel angezeigt "zwei Ebenen"-Anwendung vergleichbar, die zwei Jahrzehnten während des Zeitraums "Client-Server" Computing beliebt waren. Es gibt eine Code-Ebene, der direkt von diesem Benutzer (oder in diesem Fall von API-Consumer), gilt er erhält eine einfache Datenstruktur zurück, aus der Datenbank, die er direkt zurück an den Aufrufer übergibt. Es ist sicherlich nicht sinnvoll "objektorientierter" in diesem Code. Beim kein Geschäft-Breaker wäre schön es wir stärker "Objekt-Funktion" auf den serverseitigen Code geäußerten konnte, dass eine Validierung verschiedener Eigenschaften an einer Stelle, z. B. zentral verwaltet werden kann. Fall: Ist es zulässig, dass eine Person, die ein leeres FirstName oder LastName haben? Kann er absolut nichts in seinen Status arbeiten? Gibt es ein Status "Default", wenn er nicht zum Bereitstellen einer auswählt, oder ist ein leerer Status akzeptabel?

Node.js-Gruppe hat diese Probleme eine ganze Weile kämpfen (es war einer der ersten Sprache Ökosysteme MongoDB auf eine umfangreiche Basis zu fassenden) und überrascht nicht, dass es wurde bereits mit einer eleganten Lösung des Problems, MongooseJS aufgerufen. Es ist eine Softwareschicht, die "on Top" befindet sich der MongoDB und bietet nicht nur eine Schema-ähnlichen Sprache überprüft Überprüfungsebene, sondern auch Gelegenheit, eine Ebene "Domain Object" in den serverseitigen Code zu erstellen. Daher ist es irgendwie "anderen bin '" in der MEAN-Stapel.

MongooseJS: Erste Schritte

Jetzt sollte die Übung ziemlich einfach, einfach und wiederholten abrufen: "Welche" "Sie Npm diesmal zu tun?" Die Antwort ist "Npm-install – speichern Mongoose," aber bei durchsucht Verwirrung, was das genaue Paket könnte sein (der Knoten in der Regel zwischen "Thing" und "Thingjs" als Paketnamen waffle), "Npm suchen" oder "Npm-Suche" die Npm-Registrierung für Pakete, die mit die Begriffen in der Befehlszeile führen Sie übereinstimmen. Alternativ eingeben "Mongoose JS" in der Suchmaschine Ihrer Wahl verwendet, führt das Mongoose-Website (mongoosejs.com), die müssen die richtigen Npm-Version zusammen mit der Dokumentation zur Verwendung von Mongoose erhalten. (Dadurch wird es praktisch etwas im Browser, vergewissern Sie sich ein Lesezeichen erstellt haben.)

Nach der Installation können Sie beginnen, um Mongoose "Schema"-Objekte, zu definieren, die definieren, die Art der Objekte in der MongoDB-Auflistung gespeichert werden.

Mongoosing einer Person

Mongoose verwendet Terminologie interessante im wesentlichen Schritten zum Definieren einer JavaScript-Objektmodell über die MongoDB-Datenbank-API Neuigkeiten. Zuerst definieren wir eine "Schema", der aussieht wie eine herkömmliche Klasse aus einer herkömmlichen Klassen basierende Sprache (c#, C++, Java oder Visual Basic). Dieses Schema wird enthalten Felder, Typen für diese Felder definieren und optional einige Validierungsregeln, um die Felder für die beim Zuweisen von Werten in die Felder. Sie können auch hinzufügen, einige Methoden, die Instanz oder statisch, die ich später abrufen können. Klicken Sie dann nach der Definition des Schemaobjekts kompiliert"" in einem Modell ist, was zum Erstellen von Instanzen dieser Objekte verwendet wird.

Nehmen Sie hier zu beachten: Dies ist immer noch JavaScript, und noch wichtiger ist, dies ist die Version von JavaScript Weitere ECMAScript 5, dem Konzept der "Class" überhaupt fehlt genau beschrieben. Aus diesem Grund, obwohl der Ansatz Mongoose die Illusion erstellt wird, Sie definieren eine "Class", ist es immer noch die Prototypen basierende Sprache, die Sie kennen und schätzen (oder loathe). Dies ist zum Teil der Grund für diesen Prozess in zwei Schritten: Definieren eines Objekts, das als die Klasse dient als erster die zweite definiert ein Objekt, das implizit als "Konstruktor" oder "Factory" funktioniert gemäß den Regeln JavaScript/ECMAScript 5, um den Operator "new".

Also können übersetzen, die in Code nach "erforderlich ()" Ing Mongoose-Bibliothek in der üblichen "Mongoose" für die lokalen Variablen am Anfang der JavaScript-Code, der Sie um ein neues Objekt zu definieren:

// Define our Mongoose Schema
var personSchema = mongoose.Schema({
  firstName: String,
  lastName: String,
  status: String
});
var Person = mongoose.model('Person', personSchema);

Es gibt eine Vielzahl von Vorgängen, die Sie können mit den Feldern in der PersonSchema, die sondern beispielsweise wir ganz einfach. Dies wird nicht nur helfen, Code schneller arbeiten, sondern wird Hervorhebung auch einige Sicherungsfunktionalität zu Mongoose unterwegs. Außerdem Beachten Sie den Schritten, mit dem das Modell definiert ist: zunächst definieren Sie das Schema mit dem Schema-Funktion/Konstruktor, und dann an den Modell-Funktion/Konstruktor zusammen mit dem Namen dieses Modells übergeben des Schemaobjekts. Konvention für Mongoose Benutzer ist, dass das zurückgegebene Objekt aus dem Modell Aufruf gleich bleiben, wie der Typ definiert wird, denn das ist was sie den Eindruck, dass eine Klasse geben können.

Mongoosing in Aktion

Das Modell definieren, ist jetzt nur Sache Links führen Sie die verschiedenen Methoden der Route neu zu schreiben, die das Modell verwenden. Die einfachste Möglichkeit für den start ist die Route "getAllPersons" Weiter, siehe Abbildung 1, da sie die gesamte Auflistung ohne Filter aus der Datenbank zurückgibt.

Abbildung 1 Umschreiben der GetAllPersons Route

var getAllPersons = function(req, res) {
  Person.find(function(err, persons) {
    if (err) {
     debug("getAllPersons--ERROR:",err);
      res.status(500).jsonp(err);
    }
    else {
      debug("getAllPersons:", persons);
      res.status(200).jsonp(persons);
    }
});
};

Beachten Sie, wie der Code entspricht im Grunde war es vor, die Abfrage ausgeführt wird, und ruft die Funktion übergebenen Rückruf (in diesem Fall mit der Konvention "err, Ergebnis" als Parameter) Wenn die Abfrage abgeschlossen ist. Allerdings bietet jetzt Mongoose ein wenig mehr Struktur, um den Zugriff durch die Weiterleitung über den Personmodel-Objekt, das alle Arten von unschlagbaren Vorteile erzielt werden, wie Sie gleich sehen werden.

Weiter oben ist die Middleware PersonId, weil es in fast allen weiteren Routen, siehe Abbildung 2.

Abbildung 2 Umschreiben der Personld Route

app.param('personId', function (req, res, next, personId) {
  debug("personId found:",personId);
  if (mongodb.ObjectId.isValid(personId)) {
    Person.findById(personId)
      .then(function(person) {
        debug("Found", person.lastName);
        req.person = person;
        next();
      });
  }
  else {
    res.status(404).jsonp({ message: 'ID ' + personId + ' not found'});
  }
});

Dies ist wiederum eine Middleware-Funktion, damit das Ziel ist die Person-Objekt aus der Auflistung suchen und speichern es in das Request-Objekt (Req). Aber beachten Sie, dass nach der Überprüfung, dass die eingehende PersonId gültige MongoDB ObjectId/OID. Das Person-Objekt wird nochmals FindById aufrufen, übergeben die ObjectID/OID übergeben. Am wichtigsten sieht, dass Mongoose die "Promise" Syntaxformat unterstützt, die eine Fixierung in zukünftigen Versionen von JavaScript werden – von FindById zurückgegebenen Objekts ist ein Promise-Objekt, auf dem Sie "then" aufrufen und übergeben Sie einen Rückruf, der ausgeführt werden, wenn die Abfrage abgeschlossen ist konfigurieren.

Dieser Ansatz in zwei Schritten können Sie führen eine Vielzahl von Aufgaben mit dieser Abfrage vor der Ausführung, z. B. die Ergebnisse der Abfrage in einer bestimmten Reihenfolge basierend auf Feldern in den Ergebnissen zu sortieren oder wählen Sie nur eine Teilmenge der Felder (So blenden vertrauliche Informationen, die der Client empfängt, sollte nicht). Rechts zwischen suchen und dann in einem flüssigen Stil, wie z. B. wedged Methodenaufrufe sind:

Person.find({ })
  .sort({ 'firstName':'asc', 'lastName':'desc' })
  .select('firstName lastName status')
  .then(function(persons) {
    // Do something with the returned persons
  });

In diesem Fall dies würde die Ergebnisse nach FirstName in aufsteigender Reihenfolge sortieren, Sortieren der Ergebnisse nach Nachnamen in absteigender Reihenfolge (nicht das Ausführen, die viel Sinn macht) und stellen Sie diese Daten mit Ausnahme von "FirstName", "LastName" und "Status"-Felder. Die vollständige Liste der Abfrage "Modifizierer" ist beeindruckend und umfasst eine Reihe von nützlichen Methoden wie z. B. Grenzwert (bei einer bestimmten Anzahl von Ergebnissen abgeschnitten), überspringen (um die ersten n Ergebnisse zurückgegeben überspringen), count (um ein Count-Aggregat der zurückgegebenen Dokumente zurückgeben) und so weiter.

Bevor abgelenkt, zu erhalten jedoch werde ich den Rest der Route Methoden runden.

Verwendet die Middleware werden das betreffende Update und Delete Person-Objekt abgerufen haben, die das Speichern recht einfach verwendet werden und Löschen von Mongoose auf die Objekte selbst bereitgestellte Methoden. Siehe Abbildung 3, instanziieren ein neues Modell für die Person und speichern erfordert eine neue Person nur einfügen-Methode dafür auf.

Abbildung 3 instanziieren ein neues Modell für die Person

var updatePerson = function(req, res) {
  debug("Updating",req.person,"with",req.body);
  _.merge(req.person, req.body);
  // The req.person is already a Person, so just update()
  req.person.save(function (err, person) {
    if (err)
      res.status(500).jsonp(err);
    else {
      res.status(200).jsonp(person);
    }
  });
};
var insertPerson = function(req, res) {
  var person = new Person(req.body);
  debug("Received", person);
  person.save(function(err, person) {
    if (err)
      res.status(500).jsonp(err);
    else
      res.status(200).jsonp(person);
  });
};
var deletePerson = function(req, res) {
  debug("Removing", req.person.firstName, req.person.lastName);
  req.person.delete(function(err, result) {
    if (err) {
      debug("deletePerson: ERROR:", err);
      res.status(500).jsonp(err);
    }
    else {
      res.status(200).jsonp(req.person);
    }
  });
};

Vom Konzept her, er sieht fast wie die vorherige Version nicht Mongoose jedoch besonders wichtig, wenn feine, geändert wird: Logik zu Person wird nun weitere lokalisiert die Person-Modell (und des Mongoose dauerhaftigkeitsimplementierung).

Mongoose Überprüfung

Jetzt nur für sehen Sie mehrere neue Regeln zur Anwendung hinzufügen möchten: weder FirstName und LastName kann leer sein und Status kann nur eine von mehreren möglichen Werten (in ein enumerierter Typ). Dies ist die Mongoose die Möglichkeit zum Erstellen einer Domäne-Objektmodells in der serverseitigen besonders leistungsstarke möglich, da klassischen objektorientiertes Programmieren denken enthält, diese Arten von Regeln innerhalb der Objekttyp selbst und nicht auf den umgebenden Nutzung Code gekapselt werden soll.

Mit Mongoose ist dies tatsächlich vergleichsweise trivial. Innerhalb des Schemaobjekts, die Felder zu wechseln, wird der einfache Name: Geben Sie Paare komplexere Name: Objekt Descriptor-Paare und Validierungsregeln in diese Deskriptoren Objekt angegeben werden. Zusätzlich zu den üblichen floss numerischen Überprüfung (min und Max) und Überprüfung (min und max Länge), können Sie ein Array der zulässigen Werte für das Feld "Status" angeben und definieren Sie einen Standardwert für jedes Feld ein, wenn keine angegeben, die (nicht überraschend) passt ordentlich die neuen Anforderungen, siehe Abbildung 4.

Abbildung 4 Mongoose Überprüfung

// Define our Mongoose Schema
var personSchema = mongoose.Schema({
  firstName: {
    type: String,
    required: true,
    default: "(No name specified)"
  },
  lastName: {
    type: String,
    required: true,
    default: "(No name specified)"
  },
  status: {
    type: String,
    required: true,
    enum: [
      "Reading MSDN",
      "WCFing",
      "RESTing",
      "VBing",
      "C#ing"
    ],
    default: "Reading MSDN"
  },
});
var Person = mongoose.model('Person', personSchema);

Nichts an anderer Stelle in der Codebasis geändert werden muss, werden alle diese Regeln zur Person-Funktion erfasst, genau, wo sie, in der Definition des Schemaobjekts werden sollen.

Wenn Sie versuchen, fügen Sie nun JSON, unterliegen nicht, die die richtige Liste von Status, z. B.:

{
  "firstName":"Ted",
  "lastName":"Neward",
  "status":"Javaing"
}

Das Speichern in der Route InsertPerson aufgerufene Methode ergibt eine 500-Antwort mit den zurückgegebenen JSON-Text lesen, siehe Abbildung 5.

Abbildung 5-Fehler fehlschlägt, Validierung speichern

{
  "message":"Person validation failed",
  "name":"ValidationError",
  "errors":{
    "status":{
      "properties":{
        "enumValues":[
          "Reading MSDN",
          "WCFing",
          "RESTing",
          "VBing",
          "C#ing"
        ],
        "type":"enum",
        "message":"`{VALUE}` is not a valid enum value for path `{PATH}`.",
        "path":"status",
        "value":"Javaing"
      },
      "message":"`Javaing` is not a valid enum value for path `status`.",
      "name":"ValidatorError",
      "kind":"enum",
      "path":"status",
      "value":"Javaing"
    }
  }
}

Die Nägel ziemlich, was es gelaufen.

Mongoose Methoding

Natürlich bedeutet Objektorientierung kombinieren, Zustand und Verhalten, was bedeutet, dass diese Domänenobjekte wirklich Objekte nicht, wenn Sie Methoden Objektinstanzen oder "Class" als Ganzes anfügen können. Dies ist tatsächlich recht einfach: Eine Mongoose Methode aufzurufen (Methode für eine herkömmliche instanzbasierte Methode) oder statische für eine Klasse, die definieren Sie die Methode und übergeben Sie der Funktion als Methodentext verwendet wird wie folgt:

personSchema.method('speak', function() {
  console.log("Don't bother me, I'm", status);
});

Es ist nicht genau wie eine Klasse in c# schreiben, aber es ist ziemlich schließen. Beachten Sie, dass sowie alle anderen Änderungen an Schemaobjekten, diese alle erfolgen müssen vor dem im Modell-Objekt, das Schema kompiliert ist, damit dieser Aufruf vor dem Aufruf der mongoose.model angezeigt werden muss.

Mongoose Versionskontrolle

Übrigens, enthält einen schnellen Abruf /persons noch einmal tun, die resultierende Ausgabe ein wenig unerwartete:

[{"_id":"5681d8bfddb73cd9ff445ec2",
  "__v":0,
  "status":"RESTing",
  "lastName":"Castro",
  "firstName":"Miguel"}]

Das Feld "__v" der Mongoose im Hintergrund in der Mischung verrutscht (und ganz auf die Datenbank behält) ist ein Versioning-Feld, das in Mongoose als Feld VersionKey bekannt und Mongoose Änderungen des Dokuments erkennen können; Betrachten sie als eine Versionsnummer auf eine Quellcodedatei, als eine Möglichkeit zum Erkennen von gleichzeitigen Änderungen. Dies ist nur eine interne Details der Mongoose normalerweise etwas überrascht es beim ersten auftauchen angezeigt sein.

Dennoch auslöst, eine weitere interessante Frage: Es ist nicht ungewöhnlich, dass Systeme nachverfolgen, wenn ein Dokument erstellt wurde, oder wenn Änderungen in ein Dokument vorgenommen wurden, und es wäre sicherlich Ihre Felder ausfüllen, jedes Mal einen beliebigen Teil der umgebende Code berührt oder einer von denen gespeichert haben, möchten.

Mongoose kann, und mit deren Hilfe Sie "verknüpfen" bestimmte Lebenszyklusmethoden um Felder rechts zu aktualisieren, bevor das Dokument mit MongoDB, unabhängig von den Aufruf geschrieben werden, die sie beibehalten werden, siehe Abbildung 6.

Abbildung 6 Lebenszyklusmethoden mit Mongoose verknüpfen

// Define our Mongoose Schema
var personSchema = mongoose.Schema({
  created: {
    type: Date,
    default: Date.now
  },
  updated: {
    type: Date,
  },
  // ... as before
});
personSchema.pre('save', function(next) {
  // Make sure updated holds the current date/time
  this.updated = new Date();
  next();
});
var Person = mongoose.model('Person', personSchema);

Nun immer ein Person-Objekt erstellt wird, wird als Standardwert das erstellte Feld enthält das aktuelle Datum/Uhrzeit und das aktualisierte Feld wird auf das aktuelle Datum/Uhrzeit unmittelbar vor dem MongoDB gesendet werden, die für den Speicher festgelegt werden. Mongoose ruft diese "Middleware" Geist der Middleware-Funktionen von Express definiert werden, jedoch keine Fehler machen, sind bestimmte und enthaltenen vollständig auf das Objekt Mongoose definiert.

Nun, bei eine Person erstellt oder aktualisiert wird, sie müssen die entsprechenden Felder ausgefüllt und bei Bedarf aktualisiert. Darüber hinaus sollte etwas anspruchsvolleren (z. B. ein vollständiges Überwachungsprotokoll) erforderlich sein, es ist ziemlich einfach, um festzustellen, wie diese hinzugefügt werden konnte. Anstelle von Feldern erstellt/aktualisiert gibt es ein Feld AuditLog, ein Array wäre, und die Hookfunktion "Speichern" würde auf dieses Array beschreiben Sie jedes Mal vorgenommen, und/oder, die es je nach Anforderungen haben einfach immer wieder anfügen.

Nachbereitung

Dies war ein weiterer relativ hohe Teil und ist sicherlich viel mehr von Mongoose, die näher untersucht werden. Allerdings, die der Weg ist eine neue Technologie und die Plattform zu untersuchen, und es wäre sehr churlish von mir diese Art von Spaß macht, meine treuen Leserschaft einschließen möchten. Das wichtigste zu berücksichtigen ist, dass diese Kopplung Mongoose + MongoDB, eine leistungsfähige Kombination dar bietet: der schemalose Charakter der MongoDB-Datenbank mit der Sprache erzwungene Validierung von einfachen Typen verknüpft ist, sowie Runtime erzwungene Validierung von Datenwerten, geben Sie uns etwas der besten sowohl die statisch typisierte und dynamisch typisierte Welten. Es ist nicht ohne Probleme und unklarheiten, aber alles in allem ist es schwer für mich vorstellbar coding Arbeit in MongoDB ohne etwa Mongoose halten mich aus dumm Dinge ausführen.

Ich bin fast fertig mit serverseitigen Dinge, aber ich bin von Speicherplatz, dies jetzt... viel Spaß beim Programmieren!


Ted NewardPolytechnology Seattle basierende Berater, Referent und Mentor. Er hat mehr als 100 Artikel geschrieben ist ein F#-MVP, INETA-Sprecher wurde erstellt und Mitautor ein Dutzend Bücher. Sie können ihn erreichen unter ted@tedneward.com wenn sie möchten, dass er in Ihrem Team arbeitet, oder sein Blog lesen unter blogs.tedneward.com.

Dank den folgenden technischen Experten für die Überprüfung dieses Artikels: Shawn Wildermuth