Jak zorganizować aplikację node, która używa sequelize?

125

Szukam przykładowej aplikacji nodejs, która wykorzystuje sekwencjonowanie ORM.

Moim głównym zmartwieniem jest to, że wydaje się niemożliwe zdefiniowanie modeli w oddzielnych plikach js, jeśli modele te mają złożone relacje między sobą z powodu pętli zależności require (). Może ludzie definiują wszystkie swoje modele w jednym bardzo długim pliku?

Interesuje mnie głównie to, jak modele są definiowane i używane w aplikacji. Chciałbym uzyskać potwierdzenie, że to, co robię sam, jest „dobrym” sposobem robienia rzeczy.

mkoryak
źródło
2
Dodałem przykład, który może komuś pomóc github.com/shaishab/sequelize-express-example
Shaishab Roy
Napisałem artykuł o naszym rozwiązaniu: medium.com/@ismayilkhayredinov/ ...
hypeJunction

Odpowiedzi:

125

Krótka historia

Sztuczka w tym przypadku nie polega na zainicjowaniu modelu w pliku, ale po prostu na dostarczeniu niezbędnych informacji do jego inicjalizacji i pozwoleniu scentralizowanemu modułowi zająć się konfiguracją i instancją modeli.

Oto kroki:

  • Miej kilka plików modelu z danymi o modelu, takimi jak pola, relacje i opcje.
  • Mieć pojedynczy moduł, który ładuje wszystkie te pliki i konfiguruje wszystkie klasy modelu i relacje.
  • Skonfiguruj swój moduł singleton w pliku app.js.
  • Pobierz klasy modelu z modułu singleton , którego nie używaj requirew plikach modelu, zamiast tego załaduj modele z singletona.

Dłuższa historia

Oto bardziej szczegółowy opis tego rozwiązania wraz z odpowiednim kodem źródłowym:

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

EDYCJA: To bardzo stara odpowiedź! (przeczytaj informacje)

Jest stary i ograniczony na wiele sposobów!

  • Po pierwsze , jak @jinglesthula wspomniał w komentarzach (i ja też tego doświadczyłem) - są problemy z wymaganiem tych plików. To dlatego, requireże nie działa tak samo jak readdirSync!

  • Po drugie - jesteś bardzo ograniczony w relacjach - kod nie zapewnia opcji dla tych asocjacji, więc NIE MOŻESZ tworzyć, belongsToManyponieważ wymaga throughwłasności. Możesz zrobić najbardziej podstawowe zestawy.

  • Po trzecie - jesteś bardzo ograniczony w modelowych relacjach! Jeśli czytasz uważnie kod, widać, że stosunki to obiekt zamiast tablicy , więc jeśli chcesz mieć więcej niż jednego skojarzenia tego samego typu (jak o dwa razy belongsTo) - nie można!

  • Po czwarte - nie potrzebujesz tego singletona. Każdy moduł w nodejs jest sam w sobie singletonem, więc wszystko to sprawia, że ​​jest dość skomplikowane bez powodu.

Powinieneś zobaczyć odpowiedź Farmy! (Link do artykułu jest uszkodzony, ale naprawię to za pomocą tej oficjalnej próbki z sequelize: https://github.com/sequelize/express-example/blob/master/models/index.js - możesz przeglądać cały projekt, aby zorientować się, co się dzieje).

ps Edytuję ten post, ponieważ jest tak pozytywnie przyjęty, że ludzie nawet nie zobaczą żadnych nowych odpowiedzi (tak jak ja).

Edycja: Właśnie zmieniłem link na kopię tego samego posta, ale na stronie Github

user1778770
źródło
Miałem również wrażenie, że wszystkie requiremoduły d w węźle były w pewnym sensie singletonami, ponieważ kod w nich jest wykonywany raz, a następnie buforowany, więc następnym razem, gdy ich potrzebujesz, otrzymujesz buforowane odwołanie do obiektu. Czy to nie jest cały obraz?
mkoryak
1
@mkoryak, masz rację - wszystkie moduły commonjs w węźle są w rzeczywistości singletonami, ponieważ zwracana wartość jest buforowana po pierwszym wykonaniu. nodejs.org/api/modules.html#modules_caching
Casey Flynn
2
Tak więc przykład można uprościć, usuwając pojedynczą trudną część i po prostu wstaw module.exports = new OrmClass (). Wypróbuję to, dziękuję za twoją opinię :)
user1778770
2
Na wypadek, gdyby ktoś miał taki ból głowy, jak ja, uratuję cię. Miałem problemy z kodem wymienionym w artykule na githubie, który dotyczył ścieżek. Musiałem dodać. do require (na przykład: var object = require ('.' + modelsPath + "/" + name);), a także umieść return if name.indexOf ('DS_Store')> -1 w forEach w funkcji init (yay OSX). Mam nadzieję, że to pomoże.
jinglesthula,
jak wspomniano @jinglesthula - w przykładzie są pewne zmiany / błędy dotyczące ładowania plików z katalogiem (szczególnie jeśli jest on zagnieżdżony gdzie indziej). Dodałbym również możliwość przekazywania opcji do relacji, ponieważ są one bardzo ważne (jak nazwa klucza obcego, jeśli jest dozwolona, ​​itp.)
Andrey Popov
96

SequelizeJS ma na swojej stronie artykuł, który rozwiązuje ten problem.

Link jest uszkodzony, ale możesz znaleźć działający przykładowy projekt tutaj i przeglądać go. Zobacz zredagowaną odpowiedź powyżej, aby zobaczyć, dlaczego jest to lepsze rozwiązanie.

Wyciąg z artykułu:

  • modele / index.js

    Ideą tego pliku jest skonfigurowanie połączenia z bazą danych i zebranie wszystkich definicji modelu. Gdy wszystko będzie gotowe, wywołamy metodę skojarzoną z każdym z modeli. Tej metody można użyć do powiązania Modelu z innymi.

          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)
Gospodarstwo rolne
źródło
12
To jest sposób, w jaki Sequelize zaleca to robić. Uznałbym to za poprawną odpowiedź.
jpotts18
3
To dobrze, ale nie możesz użyć modelu z metod instancji innego modelu, a może coś przeoczyłem.
mlkmt
1
Strona już nie istnieje
Mike Cheel
1
Oto działający link: sequelize.readthedocs.org/en/1.7.0/articles/express
chrisg86
3
@mlkmt możesz! Ponieważ masz dostęp do sequelizezmiennej w pliku modelu, możesz uzyskać dostęp do innego modelu za pomocą sequelize.models.modelName.
Guilherme Sehn
29

Stworzyłem pakiet sequelize-connect, aby pomóc ludziom poradzić sobie z tym problemem. Jest zgodny z sugerowaną konwencją Sequelize tutaj: http://sequelize.readthedocs.org/en/1.7.0/articles/express/

Dodatkowo działa trochę bardziej jak Mongoose pod względem interfejsu. Umożliwia określenie zestawu lokalizacji, w których znajdują się modele, a także pozwala zdefiniować niestandardową funkcję dopasowywania, aby dopasować pliki modelu.

Użycie jest zasadniczo takie:

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

Następnie możesz uzyskać dostęp do modeli i kontynuować w ten sposób:

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

Mam nadzieję, że to komuś pomoże.

jspizziri
źródło
3
Linkowanie do artykułu trochę pomaga. Cytowanie niektórych dokumentów jest lepsze. Pokazywanie fragmentu kodu jest świetne ... Ale tak naprawdę zbudowanie biblioteki, która rozwiązuje problem i umieszczenie jej w NPM jest fantastyczne i zasługuje na więcej miłości! +1 i oznaczy Twój projekt gwiazdką.
Stijn de Witt
9

Zacząłem używać Sequelize w aplikacji Express.js. Wkrótce natrafiłem na problemy natury, którą opisujesz. Może nie do końca rozumiałem Sequelize, ale robienie rzeczy więcej niż tylko wybieranie z jednego stołu nie było zbyt wygodne. A tam, gdzie normalnie używałbyś select z dwóch lub więcej tabel lub unii w czystym SQL, musiałbyś uruchamiać oddzielne zapytania, a przy asynchronicznej naturze Node jest to po prostu dodatkowa złożoność.

Dlatego odeszłam od używania Sequelize. Ponadto przechodzę z używania DOWOLNEGO pobierania danych z DB w modelach. Moim zdaniem lepiej jest abstrakcyjne pobieranie danych. Powody są takie - wyobraź sobie, że nie korzystasz tylko z MySQL (w moim przypadku używam MySQL i MongoDB obok siebie), ale możesz uzyskać dane od dowolnego dostawcy danych i dowolnej metody transportu, np. SQL, no-SQL, system plików, zewnętrzne API, FTP, SSH itp. Gdybyś próbował zrobić to wszystko w modelach, ostatecznie stworzyłbyś złożony i trudny do zrozumienia kod, który byłby trudny do aktualizacji i debugowania.

Teraz to, co chcesz zrobić, to mieć modele uzyskać dane z warstwy, która wie, gdzie i jak je zdobyć, ale twoje modele wykorzystują tylko metody API, na przykład fetch, save, deleteetc. i wewnątrz tej warstwy masz konkretne implementacje dla konkretnych dostawców danych. Na przykład możesz zażądać pewnych danych z pliku PHP na komputerze lokalnym lub z interfejsu API Facebooka lub z Amazon AWS lub ze zdalnego dokumentu HTML itp.

PS Niektóre z tych pomysłów zostały zapożyczone od Architect by Cloud9 : http://events.yandex.ru/talks/300/

mvbl fst
źródło
Są to ważne punkty, ale wolałbym uniknąć reimplementing fetch, save, deleteitp poza Sequelizepodano, że ramy już zapewnia środki. Jest ładniej, ale mniej wygodnie jest mieć oddzielną warstwę pobierania. W tym samym czasie prawdopodobnie mógłbyś dodać pobierającą warstwę abstrakcji dookoła Sequelize, ale wtedy rozwiązanie jest bardziej skomplikowane, co daje sporne zwycięstwo.
Zorayr
ten samouczek będzie bardzo pomocny: kontynuuj + wyraźny przykład
Lucas Do Amaral
@ mvbl-fst Właśnie opisałeś warstwę DAO. Powiedzmy, że masz kilku użytkowników w bazie danych SQL i różnych użytkowników w systemie plików. Powinieneś mieć dwa DAO, które abstrakcyjne, jak uzyskać każdy z nich, a następnie warstwę biznesową, która łączy użytkowników razem (może nawet dostosowuje niektóre właściwości) i przekazuje ich z powrotem do Twojej trasy (warstwa prezentacji).
DJDaveMark
5

Ustawiłem to jako Farmę i opisałem w dokumentacji.

Ale miałem dodatkowy problem polegający na tym, że w moich metodach instancji i metodach klas, które chciałbym dołączyć do modeli w każdej funkcji, musiałbym wymagać pliku indeksu, aby uzyskać dostęp do innych obiektów bazy danych.

Rozwiązałem to, udostępniając je wszystkim modelom.

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

Oraz w pliku modelu

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

Zrobiłem to tylko dla metod klasowych, ale możesz też zrobić to samo na przykład dla metod.

Jakub
źródło
+1 dla tego prototypu classMethod, który zwraca db. Dokładnie pomysł, którego szukałem, aby móc załadować classMethods podczas definiowania, ale także móc odwoływać się do dowolnego modelu w ClassMethod (tj. W celu uwzględnienia relacji)
bitwit
2

Kieruję się oficjalnym przewodnikiem: http://sequelizejs.com/heroku , który ma folder modeli, konfiguruję każdy moduł w osobnych plikach i mam plik indeksu, aby je zaimportować i ustawić relacje między nimi.

Ron
źródło
link jest nieprawidłowy
prisar
2

Przykładowy model 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
źródło
1

Możesz importować modele z innych plików za pomocą sequelize.import http://sequelizejs.com/documentation#models-import

W ten sposób możesz mieć jeden pojedynczy moduł do kontynuacji, który następnie ładuje wszystkie inne modele.

W rzeczywistości ta odpowiedź jest dość podobna do odpowiedzi użytkownika1778770.

natrixnatrix89
źródło
1
czy to działa z zależnościami cyklicznymi? Na przykład, gdy model A ma FK do modelu B, a model ma FK do modelu A
mkoryak
1

Szukam przykładowej aplikacji nodejs, która wykorzystuje sekwencjonowanie ORM.

Możesz być zainteresowany spojrzeniem na rozwiązanie standardowe PEAN.JS.

PEAN.JS to pełne rozwiązanie JavaScript open source, które stanowi solidny punkt wyjścia dla aplikacji opartych na PostgreSQL, Node.js, Express i AngularJS.

Projekt PEAN jest rozwidleniem projektu MEAN.JS (nie mylić z MEAN.IO lub ogólnym stosem MEAN).

PEAN zastępuje MongoDB i Mongoose ORM PostgreSQL i Sequelize. Podstawową zaletą projektu MEAN.JS jest organizacja stosu, który ma wiele ruchomych elementów.

mg1075
źródło
0

Możesz również użyć iniekcji zależności, która zapewnia eleganckie rozwiązanie tego problemu. Oto jeden https://github.com/justmoon/reduct

Vahe Hovhannisyan
źródło