Février 2016

Volume 31, numéro 2

Cet article a fait l'objet d'une traduction automatique.

Le travail programmeur - comment être MEAN : Au sein de MongoDB

Par Ted Neward | Février 2016

Ted NewardBienvenue, « MEANies ». (J'ai décidé semble mieux que « Nodeists » et, en outre, commençant par cette partie, nous progressions au-delà de nœud uniquement.)

Le code a atteint un point important de transition, maintenant qu'il existe des tests en place pour vérifier que la fonctionnalité reste la même, il est possible sans commencer une refactorisation graves. En particulier, le système de stockage de données en mémoire en cours peut être intéressant pour les démonstrations rapides, mais il ne va pas évoluer particulièrement bien au fil du temps. (Pour ne pas mentionner il suffit un redémarrage/redémarrage de machine virtuelle, et vous avez perdu toutes les données qui n'est pas codé en dur dans le code de démarrage.) Il est temps d'Explorer la lettre « M » dans la moyenne : MongoDB.

HU-MongoDB-unités d'organisation

Tout d'abord, il est important de reconnaître que selon la connaissance populaires, MongoDB réellement obtient son nom à partir du mot « humongous ». Si ce bit de nature d'Internet est activé ou non, il sert à souligner que MongoDB n'est pas conçu pour fournir la même fonctionnalité exacte définie comme base de données relationnelle moyenne. Pour Mongo, évolutivité rang élevé, et dans ce but, MongoDB disposé à sacrifier quelques raisons de cohérence, ajustant des possibilités de transactions ACID au sein du cluster en faveur de « cohérence éventuelle ».

Ce système peut atteindre pas jamais l'échelle du bazillions d'enregistrements. en fait, je serais très choqué si elle se situe toujours à distance de trouver des qui en ont besoin. Toutefois, MongoDB est une autre fonctionnalité qui évalue aussi élevé sur l'échelle de « this is aborder » de curiosité et qui est son modèle de données : Il est une base de données orientée document. Cela signifie que, au lieu du traditionnel tables et colonnes appliquée par le schéma de modèle relationnel, MongoDB utilise un modèle de données collectées dans des collections de documents « schéma ». Ces documents sont représentés dans JSON et que chaque document de ce type est constitué de paires nom-valeur, où les valeurs peuvent être traditionnels des types de données (chaînes, nombres entiers, les valeurs à virgule flottante, valeurs booléennes, etc.), ainsi que davantage de données « composite » tape (des tableaux de chacun des types de données simplement répertoriés, ou des objets enfant qui à leur tour peuvent avoir des paires nom-valeur). Cela signifie, offhand, que la modélisation de données sera un peu différente que vous pourriez prévoir si votre expérience uniquement est une base de données relationnelle ; Cette application est suffisamment petite maintenant que ces différences ne sera pas très sérieusement, mais c'est quelque chose à garder à l'esprit lorsque vous travaillez avec des besoins de stockage plus complexes.

Remarque : Pour étudier plus en MongoDB du point de vue du développeur .NET, consultez 201 série en trois parties de cette colonne sur MongoDB (bit.ly/1J7DjOB).

Création de données

À partir d'un point de vue conceptuel, voir la manière dont le modèle de données « personnes » sera mappée sur MongoDB est simple : il y aura une collection de « personnes » et chaque document dans qui sera un regroupement basé sur JSON de paires nom-valeur et ainsi de suite.

Et c'est pratiquement terminé. Sérieusement. Cela fait partie de la raison que les bases de données orientées document appréciez ce privilège dans la Communauté de développement, la courbe de démarrage pour obtenir des données dans leur manque ridiculement, par rapport à leurs équivalents relationnelles basée sur un schéma. Cela présente ses propres inconvénients, aussi, bien sûr, une faute de frappe et soudainement toutes les requêtes qui sont censés être basée sur « firstName » sont soudainement bientôt vides, car aucun document ne contient un champ « firstName », mais nous allons examiner quelques façons de réduire certains de ces éléments ultérieurement.

Pour l'instant, nous allons examiner l'obtention des données dans et hors de MongoDB.

Accès aux données

La première étape consiste à permettre à l'application de communiquer avec MongoDB ; qui implique, logiquement, l'installation d'un nouveau package npm appelé « mongodb ». Par conséquent, à présent, cet exercice doit vous sembler presque automatique :

npm install --save mongodb

L'outil npm sera ATTRITION via son gyrations habituelles et lorsqu'elle retourne, le pilote Node.js MongoDB est installé dans le répertoire node_modules. Si vous obtenez un avertissement de npm sur un package kerberos n'est ne pas installé (« mongodb-core@1.2.28 nécessite un homologue kerberos@~0.0 »), il s'agit d'un bogue connu et semble être corrigée en installant simplement kerberos directement via npm (« npm install kerberos »). Il ne doit pas être tous les problèmes en outre, mais bien sûr, il s'agit tout soumis à la prochaine version d'un de ces packages, c'est le bonheur de développement sous.

Ensuite, le code devra ouvrir une connexion à l'instance MongoDB. Où réside l'instance, cependant, mérite une petite discussion.

Emplacement des données

Comme indiqué dans le premier article de cette série, il existe deux options facile pour MongoDB : une est pour l'exécuter localement, ce qui est très utile pour l'expérience de développement mais pas pour l'expérience de production ; et l'autre consiste à exécuter dans le cloud, ce qui est idéal pour l'environnement de production, mais pas pour le développement. (Si je ne peux pas exécuter le code alors que je suis un avion sur mon moyen à une conférence, puis il n'est pas une expérience idéale pour les développeurs, à mon avis.) Ce n'est pas un état inhabituel des affaires et la solution est très similaire, comme pour n'importe quelle application : l'exécuter localement au cours du développement et du cloud en production ou de test.

Comme la plupart des bases de données, se connectant à MongoDB nécessite un nom DNS du serveur ou IP adresse, un nom de base de données et (éventuellement) un port à utiliser. Normalement, dans le développement, il s'agit de « localhost », le nom de la base de données et « 27017 » (valeur par défaut de MongoDB), mais les paramètres pour une instance MongoDB dans le cloud évidemment seront différents de ceux. Par exemple, le serveur et le port de mon instance Mongolab MongoDB appelée « msdn-moyenne » sont « ds054308.mongolab.com » et « 54308 », respectivement.

Le moyen le plus simple pour capturer cette divergence dans le nœud world consiste à créer un fichier JS autonome (généralement nommé config.js) et est donc requise dans le code app.js, comme suit :

// Load modules
var express = require('express'),
  bodyParser = require('body-parser'),
  debug = require('debug')('app'),
  _ = require('lodash');
// Go get your configuration settings
var config = require('./config.js');
debug("Mongo is available at",config.mongoServer,":",config.mongoPort);
// Create express instance
var app = express();
app.use(bodyParser.json());
// ... The rest as before

Ensuite, ce qui reste est pour le fichier de configuration déterminer l'environnement dans lequel cette application s'exécute ; l'approche habituelle pour effectuer cette opération dans l'environnement Node.js consiste à examiner une variable d'environnement « ENV », qui sera défini sur un des « prod », « dev », ou « test » (si un troisième environnement centré sur les questions et réponses, est en place). Par conséquent, le code de configuration doit examiner la variable d'environnement ENV et placer les valeurs dans l'objet de module exportées :

// config.js: Configuration determination
//
var debug = require('debug')('config');
debug("Configuring environment...");
// Use these as the default
module.exports = {
  mongoServer : "localhost",
  mongoPort : "27017"
};
if (process.env["ENV"] === "prod") {
  module.exports.mongoServer = "ds054308.mongolab.com";
  module.exports.mongoPort = "54308";
}

Notez l'utilisation de l'objet « processus », il s'agit d'un objet Node.js standard, toujours implicitement présent à l'intérieur de n'importe quelle application Node.js-en cours d'exécution, et la propriété « env » est utilisée pour rechercher la variable d'environnement « ENV ». (Lecteurs aigus Notez que le code de ExpressJS effectue exactement la même chose lorsque vous décidez quel port utiliser ; vous pourriez refactoriser probablement cet extrait de code pour utiliser les paramètres config.js, également, mais je laisse cela comme un exercice pour le lecteur.)

Jusque-là, tout va bien. En fait, une meilleure ; Il a créé également implicitement une séparation intéressante du code de configuration en dehors de la base de code principal.

Commençons Ajout et suppression de données.

MongoDB + Node.js

Comme la plupart des bases de données, vous devez ouvrir une connexion MongoDB, conserver cet objet et utilisez des actions suivantes sur la base de données. Par conséquent, il peut sembler une première étape évidente serait de créer cet objet comme le démarrage de l'application et le stocker globalement, comme indiqué dans Figure 1.

Figure 1 Création d'un objet dans MongoDB

// Go get your configuration settings
var config = require('./config.js');
debug("Mongo is available at ",config.mongoServer,":",config.mongoPort);
// Connect to MongoDB
var mongo = null;
var persons = null;
var mongoURL = "mongodb://" + config.mongoServer +
  ":" + config.mongoPort + "/msdn-mean";
debug("Attempting connection to mongo @",mongoURL);
MongoClient.connect(mongoURL, function(err, db) {
  if (err) {
    debug("ERROR:", err);
  }
  else {
    debug("Connected correctly to server");
    mongo = db;
    mongo.collections(function(err, collections) {
      if (err) {
        debug("ERROR:", err);
      }
      else {
        for (var c in collections) {
          debug("Found collection",collections[c]);
        }
        persons = mongo.collection("persons");
      }
    });
  }
});
// Create express instance
var app = express();
app.use(bodyParser.json());
// ...

Notez que l'appel de connexion prend une URL et un rappel — ce rappel prend un objet d'erreur et l'objet de connexion de base de données comme paramètres, comme la convention Node.js. Si la première est tout sauf undefined ou null, il s'agit d'une erreur, sinon tout se déroule imbriquent. L'URL est une URL spécifique MongoDB, à l'aide du schéma « mongodb », mais sinon ressemble beaucoup à une URL HTTP traditionnel.

Toutefois, il existe plusieurs méthodes pour ce code ne peut pas être visible dans un premier temps : Le rappel est appelé à un moment donné, une fois le reste du code de démarrage se termine, ce qui devient plus évident lorsque vous examinez la sortie imprimée de débogage, comme indiqué dans Figure 2.

Déboguer la sortie imprimée
Figure 2 déboguer la sortie imprimée

Voir comment le message « Exemple application écoute » apparaît avant le message « Connecté correctement au serveur » à partir du rappel ? Étant donné que cela se produit au démarrage de l'application, ce problème d'accès concurrentiel n'est pas critique, mais il ne va pas immédiatement, et c'est, sans aucun doute, une des parties plus difficiles de travailler avec Node.js. Il est vrai que votre serveur Node.js code ne sera jamais exécuté simultanément sur deux threads en même temps, mais cela ne signifie pas que vous n'aurez pas certains problèmes d'accès concurrentiel intéressantes passe ici. elles apparaîtront juste différents de celui que vous êtes habitué aux développeurs .NET.

En outre, comme un rappel rapide, lorsque ce code s'exécute tout d'abord par rapport à une toute nouvelle base de données MongoDB, la boucle de collections est vide, MongoDB ne créera pas les regroupements (ou même de la base de données) jusqu'à ce qu'il ne doive, ce qui se produit généralement lorsqu'un utilisateur enregistre des éléments. Une fois l'insertion terminée, MongoDB créera les structures de données et les artefacts nécessaires pour stocker les données.

Cependant, pour le moment, nous avons une connexion de base de données. Heure de mise à jour les méthodes CRUD pour l'utiliser.

Insérer

L'insertPerson utilise la méthode insert sur l'objet de collection MongoDB et, encore une fois, vous avez besoin d'un rappel à appeler avec les résultats de l'opération de base de données :

var insertPerson = function(req, res) {
  var person = req.body;
  debug("Received", person);
  // person.id = personData.length + 1;
  // personData.push(person);
  persons.insert(person, function(err, result) {
    if (err)
      res.status(500).jsonp(err);
    else
      res.status(200).jsonp(person);
  });
};

Notez le code de commentaire (à partir de la base de données version; que je migre actuellement) ; Je l'ai laissée il spécifiquement pour prouver un point. MongoDB créera un champ d'identificateur, « _id », qui est la clé primaire pour le document dans la base de données, donc mon code de générateur incroyablement inappropriée « maison » « id » est non seulement plu nécessaire, mais entièrement indésirables.

Remarquez également que la dernière instruction dans la fonction est la méthode d'insertion, avec le rappel associé. Alors que ce n'est pas nécessaire que ce soit la dernière instruction dans le bloc de fonction, il est essentiel de comprendre que la fonction insertPerson termine avant la fin de l'insertion de la base de données. La nature en fonction de rappel de Node.js est telle que vous ne souhaitez pas retourner une valeur à l'appelant jusqu'à ce que vous connaissez la réussite ou l'échec de l'opération de base de données, par conséquent, les appels à « res » ne se produisent pas n'importe où à l'extérieur du rappel. (ARPANET doit convaincre de cela en plaçant un appel de débogage après l'appel de persons.insert et une autre dans le rappel lui-même et consultez le premier feu avant le rappel.)

Récupérer tous les

Insertions nécessitent une validation, donc alors que je suis ici, je vais refactoriser getAllPersons, ce qui est juste a besoin d'une requête rapide à la collection pour rechercher tous les documents de la collection :

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

Avant de poursuivre, il existe quelques points à noter : Tout d'abord, l'appel de recherche prend un prédicat document décrivant les critères par lequel vous souhaitez interroger la collection, qui dans ce cas, je laisse comme vide ; s'il s'agissait d'une requête par prénom, ce document prédicat devra ressembler à :

"{ 'firstName':'Ted' }"

Ensuite, notez que l'objet retourné à partir de la recherche n'est pas un véritable jeu de résultats, d'où la nécessité d'appeler toArray pour la convertir en quelque chose d'utilisation. Le toArray prend un rappel et, encore une fois, chaque branche du rappel assurer à quelque chose de communiquer à l'appelant à l'aide de res.status () .jsonp.

Middleware

Avant que je peux passer, rappel de mes articles précédents, que les getPerson, updatePerson deletePerson fonctions et toutes dépendent des fonctions personId intergiciel (middleware) pour rechercher une personne par identificateur. Cela signifie que ce middleware doit être mis à jour pour interroger la collection par son champ _id (c'est-à-dire un MongoDB ObjectID, pas une chaîne!), au lieu de rechercher dans le tableau en mémoire, comme indiqué dans Figure 3.

Figure 3 mise à jour de logiciels intermédiaires pour interroger une Collection

app.param('personId', function (req, res, next, personId) {
  debug("personId found:",personId);
  if (mongodb.ObjectId.isValid(personId)) {
    persons.find({"_id":new mongodb.ObjectId(personId)})
      .toArray(function(err, docs){
        if (err) {
          debug("ERROR: personId:",err);
          res.status(500).jsonp(err);
        }
        else if (docs.length < 1) {
          res.status(404).jsonp(
            { message: 'ID ' + personId + ' not found'});
        }
        else {
          debug("person:", docs[0]);
          req.person = docs[0];
          next();
        }
      });
  }
  else {
    res.status(404).jsonp({ message: 'ID ' + personId + ' not found'});
  }
});

Le pilote MongoDB Node.js décrit une méthode findOne qui semble être le plus approprié, mais la documentation du pilote indique que comme une méthode déconseillée.

Notez que le middleware, si elle obtient un ID d'objet non valide, n'appelle pas ensuite. Il s'agit d'un moyen rapide pour enregistrer quelques lignes de code dans les différentes méthodes qui dépendent de la recherche de personnes à partir de la base de données, car si elle n'est pas un ID légitime, il ne peut pas être, disponible dans ce nouveau une erreur 404. Il en est de même si les résultats ont zéro documents (ce qui signifie que le code n'était pas dans la base de données).

Récupérer un, Delete et Update

Par conséquent, le middleware rend getPerson trivial, parce qu'il gère tous les erreurs possibles ou conditions de document non trouvé :

var getPerson = function(req, res) {
  res.status(200).jsonp(req.person);
};

Et deletePerson est presque aussi simple :

var deletePerson = function(req, res) {
  debug(“Removing”, req.person.firstName, req.person.lastName);
  persons.deleteOne({“_id”:req.person._id}, function(err, result) {
    if (err) {
      debug(“deletePerson: ERROR:”, err);
      res.status(500).jsonp(err);
    }
    else {
      res.person._id = undefined;
      res.status(200).jsonp(req.person);
    }
  });
};

Tous deux faire updatePerson assez prévisibles :

var updatePerson = function(req, res) {
  debug(“Updating”,req.person,”with”,req.body);
  _.merge(req.person, req.body);
    persons.updateOne({“_id”:req.person._id}, req.person, function(err, result) {
    if (err)
      res.status(500).jsonp(err);
    else {
      res.status(200).jsonp(result);
    }
  });
};

L'appel de la fusion, est par ailleurs, la même fonction Lodash utilisée avant pour copier les propriétés du corps de la demande sur l'objet de la personne qui a été chargé la base de données.

Synthèse

Remarquable ! Cela a été un peu plu de certaines autres dans la série, mais à ce stade, j'ai code complètement fonctionne maintenant sur une base de données MongoDB, au lieu du tableau en mémoire que j'avais utilisé lors de la simulation des. Mais il n'est pas parfaite, pas loin. Pour commencer, les fautes de frappe dans le code dans les prédicats de requête crée des erreurs d'exécution inattendues. Plus important encore, les développeurs .NET, nous sommes habitués à une sorte de « objet de domaine » à manipuler, en particulier s'il existe une sorte de validation sur les différentes propriétés de l'objet qui doit être effectuée, il n'est pas une bonne idée pour répartir ce code de validation dans l'ensemble des parties de la base de code Express. Qui se trouve sur le dossier pour la prochaine fois. Mais pour l'instant... bon codage !


Ted Newardest directeur technique d'iTrellis, une société de conseil basée à Seattle et un polytechnology. Il a écrit plus de 100 articles, est un MVP F #, l'intervenant de l'INETA et a créé ou co-écrit une dizaine d'ouvrages. Vous pouvez le contacter à l'adresse ted@tedneward.com si vous souhaitez qu'il vienne travailler avec votre équipe, ou vous pouvez lire son blog à l'adresse blogs.tedneward.com.

Merci aux experts techniques suivants d'avoir relu cet article : Shawn Wildermuth