Wie organisiere ich eine Knoten-App, die Sequelize verwendet?

125

Ich suche nach einer Beispiel-NodeJS-App, die das Sequelize-ORM verwendet.

Mein Hauptanliegen ist, dass es nahezu unmöglich erscheint, Ihre Modelle in separaten js-Dateien zu definieren, wenn diese Modelle aufgrund von require () - Abhängigkeitsschleifen komplexe Beziehungen zueinander haben. Vielleicht definieren die Leute alle ihre Modelle in einer Datei, die sehr, sehr lang ist?

Ich interessiere mich hauptsächlich dafür, wie die Modelle in der gesamten App definiert und verwendet werden. Ich hätte gerne eine Bestätigung, dass das, was ich alleine mache, die "gute" Art ist, Dinge zu tun.

mkoryak
quelle
2
Ich habe ein Beispiel , dass jemand helfen kann github.com/shaishab/sequelize-express-example
Shaishab Roy
Ich habe einen Artikel über unsere Lösung geschrieben: medium.com/@ismayilkhayredinov/…
hypeJunction

Antworten:

125

Die Kurzgeschichte

Der Trick in diesem Fall besteht nicht darin, das Modell in zu initialisieren der Datei sondern lediglich die für die Initialisierung erforderlichen Informationen bereitzustellen und ein zentrales Modul die Einrichtung und Instanziierung des Modells übernehmen zu lassen.

Die Schritte sind also:

  • Verfügen Sie über mehrere Modelldateien mit Daten zum Modell, z. B. Feldern, Beziehungen und Optionen.
  • Haben Sie ein Singleton-Modul, das alle diese Dateien lädt und alle Modellklassen und Beziehungen einrichtet.
  • Richten Sie Ihr Singleton-Modul in der Datei app.js ein.
  • Holen Sie sich die Modellklassen aus dem Singleton - Modul nicht verwenden , requireauf dem Modelldatei, laden Sie die Modelle aus dem Singleton statt.

Die längere Geschichte

Hier ist eine detailliertere Beschreibung dieser Lösung mit dem entsprechenden Quellcode:

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

EDIT: Dies ist eine sehr alte Antwort! (Informationen lesen)

Es ist alt und in vielerlei Hinsicht begrenzt!

  • Erstens , wie @jinglesthula in den Kommentaren erwähnt hat (und ich habe es auch erlebt), gibt es Probleme, diese Dateien zu benötigen. Es ist, weil requirees nicht so funktioniert wie readdirSync!

  • Zweitens - Ihre Beziehungen sind sehr eingeschränkt - bietet der Code diesen Zuordnungen keine Optionen , sodass Sie nicht erstellen können, belongsToManywenn er throughEigenschaften benötigt . Sie können die grundlegendsten Assocs machen.

  • Drittens - Sie sind in Modellbeziehungen sehr eingeschränkt! Wenn Sie den Code genau lesen, werden Sie feststellen, dass Relationen ein Objekt anstelle eines Arrays sind. Wenn Sie also mehr als eine Zuordnung desselben Typs herstellen möchten (z. B. zweimal belongsTo), können Sie dies nicht!

  • Viertens - Sie brauchen dieses Singleton-Ding nicht. Jedes Modul in nodejs ist für sich genommen Singleton, daher ist alles ohne Grund ziemlich komplex.

Sie sollten Farms Antwort sehen! (Der Link zum Artikel ist defekt, aber ich werde ihn mit diesem offiziellen Beispiel von sequelize beheben: https://github.com/sequelize/express-example/blob/master/models/index.js - Sie können das durchsuchen ganzes Projekt, um eine Vorstellung davon zu bekommen, was los ist).

ps Ich bearbeite diesen Beitrag, da er so positiv bewertet ist, dass die Leute nicht einmal neue Antworten sehen (wie ich).

Bearbeiten: Ich habe gerade den Link zu einer Kopie desselben Beitrags geändert, jedoch auf einer Github-Seite

user1778770
quelle
Außerdem hatte ich den Eindruck, dass alle required-Module im Knoten in gewisser Weise Singletons waren, da der darin enthaltene Code einmal ausgeführt und dann zwischengespeichert wird, sodass Sie beim nächsten Mal eine zwischengespeicherte Objektreferenz erhalten. Ist das nicht das ganze Bild?
Mkoryak
1
@mkoryak, Sie haben Recht - alle commonjs-Module im Knoten sind effektiv Singletons, da der zurückgegebene Wert nach der ersten Ausführung zwischengespeichert wird. nodejs.org/api/modules.html#modules_caching
Casey Flynn
2
Das Beispiel könnte also vereinfacht werden, indem der knifflige Singleton-Teil entfernt und einfach module.exports = new OrmClass () eingefügt wird. Ich werde es ausprobieren, danke für dein Feedback :)
user1778770
2
Nur für den Fall, dass jemand die Kopfschmerzen hatte, die ich hatte, werde ich dich retten. Ich hatte Probleme mit dem im Github-Artikel aufgeführten Code, der sich um Pfade drehte. Ich musste ein hinzufügen. auf die Anforderung (wie folgt: var object = require ('.' + modelsPath + "/" + name);) und geben Sie auch eine return if name.indexOf ('DS_Store')> -1 in forEach in der init-Funktion ein (yay OSX). Hoffentlich hilft das.
Jinglesthula
wie @jinglesthula erwähnt - es gibt einige Änderungen / Fehler im Beispiel zum Laden von Dateien mit Verzeichnis (insbesondere wenn es woanders verschachtelt ist). Ich würde auch die Möglichkeit hinzufügen, Optionen an die Relationen zu übergeben, da diese sehr wichtig sind (wie der Name des Fremdschlüssels, wenn er null sein darf usw.)
Andrey Popov
96

SequelizeJS hat einen Artikel auf seiner Website, der dieses Problem löst.

Der Link ist defekt, aber Sie können das funktionierende Beispielprojekt hier finden und es durchsuchen. In der bearbeiteten Antwort oben erfahren Sie, warum dies eine bessere Lösung ist.

Auszug aus dem Artikel:

  • models / index.js

    Die Idee dieser Datei ist es, eine Verbindung zur Datenbank zu konfigurieren und alle Modelldefinitionen zu sammeln. Sobald alles vorhanden ist, rufen wir die Methode auf, die jedem der Modelle zugeordnet ist. Diese Methode kann verwendet werden, um das Modell anderen zuzuordnen.

          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)
Bauernhof
quelle
12
Auf diese Weise empfiehlt Sequelize dies. Ich würde dies als die richtige Antwort akzeptieren.
jpotts18
3
Das ist gut, aber Sie können kein Modell aus den Instanzmethoden eines anderen Modells verwenden, oder ich habe etwas verpasst.
mlkmt
1
Die Seite existiert nicht mehr
Mike Cheel
1
Hier ist der Arbeitslink: sequelize.readthedocs.org/en/1.7.0/articles/express
chrisg86
3
@mlkmt kannst du! Da Sie Zugriff auf die sequelizeVariable in Ihrer Modelldatei haben, können Sie mit auf Ihr anderes Modell zugreifen sequelize.models.modelName.
Guilherme Sehn
29

Ich habe ein Paket erstellt sequelize-connect erstellt , um Menschen bei der Bewältigung dieses Problems zu helfen. Es folgt der von Sequelize vorgeschlagenen Konvention hier:http://sequelize.readthedocs.org/en/1.7.0/articles/express/

Außerdem funktioniert es in Bezug auf die Benutzeroberfläche ein bisschen mehr wie Mongoose. Sie können eine Reihe von Speicherorten angeben, an denen sich Ihre Modelle befinden, und eine benutzerdefinierte Übereinstimmungsfunktion definieren, die Ihren Modelldateien entspricht.

Die Verwendung ist im Grunde wie folgt:

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

Dann können Sie auf die Modelle zugreifen und wie folgt fortfahren:

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

Hoffentlich hilft das jemandem.

jspizziri
quelle
3
Das Verknüpfen mit einem Artikel hilft ein bisschen. Einige Dokumente zu zitieren ist besser. Ein Code-Snippet zu zeigen ist großartig ... Aber tatsächlich ist es fantastisch, eine Bibliothek zu erstellen, die das Problem löst und auf NPM veröffentlicht, und verdient mehr Liebe! +1 und wird Ihr Projekt markieren.
Stijn de Witt
9

Ich habe angefangen, Sequelize in der Express.js-App zu verwenden. Schon bald stießen wir auf Probleme der Art, die Sie beschreiben. Vielleicht habe ich Sequelize nicht ganz verstanden, aber für mich war es nicht wirklich praktisch, Dinge zu tun, die mehr als nur aus einer Tabelle auszuwählen. Und wo Sie normalerweise die Auswahl aus zwei oder mehr Tabellen oder eine Vereinigung in reinem SQL verwenden würden, müssten Sie separate Abfragen ausführen, und mit der asynchronen Natur von Node wird nur die Komplexität erhöht.

Deshalb habe ich mich von Sequelize verabschiedet. Außerdem wechsle ich von der Verwendung von JEDEM Datenabruf aus der Datenbank in den Modellen. Meiner Meinung nach ist es besser, Daten vollständig zu abstrahieren. Und Gründe sind - stellen Sie sich vor, Sie verwenden nicht nur MySQL (in meinem Fall MySQL und MongoDB nebeneinander), sondern Sie können Ihre Daten von jedem Datenanbieter und jeder Transportmethode abrufen, z. B. SQL, No-SQL, Dateisystem, externe API, FTP, SSH usw. Wenn Sie versuchen würden, alles in den Modellen zu tun, würden Sie schließlich komplexen und schwer verständlichen Code erstellen, der schwer zu aktualisieren und zu debuggen wäre.

Nun , was Sie tun möchten , ist es, Modelle von Daten aus einer Schicht erhalten haben, der weiß , wo und wie sie es zu bekommen, aber Ihre Modelle nur API - Methoden verwenden, zB fetch, save, deleteusw. Und innerhalb dieser Schicht haben Sie spezifische Implementierungen für bestimmte Datenanbieter. Sie können beispielsweise bestimmte Daten von einer PHP-Datei auf einem lokalen Computer oder von der Facebook-API oder von Amazon AWS oder von einem entfernten HTML-Dokument usw. anfordern.

PS: Einige dieser Ideen wurden von Cloud9 von Architect übernommen : http://events.yandex.ru/talks/300/

mvbl fst
quelle
Dies sind gültige Punkte, aber ich würde lieber vermeiden Neuimplementierung fetch, save, deleteusw. außerhalb Sequelizegegeben , dass der Rahmen bereits die Mittel zur Verfügung stellt. Es ist schöner, aber weniger bequem, eine separate Abrufschicht zu haben. Gleichzeitig könnten Sie wahrscheinlich eine Abstraktionsebene zum Abrufen von Sequelize hinzufügen, aber dann ist die Lösung komplizierter, um einen streitbaren Gewinn zu erzielen.
Zorayr
Dieses Tutorial ist sehr hilfreich: Fortsetzung + Express-Beispiel
Lucas Do Amaral
@ mvbl-fst Du hast gerade eine DAO-Ebene beschrieben. Angenommen, Sie haben einige Benutzer in einer SQL-Datenbank und verschiedene Benutzer im Dateisystem. Sie sollten zwei DAOs haben, die abstrahieren, wie jeder von ihnen abgerufen werden soll, und dann eine Geschäftsschicht, die die Benutzer miteinander verkettet (möglicherweise sogar einige Eigenschaften anpasst) und sie an Ihre Route (die Präsentationsschicht) zurückgibt.
DJDaveMark
5

Ich habe es als Farm eingerichtet und die Dokumentation beschreibt.

Ich hatte jedoch das zusätzliche Problem, dass ich in meinen Instanzmethoden und Klassenmethoden, die ich an die Modelle in jeder Funktion anhängen würde, die Indexdatei benötigen würde, um andere Datenbankobjekte zu erhalten.

Es wurde gelöst, indem sie allen Modellen zugänglich gemacht wurden.

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

Und in der Modelldatei

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

Ich habe dies nur für die Klassenmethoden getan, aber Sie können das Gleiche auch für Methoden tun.

Jacob
quelle
+1 für diesen Prototyp classMethod, der die Datenbank zurückgibt. Genau die Idee, nach der ich gesucht habe, um classMethods während des Definierens laden zu können, aber auch auf jedes Modell in einer ClassMethod verweisen zu können (dh um Beziehungen
einzuschließen
2

Ich folge dem offiziellen Handbuch: http://sequelizejs.com/heroku , das einen Modellordner hat, richte jedes Modul in separaten Dateien ein und habe eine Indexdatei, um sie zu importieren und die Beziehung zwischen ihnen festzulegen.

Ron
quelle
Link ist nicht gültig
Prisar
2

Beispielmodell sequelize

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

Sie können Modelle aus anderen Dateien mit http://sequelizejs.com/documentation#models-import importierensequelize.import

Auf diese Weise können Sie ein Singleton-Modul für die Fortsetzung haben, das dann alle anderen Modelle lädt.

Eigentlich ist diese Antwort der Antwort von user1778770 ziemlich ähnlich.

natrixnatrix89
quelle
1
funktioniert das mit zirkulären Abhängigkeiten? Zum Beispiel, wenn Modell A eine FK zu Modell B hat und Modell be eine FK zu Modell A hat
mkoryak
1

Ich suche nach einer Beispiel-NodeJS-App, die das Sequelize-ORM verwendet.

Vielleicht interessieren Sie sich für die PEAN.JS Boilerplate-Lösung.

PEAN.JS ist eine Full-Stack-JavaScript-Open-Source-Lösung, die einen soliden Ausgangspunkt für PostgreSQL-, Node.js-, Express- und AngularJS-basierte Anwendungen bietet.

Das PEAN-Projekt ist eine Abzweigung des MEAN.JS-Projekts (nicht zu verwechseln mit MEAN.IO oder dem generischen MEAN-Stack).

PEAN ersetzt MongoDB und das Mongoose ORM durch PostgreSQL und Sequelize. Ein Hauptvorteil des MEAN.JS-Projekts ist die Organisation eines Stapels mit vielen beweglichen Teilen.

mg1075
quelle
0

Sie können auch eine Abhängigkeitsinjektion verwenden, die eine elegante Lösung dafür bietet. Hier ist eine https://github.com/justmoon/reduct

Vahe Hovhannisyan
quelle