Marzo 2016

Volume 31 Numero 3

Il presente articolo è stato tradotto automaticamente.

The Working Programmer - Come usare MEAN: Convalida efficace con MongooseJS

Da Ted Neward | Marzo 2016

Ted NewardNel mio articolo di febbraio 2016 (msdn.com/magazine/mt632276), passata a un database MongoDB. In questo modo non è stato difficile grazie alla natura basata su JSON di Node. js e la natura basata su JSON di MongoDB. (Vita è sempre più semplice quando si lavora con i dati se la transizione da mele mele). MongoDB è un grande vantaggio in quanto sarà "scalabilità" e "scalabilità" facilmente, senza dimenticare che è estremamente semplice per iniziare. Ma ha anche un notevole svantaggio: Poiché MongoDB è un database "privo di schema" (in quanto lo schema è già definito, ovvero un database contiene raccolte, gli insiemi di contengono documenti e i documenti sono fondamentalmente solo oggetti JSON), in cui si trova i seeding di un proprio distruzione.

In primo luogo, tenere presente che le query MongoDB sono essenzialmente query nei documenti (il primo parametro alla chiamata di ricerca), che contiene i campi da utilizzare per l'analisi dell'insieme delle corrispondenze. Pertanto, se la query "{'fristName': 'Ted'} "viene eseguita sulla raccolta"persone"nel database esistente, non verranno restituiti, il nome del campo nel documento di query è errata ("fristName"anziché"firstName"), in modo corrisponderà contro alcuna documenti nella raccolta. (A meno che non vi è un errore di digitazione nel documento nella raccolta, ovviamente.) Si tratta di uno degli svantaggi principali di un database privo di schema, ovvero un semplice errore di digitazione nel codice o utente può creare bug accidentale della natura imbattuto più colpi di input. Si tratta in cui sarebbe utile ottenere il supporto per alcuni linguaggi che non dipende da un compilatore o un interprete.

In secondo luogo, si noti che il codice visualizzato nel mio ultimo articolo è simile a quella delle applicazioni "a due livelli" che sono stati diffusi per due decenni durante l'epoca di "client-server" di elaborazione. Esiste un livello di codice che accetta input direttamente dall'utente (o, in questo caso, il consumer di API), viene applicato e ottiene una struttura di dati semplice risposta dal database, che passa direttamente torna al chiamante. Non è certamente alcun senso di "-orientamento agli oggetti" in tale codice. Mentre non un affare-breaker, sarebbe interessante se è stato possibile ottenere più forte senso di "oggetto caratteristica" al codice lato server, in modo che potrebbero centralizzata alcune convalide di varie proprietà in un'unica posizione, ad esempio. Nella fattispecie: È possibile che un utente dispone di un nome vuoto o lastName? Possibile egli utilizzo assolutamente niente nel suo stato? È presente uno stato "default", se si sceglie di non fornire uno, o è uno stato vuoto accettabile?

Distinguersi Node. js sono wrestled questi problemi per molto tempo (questo è uno degli ecosistemi lingua prima di adottare MongoDB su una base su larga scala) e, ovviamente è stato attivato in con una soluzione elegante al problema, denominato MongooseJS. È un livello di software che si trova "in primo piano" di MongoDB e fornisce non solo un livello di convalida dello schema simile verificato per la lingua, ma anche un'opportunità per creare un livello di "oggetto dominio" nel codice lato server. Di conseguenza, è sorta di "l'altro sono '" nello stack MEAN.

MongooseJS: Introduzione

A questo punto, l'esercizio deve ottenere piuttosto semplice, semplice e ricorrenti: "Quale 'operazione' si npm stavolta?" La risposta breve è "npm install - Salva mongoose,", ma se ci saranno alcune difficoltà di ciò che il pacchetto esatto potrebbe essere (il nodo in cui waffle tra "cosa" e "thingjs" come nomi dei pacchetti), un "find npm" o "npm search" eseguirà la ricerca del Registro di sistema npm per i pacchetti che corrispondono a seguono le condizioni nella riga di comando. In alternativa, digitare "JS Mongoose" nel motore di ricerca di scelta consente di ottenere il sito Web Mongoose (mongoosejs.com), che avrà l'evoluzione di npm corretto, insieme a una considerevole quantità di documentazione su come usare Mongoose. (Questo rende un'operazione utile hanno salvato nel browser con certezza.)

Una volta installato, è possibile iniziare a definire gli oggetti "schema" Mongoose, che consentiranno di definire il tipo di oggetti da archiviare nella raccolta MongoDB.

Mongoosing una persona

Mongoose utilizza la terminologia interessa per i quali è essenzialmente un processo in due fasi per definire un modello a oggetti JavaScript sopra l'API di database MongoDB. In primo luogo, definiamo un "dello schema," simile a una classe tradizionale da un linguaggio basato su classi più tradizionale (c#, C++, Java o Visual Basic). Questo schema dispongono di campi, definire i tipi per questi campi e includere alcune regole di convalida dei campi per quando si assegna valori ai campi. È inoltre possibile aggiungere alcuni metodi, l'istanza o statici, si parlerà più avanti. Quindi, dopo aver definito l'oggetto dello schema, "compilarlo" in un modello che è ciò che verrà utilizzato per creare istanze di tali oggetti.

Prendere nota attenzione qui: Si tratta comunque di JavaScript e più importante, si tratta della versione di JavaScript più designato in maniera accurata come ECMAScript 5, che non dispone di alcun concetto di "classe" qualsiasi. Pertanto, anche se l'approccio Mongoose crea l'illusione che si sta definendo una "classe", è comunque la lingua basata su prototipo che si conoscono e apprezzano (o loathe). Questo è in parte il motivo per questo processo in due fasi, ovvero il primo consente di definire un oggetto che verrà utilizzato come classe, la seconda per definire un oggetto in grado di funzionare in modo implicito come "costruttore" o "factory", in base alle regole 5 JavaScript/ECMAScript per l'operatore "new".

Pertanto, la traduzione che nel codice, dopo "Richiedi ()" ing libreria Mongoose nella variabile locale consueto "mongoose" nella parte superiore del codice JavaScript, è possibile utilizzare che per definire un nuovo oggetto Schema:

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

Vi sono varie operazioni, è possibile eseguire con i campi di personSchema, ma per i principianti, manteniamo semplice. verrà non solo consentiranno all'utilizzo di codice più veloce, ma verrà evidenziato anche alcuni niceties su Mongoose lungo il percorso. Inoltre, nuovo, si noti che il processo in due fasi mediante il quale viene definito il modello: in primo luogo, si definisce lo schema, utilizzando il costruttore/funzione dello Schema e quindi passare l'oggetto dello schema in funzione di modello/costruttore, insieme al nome di questo modello. Convenzione tra gli utenti Mongoose è che l'oggetto restituito dalla chiamata del modello sarà lo stesso come tipo definito perché questo è ciò che consente di assegnare l'illusione di essere una classe.

Mongoosing in azione

Dopo aver definito il modello, ora il resta solo effettuare consiste nel riscrivere i vari metodi di routing che verranno utilizzato il modello. Il punto di avvio è la route getAllPersons, come illustrato nella Figura 1, in quanto restituisce l'intera raccolta dal database, senza filtri.

Figura 1 riscrittura 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);
    }
});
};

Si noti come il codice è fondamentalmente simile ai quali era presente prima, la query viene eseguita e richiama la funzione di callback passato (anche in questo caso, con la convenzione "err, risultato" come parametri) quando la query è stata completata. Tuttavia, Mongoose offre ora un po' più struttura per l'accesso dal routing tramite l'oggetto Personmodel, che restituirà tutti i tipi di vantaggi, come si vedrà un minuto.

Quindi, troviamo il middleware personId, perché viene utilizzato in quasi tutte le altre route, come illustrato nella Figura 2.

Figura 2 riscrittura 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'});
  }
});

Anche questa è una funzione di middleware, pertanto, l'obiettivo è individuare l'oggetto utente della raccolta e li archivia nell'oggetto della richiesta (req). Ma si noti come dopo aver verificato che personId in ingresso è un valido MongoDB ObjectId/OID; l'oggetto utente viene visualizzato, ma questa volta la chiamata a findById, passando l'ObjectID/OID passato. Maggior interesse è qui che Mongoose supporta la sintassi e stile "Promessa" che sarà una fixture nelle versioni future di JavaScript, l'oggetto restituito da findById è un oggetto Promise, su cui è possibile richiamare "then" e passare un callback da eseguire al termine della query la configurazione.

Questo approccio in due passaggi consente quindi di eseguire tutta una gamma di operazioni con la query prima dell'esecuzione, come ordinare i risultati della query in un ordine specifico in base ai campi all'interno dei risultati o selezionare solo un sottoinsieme dei campi (in modo da nascondere informazioni riservate che non deve ricevere il client). Queste sarebbero chiamate al metodo inserite destra tra ricerca e quindi in modo fluido, ad esempio:

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

In questo caso, questo sarebbe ordinare i risultati firstName in ordine crescente, ordinare i risultati in base a lastName in senso decrescente (non che questa operazione rende molto senso) e quindi eliminare gli elementi tranne "firstName", i campi "lastName" e "status". L'elenco completo dei modificatori"query" è sorprendente e include una serie di metodi utili, ad esempio limite (tagliato in un determinato numero di risultati), ignorare (per saltare i primi n risultati restituiti), (per restituire un conteggio aggregato di documenti restituiti) e così via.

Prima ottenere troppo distrarre dai che, tuttavia, sarà arrotondare il resto dei metodi di route.

Quelle che utilizzato il middleware per ottenere l'oggetto utente in questione per update e delete, diventare piuttosto semplici utilizzi di salvataggio ed eliminare metodi forniti da Mongoose sugli oggetti stessi. Come illustrato nella Figura 3, inserire una nuova persona appena richiede un'istanza di un nuovo modello di persona e utilizzando la funzione Salva metodo su di esso.

Figura 3 Creazione di un nuovo modello di persona

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);
    }
  });
};

Concettualmente, risulta molto simile alla versione precedente, non Mongoose, ma si verifica un cambiamento importante, se impercettibile,: logica su persona in corso ulteriori localizzata per il modello di persona (e l'implementazione di persistenza del Mongoose).

Convalida mongoose

A questo punto, solo per grins, si desidera aggiungere molte nuove regole per l'applicazione: firstName né lastName può essere vuoto e lo stato può essere solo uno dei diversi valori possibili (in un tipo enumerato). Si tratta in cui la possibilità di Mongoose per creare un modello a oggetti dominio all'interno di lato server può essere particolarmente potente, perché contiene classico pensare O O che questi tipi di regole devono essere incapsulati all'interno del tipo di oggetto stesso e non il codice circostante utilizzo.

Con Mongoose, si tratta in effetti piuttosto semplice. All'interno dell'oggetto schema, i campi passare dal semplice nome: digitare le coppie di nome più complesse: coppie descrittore di oggetto e le regole di convalida possono essere specificate in questi descrittori di oggetto. Oltre alle serie consueto di convalida di tipo numeric (min e max) e la convalida della stringa (lunghezza min e max length), è possibile specificare una matrice di valori accettabili per il campo "status" e definire un valore predefinito per ogni campo quando non è specificato, quale (non sorprende) accuratamente soddisfa i requisiti di nuovo, come illustrato nella Figura 4.

Figura 4 Mongoose convalida

// 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);

Non è necessario modificare in qualunque altra parte della base di codice, ovvero tutte le regole sulla persona ness vengono acquisite esattamente dove saranno, nella definizione dell'oggetto dello schema.

Se si tenta di inserire JSON, che non rispettano l'elenco a destra degli Stati, ad esempio:

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

Salva metodo chiamato all'interno della route insertPerson restituirà una risposta 500, con il corpo JSON restituito lettura come illustrato nella Figura 5.

Figura 5 errore risultato di errori di convalida del salvataggio

{
  "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"
    }
  }
}

Che nails sostanzialmente la causa dell'errore non esiste.

Mongoose Methoding

Naturalmente, orientamento agli oggetti comporta l'unione di stato e il comportamento, il che significa che questi oggetti di dominio non siano effettivamente gli oggetti a meno che non è possibile collegare i metodi di istanze di un oggetto o la "classe" nel suo complesso. In questo modo è abbastanza semplice: Si chiama un metodo Mongoose (metodo di un metodo tradizionale basato su istanza) o statico per un elemento basato su classi che definiscono il metodo e passare la funzione da utilizzare come corpo del metodo, come illustrato di seguito:

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

È non esattamente come si scrive una classe in c#, ma è piuttosto chiudere. Si noti che insieme a tutte le altre modifiche per gli oggetti dello Schema, queste devono essere eseguite prima che lo Schema viene compilato nell'oggetto modello, pertanto questa chiamata deve precedere la chiamata mongoose.model.

Controllo delle versioni mongoose

Per inciso, se si richiede la lettura rapida a /persons nuovamente, l'output risultante contiene un evento imprevisto un po':

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

Il campo "__v", Mongoose automaticamente inserita in combinazione (e che mantiene fino al database) è un campo di controllo delle versioni, noto all'interno del campo versionKey, Mongoose e Mongoose consente riconosca le modifiche apportate al documento. considerarlo come un numero di versione in un file di codice sorgente, allo scopo di rilevamento di modifiche simultanee. In genere, questo è solo un dettaglio interno di Mongoose, ma può essere un po' sorprendente per visualizzarlo visualizzata è la prima volta.

Tuttavia, che genera una domanda più interessante: Non è insolito per i sistemi tenere traccia di quando è stato creato un documento o quando sono state apportate modifiche a un documento e sarebbe certamente una seccatura per compilare i campi ogni volta che qualsiasi parte del codice circostante modificati o salvati uno di questi elementi.

Mongoose può rivelarsi utile, consentendo "hook" alcuni metodi del ciclo di vita per aggiornare i campi a destra prima che il documento è scritto in MongoDB, indipendentemente dalla chiamata che è la persistenza, come illustrato nella Figura 6.

Figura 6 hook dei metodi del ciclo di vita con Mongoose

// 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);

A questo punto, ogni volta che viene costruito un oggetto Person, esso verrà aperto il campo creato per contenere la data/ora corrente e il campo aggiornato verrà impostato per la data/ora corrente prima di inviati a MongoDB per l'archiviazione. Mongoose chiama questi "middleware", perché sono nello spirito alle funzioni middleware definito da Express, ma non fare errori, queste sono specifiche e contenuti completamente all'oggetto definito Mongoose.

A questo punto, ogni volta che una persona viene creata o aggiornata, disporrà di tali campi corrispondenti compilati e aggiornati in base alle esigenze. Inoltre, non qualcosa di più sofisticata (ad esempio, un log di controllo completo) è necessario, che è abbastanza facile vedere come questo può essere aggiunto. Invece di creare/aggiornare i campi, è un campo di auditLog sarebbe una matrice, e la funzione hook "Salva" semplicemente aggiungere più volte su quella matrice per descrivere le operazioni eseguite ogni volta e/o chi ha, a seconda dei requisiti.

Avvolgendo

Questo è stato un altro componente relativamente pesante, ed è certamente molto di Mongoose che vale la pena esplorare. Tuttavia, che è metà il divertimento di esplorazione di un nuovo stack di tecnologia e della piattaforma e sarebbe estremamente churlish di me non includere questo tipo di divertente my lettori fedeli. La cosa principale da tenere presente è che questa coppia di Mongoose + MongoDB fornisce una potente combinazione: la natura non ha uno schema del database MongoDB è associata alla lingua a livello di convalida dei tipi di base, oltre a runtime a livello di convalida dei valori di dati per ottenere il meglio di entrambe le tipizzato in modo statico e dinamico tipizzato mondi. Non è senza problemi e aspetti problematici, ma in generale, è difficile per me immaginare svolgere alcuna operazione di codifica grave senza simile Mongoose mi impedisce di eventuali operazioni stupidi in MongoDB.

Ho quasi terminato sul lato server delle operazioni, ma sono di spazio, per ora... buona codifica!


Ted Newardè un consulente polytechnology basato su Seattle, relatore e mentore. Ha scritto oltre 100 articoli, è un MVP c#, relatore di INETA, ha creato e coautore di numerosi libri. Contattarlo all'indirizzo ted@tedneward.com se si è interessati a far lui provenire collaborano con il team o leggere il suo blog all'indirizzo blogs.tedneward.com.

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Shawn Wildermuth