¿Cómo organizar una aplicación de nodo que usa la secuencia?

125

Estoy buscando un ejemplo de aplicación nodejs que utiliza el ORM de secuencia.

Mi principal preocupación es que parece casi imposible definir sus modelos en archivos js separados si esos modelos tienen relaciones complejas entre sí debido a los bucles de dependencia require (). ¿Quizás las personas definen todos sus modelos en un archivo que es muy, muy largo?

Estoy interesado principalmente en cómo se definen y usan los modelos a través de la aplicación. Me gustaría tener alguna validación de que lo que estoy haciendo por mi cuenta es la forma "buena" de hacer las cosas.

mkoryak
fuente
2
Agregué
Shaishab Roy
He escrito un artículo sobre nuestra solución: medium.com/@ismayilkhayredinov/…
hypeJunction

Respuestas:

125

La historia corta

El truco en este caso no es inicializar el modelo en el archivo, sino simplemente proporcionar la información necesaria para su inicialización y dejar que un módulo centralizado se encargue de la configuración e instanciación de los modelos.

Entonces los pasos son:

  • Tenga varios archivos de modelo con datos sobre el modelo, como campos, relaciones y opciones.
  • Tenga un módulo singleton que cargue todos esos archivos y configure todas las clases y relaciones del modelo.
  • Configure su módulo singleton en el archivo app.js.
  • Obtenga las clases de modelo del módulo singleton que no utilice requireen sus archivos de modelo, cargue los modelos desde el singleton.

La historia mas larga

Aquí hay una descripción más detallada de esta solución con el código fuente correspondiente:

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

EDITAR: ¡Esta es una respuesta muy antigua! (lea abajo para obtener información)

¡Es viejo y limitado de muchas maneras!

  • Primero , como @jinglesthula mencionó en los comentarios (y también lo experimenté), hay problemas al requerir esos archivos. ¡Es porque requireno funciona de la misma manera que readdirSync!

  • En segundo lugar , está muy limitado en las relaciones: el código no proporciona opciones a esas asociaciones, por lo que no puede crearlo belongsToManyya que necesita throughpropiedad. Puedes hacer las asociaciones más básicas.

  • Tercero : ¡eres muy limitado en las relaciones modelo! Si lee detenidamente el código, verá que las relaciones son un Objeto en lugar de una Matriz , por lo que si desea hacer más de una asociación del mismo tipo (como tener dos veces belongsTo), ¡no puede!

  • Cuarto : no necesitas esa cosita singleton. Cada módulo en nodejs es singleton en sí mismo, por lo que todo esto es bastante complejo sin ninguna razón.

¡Deberías ver la respuesta de Farm! (El enlace al artículo está roto, pero lo arreglaré con esta muestra oficial de la secuencia: https://github.com/sequelize/express-example/blob/master/models/index.js ; puede navegar por el proyecto completo para tener una idea de lo que está pasando).

PD: Estoy editando esta publicación porque está tan votada que la gente ni siquiera verá nuevas respuestas (como yo lo hice).

Editar: solo cambié el enlace a una copia de la misma publicación, pero en una página de Github

usuario1778770
fuente
Además, tenía la impresión de que todos los requiremódulos d en el nodo eran en cierto sentido simples porque el código en ellos se ejecuta una vez y luego se almacena en caché, por lo que la próxima vez que los necesite obtendrá una referencia de objeto en caché. ¿No es esta la imagen completa?
mkoryak 01 de
1
@mkoryak, tiene razón: todos los módulos commonjs en el nodo son, de hecho, únicos, ya que el valor devuelto se almacena en caché después de la primera ejecución. nodejs.org/api/modules.html#modules_caching
Casey Flynn
2
Por lo tanto, el ejemplo podría simplificarse eliminando la parte difícil de singleton y simplemente poniendo module.exports = new OrmClass (). Lo probaré, gracias por tus comentarios :)
user1778770
2
En caso de que alguien tuviera el dolor de cabeza que yo tenía, te salvaré. Tuve problemas con el código enumerado en el artículo de github que se centró en las rutas. Tuve que agregar a. a require (así: var object = require ('.' + modelsPath + "/" + name);) y también devuelve if name.indexOf ('DS_Store')> -1 en forEach en la función init (yay OSX). Espero que ayude.
jinglesthula
como mencionó @jinglesthula: hay algunos cambios / errores en la muestra para cargar archivos dentro del directorio (especialmente si está anidado en otro lugar). También agregaría la posibilidad de pasar opciones a las relaciones, ya que son muy importantes (como el nombre de la clave externa, si se permite que sea nula, etc.)
Andrey Popov
96

SequelizeJS tiene un artículo en su sitio web que resuelve este problema.

El enlace está roto, pero puede encontrar el proyecto de muestra de trabajo aquí y navegarlo. Vea la respuesta editada arriba para ver por qué esta es una mejor solución.

Extracto del artículo:

  • models / index.js

    La idea de este archivo es configurar una conexión a la base de datos y recopilar todas las definiciones del Modelo. Una vez que todo esté en su lugar, llamaremos al método asociado en cada uno de los Modelos. Este método se puede utilizar para asociar el modelo con otros.

          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)
Granja
fuente
12
Esta es la forma en que Sequelize recomienda hacerlo. Aceptaría esto como la respuesta correcta.
jpotts18
3
Esto es bueno, pero no puede usar un modelo de los métodos de instancia de otro modelo, o tal vez me perdí algo.
mlkmt
1
La página ya no existe
Mike Cheel
1
Aquí está el enlace de trabajo: sequelize.readthedocs.org/en/1.7.0/articles/express
chrisg86
3
@mlkmt puedes! Como tiene acceso a la sequelizevariable en su archivo de modelo, puede acceder a su otro modelo con sequelize.models.modelName.
Guilherme Sehn
29

Creé un paquete sequelize-connect para ayudar a las personas a lidiar con este problema. Sigue la convención sugerida de Sequelize aquí: http://sequelize.readthedocs.org/en/1.7.0/articles/express/

Además, también funciona un poco más como Mongoose en términos de su interfaz. Le permite especificar un conjunto de ubicaciones donde se encuentran sus modelos y también le permite definir una función de coincidencia personalizada para que coincida con sus archivos de modelo.

El uso es básicamente así:

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

Luego puede acceder a los modelos y secuenciar así:

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

Esperemos que esto ayude a alguien.

jspizziri
fuente
3
Vincular a un artículo ayuda un poco. Citar algunos documentos es mejor. Mostrar un fragmento de código es genial ... ¡Pero construir una biblioteca que resuelva el problema y ponerlo en NPM es fantástico y merece más amor! +1 y protagonizará tu proyecto.
Stijn de Witt
9

Empecé a usar Sequelize en la aplicación Express.js. Muy pronto se toparon con problemas de la naturaleza que estás describiendo. Tal vez no entendí bien Sequelize, pero para mí hacer cosas más que simplemente seleccionar de una tabla no era realmente conveniente. Y donde normalmente usaría select de dos o más tablas, o una unión en SQL puro, tendría que ejecutar consultas separadas, y con la naturaleza asíncrona de Node es solo complejidad añadida.

Por lo tanto, me alejé de usar Sequelize. Además, estoy cambiando de usar CUALQUIER búsqueda de datos de DB en los modelos. En mi opinión, es mejor abstraer la obtención de datos por completo. Y las razones son: imagine que no solo usa MySQL (en mi caso, uso MySQL y MongoDB lado a lado), sino que puede obtener sus datos de cualquier proveedor de datos y cualquier método de transporte, por ejemplo, SQL, no-SQL, sistema de archivos, API externa, FTP, SSH, etc. Si intentara hacerlo todo en los modelos, eventualmente crearía un código complejo y difícil de entender que sería difícil de actualizar y depurar.

Ahora lo que quiere hacer es tener modelos de obtener datos de una capa que se sabe dónde y cómo conseguirlo, pero sus modelos utilizar métodos de la API, por ejemplo fetch, save, deleteetc. Y dentro de esta capa que tienen implementaciones específicas para los proveedores de datos específicos. Por ejemplo, puede solicitar ciertos datos de un archivo PHP en una máquina local o de la API de Facebook o de Amazon AWS o de un documento HTML remoto, etc.

PS algunas de estas ideas se tomó de Arquitecto por Cloud9 : http://events.yandex.ru/talks/300/

mvbl fst
fuente
Estos son puntos válidos, pero preferiría evitar reimplementar fetch, save, deleteetc. fuera del Sequelizedado que el marco ya proporciona los medios. Es mejor, pero menos conveniente tener una capa de recuperación separada. Al mismo tiempo, probablemente podría agregar una capa de abstracción para buscar alrededor de Sequelize, pero luego la solución es más complicada, para una victoria discutible.
Zorayr
este tutorial será de gran ayuda: ejemplo de secuelizar + expresar
Lucas Do Amaral
@ mvbl-fst Acaba de describir una capa DAO. Supongamos que tiene algunos usuarios en una base de datos SQL y diferentes usuarios en el sistema de archivos. Debería tener dos DAO que resumen cómo obtener cada uno de ellos, luego una capa empresarial que concatena a los usuarios (tal vez incluso adapta algunas propiedades) y los devuelve a su ruta (la capa de presentación).
DJDaveMark
5

Lo configuré como Granja y la documentación lo describe.

Pero estaba teniendo el problema adicional de que en mis métodos de instancia y métodos de clase que asociaría a los modelos en cada función, necesitaría requerir el archivo de índice para obtener otros objetos de la base de datos.

Lo resolvió haciéndolos accesibles a todos los modelos.

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

Y en el archivo del modelo

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

Solo hice esto para los métodos de clase, pero también podría hacer lo mismo para los métodos de ejemplo.

jacob
fuente
+1 para ese prototipo classMethod que devuelve el db. Exactamente la idea que estaba buscando para poder cargar classMethods durante la definición pero también poder hacer referencia a cualquier Modelo en un ClassMethod (es decir, para incluir relaciones)
bitwit
2

Estoy siguiendo la guía oficial: http://sequelizejs.com/heroku , que tiene una carpeta de modelos, configura cada módulo en archivos separados y tiene un archivo de índice para importarlos y establecer la relación entre ellos.

Ron
fuente
el enlace no es válido
prisar
2

Secuencia de modelo de muestra

'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
fuente
1

Puede importar modelos de otros archivos con sequelize.import http://sequelizejs.com/documentation#models-import

De esa manera, puede tener un módulo singleton para la secuencia, que luego carga todos los demás modelos.

En realidad, esta respuesta es bastante similar a la respuesta del usuario 1778770.

natrixnatrix89
fuente
1
¿Funciona esto con dependencias circulares? Por ejemplo, cuando el modelo A tiene un FK al modelo B y el modelo be tiene un FK al modelo A
mkoryak
1

Estoy buscando un ejemplo de aplicación nodejs que utiliza el ORM de secuencia.

Tal vez le interese buscar la solución estándar PEAN.JS.

PEAN.JS es una solución de código abierto JavaScript de pila completa, que proporciona un punto de partida sólido para aplicaciones basadas en PostgreSQL, Node.js, Express y AngularJS.

El proyecto PEAN es una bifurcación del proyecto MEAN.JS (no debe confundirse con MEAN.IO o la pila genérica MEAN).

PEAN reemplaza MongoDB y Mongoose ORM con PostgreSQL y Sequelize. Un beneficio principal del proyecto MEAN.JS es la organización que proporciona a una pila que tiene muchas piezas móviles.

mg1075
fuente