Comment organiser une application de nœud qui utilise Sequelize?

125

Je recherche un exemple d'application nodejs qui utilise l'ORM sequelize.

Ma principale préoccupation est qu'il semble presque impossible de définir vos modèles dans des fichiers js séparés si ces modèles ont des relations complexes entre eux à cause des boucles de dépendance require (). Peut-être que les gens définissent tous leurs modèles dans un seul fichier qui est très très long?

Je m'intéresse principalement à la manière dont les modèles sont définis et utilisés dans l'application. Je voudrais avoir une confirmation que ce que je fais tout seul est la «bonne» façon de faire les choses.

mkoryak
la source
2
J'ai ajouté un exemple qui pourrait aider quelqu'un github.com/shaishab/sequelize-express-example
Shaishab Roy
J'ai écrit un article sur notre solution: medium.com/@ismayilkhayredinov/…
hypeJunction

Réponses:

125

La petite histoire

L'astuce dans ce cas n'est pas d'initialiser le modèle dans le fichier mais simplement de fournir les informations nécessaires à son initialisation et de laisser un module centralisé s'occuper de la configuration et de l'instanciation des modèles.

Les étapes sont donc:

  • Avoir plusieurs fichiers de modèle avec des données sur le modèle, comme des champs, des relations et des options.
  • Avoir un module singleton qui charge tous ces fichiers et configure toutes les classes et relations de modèle.
  • Configurez votre module singleton dans le fichier app.js.
  • Obtenez les classes de modèle à partir du module singleton n'utiliser sur vos fichiers de modèle, chargez les modèles de la singleton à la place.require

L'histoire plus longue

Voici une description plus détaillée de cette solution avec le code source correspondant:

http://jeydotc.github.io/blog/2012/10/30/EXPRESS-WITH-SEQUELIZE.html

EDIT: C'est une très vieille réponse! (lire pour plus d'informations)

C'est vieux et limité à bien des égards!

  • Tout d'abord , comme @jinglesthula l'a mentionné dans les commentaires (et je l'ai expérimenté aussi) - il y a des problèmes pour exiger ces fichiers. C'est parce que requireça ne marche pas de la même manière que readdirSync!

  • Deuxièmement - vous très limité dans les relations - le code ne fournit pas d' options à ces associations afin que vous êtes IMPOSSIBLE de créer belongsToManycomme il a besoin de la throughpropriété. Vous pouvez créer les assocs les plus basiques.

  • Troisièmement , vous êtes très limité dans les relations modèles! Si vous lisez attentivement le code, vous verrez que les relations sont un objet au lieu d'un tableau , donc si vous voulez faire plus d'une association du même type (comme avoir deux fois belongsTo) - vous ne pouvez pas!

  • Quatrièmement - Vous n'avez pas besoin de ce truc unique. Chaque module de nodejs est singleton par lui-même, donc tout cela est assez complexe sans raison.

Vous devriez voir la réponse de Farm! (Le lien vers l'article est rompu, mais je vais le réparer avec cet exemple officiel de sequelize: https://github.com/sequelize/express-example/blob/master/models/index.js - vous pouvez parcourir le projet complet pour avoir une idée de ce qui se passe).

ps J'édite ce post car il a tellement voté que les gens ne verront même pas de nouvelles réponses (comme je l'ai fait).

Modifier: il suffit de modifier le lien vers une copie du même message, mais dans une page Github

utilisateur1778770
la source
De plus, j'avais l'impression que tous les requiremodules d dans node étaient en un sens des singletons car leur code est exécuté une fois puis mis en cache, de sorte que la prochaine fois que vous en aurez besoin, vous obtiendrez une référence d'objet en cache. N'est-ce pas une vue d'ensemble?
mkoryak
1
@mkoryak, vous avez raison - tous les modules commonjs dans node sont en fait des singletons, car la valeur renvoyée est mise en cache après la première exécution. nodejs.org/api/modules.html#modules_caching
Casey Flynn
2
Ainsi, l'exemple pourrait être simplifié en supprimant la partie délicate du singleton et en mettant simplement module.exports = new OrmClass (). Je vais l'essayer, merci pour vos commentaires :)
user1778770
2
Juste au cas où quelqu'un aurait mal à la tête, je te sauverai. J'ai eu des problèmes avec le code répertorié dans l'article github centré sur les chemins. J'ai dû ajouter un. au require (comme ceci: var object = require ('.' + modelsPath + "/" + name);) et aussi mettre un return if name.indexOf ('DS_Store')> -1 dans le forEach dans la fonction init (yay OSX). J'espère que cela pourra aider.
jinglesthula
comme @jinglesthula l'a mentionné - il y a quelques changements / bogues dans l'exemple pour charger des fichiers avec un répertoire (surtout s'il est imbriqué ailleurs). J'ajouterais également la possibilité de passer des options aux relations, car elles sont très importantes (comme le nom de la clé étrangère, si elle est autorisée à être nulle, etc.)
Andrey Popov
96

SequelizeJS a un article sur son site Web qui résout ce problème.

Le lien est rompu, mais vous pouvez trouver le projet d'exemple de travail ici et le parcourir. Voir la réponse modifiée ci-dessus pour voir pourquoi c'est une meilleure solution.

Extrait de l'article:

  • models / index.js

    L'idée de ce fichier est de configurer une connexion à la base de données et de collecter toutes les définitions de modèle. Une fois que tout est en place, nous appellerons la méthode associée sur chacun des modèles. Cette méthode peut être utilisée pour associer le modèle à d'autres.

          var fs        = require('fs')
            , path      = require('path')
            , Sequelize = require('sequelize')
            , lodash    = require('lodash')
            , sequelize = new Sequelize('sequelize_test', 'root', null)
            , db        = {} 
    
          fs.readdirSync(__dirname)
            .filter(function(file) {
              return (file.indexOf('.') !== 0) && (file !== 'index.js')
            })
            .forEach(function(file) {
              var model = sequelize.import(path.join(__dirname, file))
              db[model.name] = model
            })
    
          Object.keys(db).forEach(function(modelName) {
            if (db[modelName].options.hasOwnProperty('associate')) {
              db[modelName].options.associate(db)
            }
          })
    
          module.exports = lodash.extend({
            sequelize: sequelize,
            Sequelize: Sequelize
          }, db)
Ferme
la source
12
C'est la manière dont Sequelize recommande de le faire. J'accepterais cela comme la bonne réponse.
jpotts18
3
C'est bien, mais vous ne pouvez pas utiliser un modèle à partir des méthodes d'instance d'un autre modèle, ou peut-être que j'ai raté quelque chose.
mlkmt
1
La page n'existe plus
Mike Cheel
1
Voici le lien de travail: sequelize.readthedocs.org/en/1.7.0/articles/express
chrisg86
3
@mlkmt vous pouvez! Puisque vous avez accès à la sequelizevariable dans votre fichier de modèle, vous pouvez accéder à votre autre modèle avec sequelize.models.modelName.
Guilherme Sehn
29

J'ai créé un package sequelize-connect pour aider les gens à résoudre ce problème. Il suit la convention suggérée de Sequelize ici: http://sequelize.readthedocs.org/en/1.7.0/articles/express/

De plus, il fonctionne un peu plus comme Mongoose en termes d'interface. Il vous permet de spécifier un ensemble d'emplacements où se trouvent vos modèles et vous permet également de définir une fonction de correspondance personnalisée pour correspondre à vos fichiers de modèle.

L'utilisation est essentiellement comme ceci:

var orm = require('sequelize-connect');

orm.discover = ["/my/model/path/1", "/path/to/models/2"];      // 1 to n paths can be specified here
orm.connect(db, user, passwd, options);                        // initialize the sequelize connection and models

Ensuite, vous pouvez accéder aux modèles et effectuer des séquelles comme ceci:

var orm       = require('sequelize-connect');
var sequelize = orm.sequelize;
var Sequelize = orm.Sequelize;
var models    = orm.models;
var User      = models.User;

Espérons que cela aide quelqu'un.

jspizziri
la source
3
Un lien vers un article aide un peu. Il est préférable de citer quelques documents. Afficher un extrait de code est génial ... Mais en fait, créer une bibliothèque qui résout le problème et la mettre sur NPM est fantastique et mérite plus d'amour! +1 et mettra en vedette votre projet.
Stijn de Witt
9

J'ai commencé à utiliser Sequelize dans l'application Express.js. Assez rapidement rencontré des problèmes de la nature que vous décrivez. Peut-être que je ne comprenais pas très bien Sequelize, mais pour moi, faire plus que simplement sélectionner dans une table n'était pas vraiment pratique. Et là où vous utiliseriez normalement une sélection de deux tables ou plus, ou une union en SQL pur, vous devrez exécuter des requêtes distinctes, et avec la nature asynchrone de Node, cela ajoute simplement de la complexité.

Par conséquent, j'ai abandonné l'utilisation de Sequelize. De plus, je passe de l'utilisation de TOUTES les données extraites de DB dans les modèles. À mon avis, il est préférable d'abstraire complètement l'obtention de données. Et les raisons sont - imaginez que vous n'utilisez pas seulement MySQL (dans mon cas, j'utilise MySQL et MongoDB côte à côte), mais vous pouvez obtenir vos données à partir de n'importe quel fournisseur de données et de toute méthode de transport, par exemple SQL, no-SQL, système de fichiers, API externe, FTP, SSH etc. Si vous essayiez de tout faire dans les modèles, vous finiriez par créer un code complexe et difficile à comprendre qui serait difficile à mettre à niveau et à déboguer.

Maintenant , ce que vous voulez faire est d'avoir des modèles obtenir des données à partir d' une couche qui sait où et comment l' obtenir, mais vos modèles utilisent uniquement des méthodes de l' API, par exemple fetch, save,delete etc. Et à l' intérieur de cette couche vous avez des implémentations spécifiques pour les fournisseurs de données spécifiques. Par exemple, vous pouvez demander certaines données à partir d'un fichier PHP sur une machine locale ou de l'API Facebook ou d'Amazon AWS ou d'un document HTML distant, etc.

PS certaines de ces idées ont été empruntées à Architect by Cloud9 : http://events.yandex.ru/talks/300/

mvbl fst
la source
Ce sont des arguments valables, mais je préfère éviter réimplémentant fetch, save, deleteetc. en dehors du Sequelizeétant donné que le cadre fournit déjà les moyens. Il est plus agréable, mais moins pratique d'avoir un calque de récupération séparé. Dans le même temps, vous pourriez probablement ajouter une couche d'abstraction de récupération autour de Sequelize, mais la solution est plus compliquée, pour une victoire discutable.
Zorayr le
ce tutoriel soit très utile: suite + exemple express
Lucas Do Amaral
@ mvbl-fst Vous venez de décrire une couche DAO. Supposons que vous ayez des utilisateurs dans une base de données SQL et différents utilisateurs sur le système de fichiers. Vous devriez avoir deux DAO qui résument comment obtenir chacun d'eux, puis une couche de gestion qui concatène les utilisateurs ensemble (peut-être même adapte certaines propriétés) et les renvoie à votre itinéraire (la couche de présentation).
DJDaveMark
5

Je l'ai configuré comme Farm et la documentation décrit.

Mais j'avais le problème supplémentaire que dans mes méthodes d'instance et méthodes de classe que j'attacherais aux modèles dans chaque fonction, j'aurais besoin du fichier d'index pour obtenir d'autres objets de base de données.

Résolu le problème en les rendant accessibles à tous les modèles.

var Config = require('../config/config');

 var fs = require('fs');
var path = require('path');
var Sequelize = require('sequelize');
var _ = require('lodash');
var sequelize;
var db = {};

var dbName, dbUsername, dbPassword, dbPort, dbHost;
// set above vars

var sequelize = new Sequelize(dbName, dbUsername, dbPassword, {
dialect: 'postgres', protocol: 'postgres', port: dbPort, logging: false, host: dbHost,
  define: {
    classMethods: {
        db: function () {
                    return db;
        },
        Sequelize: function () {
                    return Sequelize;
        }

    }
  }
});


fs.readdirSync(__dirname).filter(function(file) {
   return (file.indexOf('.') !== 0) && (file !== 'index.js');
}).forEach(function(file) {
  var model = sequelize.import(path.join(__dirname, file));
  db[model.name] = model;
});

Object.keys(db).forEach(function(modelName) {
  if ('associate' in db[modelName]) {
      db[modelName].associate(db);
  }
});

module.exports = _.extend({
  sequelize: sequelize,
  Sequelize: Sequelize
}, db);

Et dans le fichier modèle

var classMethods = {
  createFromParams: function (userParams) {
    var user = this.build(userParams);

    return this.db().PromoCode.find({where: {name: user.promoCode}}).then(function (code) {
        user.credits += code.credits;
                return user.save();
    });
  }

};

module.exports = function(sequelize, DataTypes) {
  return sequelize.define("User", {
  userId: DataTypes.STRING,
}, {  tableName: 'users',
    classMethods: classMethods
 });
};

Je n'ai fait cela que pour les méthodes de classe, mais vous pouvez également faire la même chose pour les méthodes d'instance.

Jacob
la source
+1 pour cette classe prototype Méthode qui renvoie la base de données. Exactement l'idée que je recherchais pour pouvoir charger les méthodes de classe pendant la définition, mais aussi pouvoir référencer n'importe quel modèle dans une méthode de classe (c'est-à-dire pour inclure des relations)
bitwit
2

Je suis le guide officiel: http://sequelizejs.com/heroku , qui a un dossier de modèles, configure chaque module dans des fichiers séparés et dispose d'un fichier d'index pour les importer et définir la relation entre eux.

Ron
la source
le lien n'est pas valide
prisar
2

Exemple de séquelle de modèle

'use strict';
const getRole   = require('../helpers/getRole')
const library   = require('../helpers/library')
const Op        = require('sequelize').Op

module.exports = (sequelize, DataTypes) => {
  var User = sequelize.define('User', {
    AdminId: DataTypes.INTEGER,
    name: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Name must be filled !!'
        },
      }
    },
    email: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Email must be filled !!'
        },
        isUnique: function(value, next) {
          User.findAll({
            where:{
              email: value,
              id: { [Op.ne]: this.id, }
            }
          })
          .then(function(user) {
            if (user.length == 0) {
              next()
            } else {
              next('Email already used !!')
            }
          })
          .catch(function(err) {
            next(err)
          })
        }
      }
    },
    password: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Password must be filled !!'
        },
        len: {
          args: [6, 255],
          msg: 'Password at least 6 characters !!'
        }
      }
    },
    role: {
      type: DataTypes.INTEGER,
      validate: {
        customValidation: function(value, next) {
          if (value == '') {
            next('Please choose a role !!')
          } else {
            next()
          }
        }
      }
    },
    gender: {
      type: DataTypes.INTEGER,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Gender must be filled !!'
        },
      }
    },
    handphone: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Mobile no. must be filled !!'
        },
      }
    },
    address: DataTypes.TEXT,
    photo: DataTypes.STRING,
    reset_token: DataTypes.STRING,
    reset_expired: DataTypes.DATE,
    status: DataTypes.INTEGER
  }, {
    hooks: {
      beforeCreate: (user, options) => {
        user.password = library.encrypt(user.password)
      },
      beforeUpdate: (user, options) => {
        user.password = library.encrypt(user.password)
      }
    }
  });

  User.prototype.check_password = function (userPassword, callback) {
    if (library.comparePassword(userPassword, this.password)) {
      callback(true)
    }else{
      callback(false)
    }
  }

  User.prototype.getRole = function() {
    return getRole(this.role)
  }

  User.associate = function(models) {
    User.hasMany(models.Request)
  }

  return User;
};


Muhammad Arief Trimanda
la source
1

Vous pouvez importer des modèles à partir d'autres fichiers avec sequelize.import http://sequelizejs.com/documentation#models-import

De cette façon, vous pouvez avoir un module singleton pour sequelize, qui charge ensuite tous les autres modèles.

En fait, cette réponse est assez similaire à la réponse de user1778770.

natrixnatrix89
la source
1
cela fonctionne-t-il avec des dépendances circulaires? Par exemple, lorsque le modèle A a un FK pour le modèle B et que le modèle be a un FK pour le modèle A
mkoryak
1

Je recherche un exemple d'application nodejs qui utilise l'ORM sequelize.

Vous pourriez être intéressé par la solution passe-partout PEAN.JS.

PEAN.JS est une solution open source JavaScript à pile complète, qui fournit un point de départ solide pour les applications basées sur PostgreSQL, Node.js, Express et AngularJS.

Le projet PEAN est un fork du projet MEAN.JS (à ne pas confondre avec MEAN.IO ou la pile MEAN générique ).

PEAN remplace MongoDB et Mongoose ORM par PostgreSQL et Sequelize. L'un des principaux avantages du projet MEAN.JS est l'organisation qu'il fournit à une pile qui comporte de nombreuses pièces mobiles.

mg1075
la source
0

Vous pouvez également utiliser une injection de dépendances qui fournit une solution élégante à cela. En voici un https://github.com/justmoon/reduct

Vahe Hovhannisyan
la source