จะจัดระเบียบแอปโหนดที่ใช้ sequelize ได้อย่างไร?


125

ฉันกำลังมองหาแอป nodejs ตัวอย่างที่ใช้ ORM ต่อเนื่อง

ข้อกังวลหลักของฉันคือดูเหมือนว่าจะเป็นไปไม่ได้ที่จะกำหนดโมเดลของคุณในไฟล์ js แยกกันหากโมเดลเหล่านั้นมีความสัมพันธ์ที่ซับซ้อนซึ่งกันและกันเนื่องจากต้องใช้ลูปการพึ่งพา () อาจมีคนกำหนดโมเดลทั้งหมดในไฟล์เดียวที่ยาวมาก?

ฉันสนใจเป็นหลักว่าโมเดลถูกกำหนดและใช้งานผ่านแอพอย่างไร ฉันต้องการให้มีการตรวจสอบความถูกต้องว่าสิ่งที่ฉันทำด้วยตัวเองเป็นวิธีที่ "ดี" ในการทำสิ่งต่างๆ


2
ฉันได้เพิ่มตัวอย่างที่อาจช่วยใครบางคนgithub.com/shaishab/sequelize-express-example
Shaishab Roy

ฉันได้เขียนบทความเกี่ยวกับโซลูชันของเราแล้ว: medium.com/@ismayilkhayredinov/…
hypeJunction

คำตอบ:


125

เรื่องสั้น

เคล็ดลับในกรณีนี้ไม่ใช่การเริ่มต้นโมเดลในไฟล์ แต่เพียงเพื่อให้ข้อมูลที่จำเป็นสำหรับการเริ่มต้นและปล่อยให้โมดูลส่วนกลางดูแลการตั้งค่าโมเดลและการสร้างอินสแตนซ์

ดังนั้นขั้นตอนคือ:

  • มีไฟล์ Model หลายไฟล์ที่มีข้อมูลเกี่ยวกับโมเดลเช่นฟิลด์ความสัมพันธ์และตัวเลือก
  • มีโมดูลซิงเกิลตันที่โหลดไฟล์เหล่านั้นทั้งหมดและตั้งค่าคลาสโมเดลและความสัมพันธ์ทั้งหมด
  • ตั้งค่าโมดูลซิงเกิลของคุณที่ไฟล์ app.js
  • รับคลาสโมเดลจากโมดูลซิงเกิลตันที่ไม่ได้ใช้requireกับไฟล์โมเดลของคุณโหลดโมเดลจากซิงเกิลตันแทน

เรื่องยาว

นี่คือคำอธิบายโดยละเอียดเพิ่มเติมของโซลูชันนี้พร้อมซอร์สโค้ดที่เกี่ยวข้อง:

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

แก้ไข: นี่เป็นคำตอบที่เก่ามาก! (อ่านข้อมูล)

มันเก่าและมีข้อ จำกัด ในหลาย ๆ ด้าน!

  • ก่อนอื่นตามที่ @jinglesthula กล่าวไว้ในความคิดเห็น (และฉันก็ประสบปัญหาเช่นกัน) - มีปัญหาในการต้องใช้ไฟล์เหล่านั้น เป็นเพราะrequireไม่ได้ผลเช่นเดียวกับreaddirSync!

  • ประการที่สอง - คุณมีความสัมพันธ์อย่างจำกัด - รหัสไม่มีตัวเลือกให้กับการเชื่อมโยงเหล่านั้นดังนั้นคุณจึงไม่สามารถสร้างได้belongsToManyตามที่ต้องการthroughคุณสมบัติ คุณสามารถสร้างการเชื่อมโยงขั้นพื้นฐานที่สุดได้

  • สาม - คุณมีความสัมพันธ์แบบจำลองที่ จำกัด มาก! หากคุณอ่านโค้ดอย่างละเอียดคุณจะเห็นว่าความสัมพันธ์เป็นObjectแทนที่จะเป็นArrayดังนั้นหากคุณต้องการสร้างการเชื่อมโยงประเภทเดียวกันมากกว่าหนึ่งรายการ (เช่นมีสองครั้งbelongsTo) คุณไม่สามารถทำได้!

  • ประการที่สี่ - คุณไม่ต้องการสิ่งที่เป็นซิงเกิลตัน ทุกโมดูลใน nodejs เป็นซิงเกิลตันดังนั้นการสร้างทั้งหมดนี้ค่อนข้างซับซ้อนโดยไม่มีเหตุผล

คุณควรเห็นคำตอบของ Farm! (ลิงก์ไปยังบทความเสีย แต่ฉันจะแก้ไขด้วยตัวอย่างอย่างเป็นทางการจาก sequelize: https://github.com/sequelize/express-example/blob/master/models/index.js - คุณสามารถเรียกดู ทั้งโครงการเพื่อให้ทราบว่าเกิดอะไรขึ้น)

ป.ล. ฉันกำลังแก้ไขโพสต์นี้เนื่องจากมีการโหวตเพิ่มมากขึ้นจนผู้คนไม่เห็นคำตอบใหม่ ๆ (เหมือนที่ฉันเคยทำ)

แก้ไข:เพิ่งเปลี่ยนลิงค์เป็นสำเนาของโพสต์เดียวกัน แต่อยู่ในหน้า Github


นอกจากนี้ฉันยังรู้สึกว่าrequireโมดูล d ทั้งหมดในโหนดอยู่ในความหมายเดียวเนื่องจากโค้ดในนั้นถูกเรียกใช้ครั้งเดียวแล้วแคชดังนั้นในครั้งต่อไปที่คุณต้องการคุณจะได้รับการอ้างอิงอ็อบเจ็กต์ที่แคชไว้ นี่ไม่ใช่ภาพรวมเหรอ?
mkoryak

1
@mkoryak คุณพูดถูก - โมดูล commonjs ทั้งหมดในโหนดเป็น singletons อย่างมีประสิทธิภาพเนื่องจากค่าที่ส่งคืนจะถูกแคชหลังจากการดำเนินการครั้งแรก nodejs.org/api/modules.html#modules_caching
Casey Flynn

2
ดังนั้นตัวอย่างสามารถทำให้ง่ายขึ้นโดยการลบส่วนที่ยุ่งยาก singleton และใส่ module.exports = new OrmClass () ฉันจะลองดูขอบคุณสำหรับคำติชมของคุณ :)
user1778770

2
เผื่อว่ามีใครปวดหัวฉันจะช่วยคุณเอง ฉันมีปัญหากับรหัสที่แสดงในบทความ Github ที่อยู่ตรงกลางเส้นทาง ฉันต้องเพิ่มไฟล์. เป็นสิ่งที่ต้องการ (เช่นนี้: var object = ต้องใช้ ('.' + modelsPath + "/" + name);) และใส่ return if name.indexOf ('DS_Store')> -1 ใน forEach ในฟังก์ชัน init (ใช่ OSX) หวังว่าจะช่วยได้
jinglesthula

ตามที่ @jinglesthula กล่าวไว้ - มีการเปลี่ยนแปลง / ข้อบกพร่องบางอย่างในตัวอย่างสำหรับการโหลดไฟล์ที่มีไดเร็กทอรี (โดยเฉพาะอย่างยิ่งถ้ามันซ้อนอยู่ที่อื่น) ฉันจะเพิ่มความสามารถในการส่งผ่านตัวเลือกไปยังความสัมพันธ์เนื่องจากมีความสำคัญมาก (เช่นชื่อของคีย์ต่างประเทศหากอนุญาตให้เป็นโมฆะเป็นต้น)
Andrey Popov

96

SequelizeJS มีบทความในเว็บไซต์ที่ช่วยแก้ปัญหานี้ได้

ลิงก์เสีย แต่คุณสามารถค้นหาโครงการตัวอย่างที่ใช้งานได้ที่นี่และเรียกดู ดูคำตอบที่แก้ไขด้านบนเพื่อดูว่าเหตุใดจึงเป็นทางออกที่ดีกว่า

สารสกัดจากบทความ:

  • รุ่น / index.js

    แนวคิดของไฟล์นี้คือการกำหนดค่าการเชื่อมต่อกับฐานข้อมูลและรวบรวมนิยามของโมเดลทั้งหมด เมื่อทุกอย่างเข้าที่แล้วเราจะเรียกใช้วิธีการที่เกี่ยวข้องกับแต่ละรุ่น วิธีนี้สามารถใช้เพื่อเชื่อมโยง Model กับผู้อื่น

          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)

12
นี่คือวิธีที่ Sequelize แนะนำให้ทำ ฉันยอมรับว่านี่เป็นคำตอบที่ถูกต้อง
jpotts18

3
นี่เป็นสิ่งที่ดี แต่คุณไม่สามารถใช้โมเดลจากวิธีการอินสแตนซ์ของโมเดลอื่นได้หรือบางทีฉันอาจพลาดอะไรไป
mlkmt

1
หน้านี้ไม่มีอีกแล้ว
Mike Cheel

1
นี่คือลิงค์การทำงาน: sequelize.readthedocs.org/en/1.7.0/articles/express
chrisg86

3
@mlkmt ได้เลย! เนื่องจากคุณสามารถเข้าถึงsequelizeตัวแปรในไฟล์โมเดลของคุณคุณจึงสามารถเข้าถึงโมเดลอื่นของคุณได้ด้วยsequelize.models.modelName.
Guilherme Sehn

29

ฉันได้สร้างแพ็กเกจsequelize-connectเพื่อช่วยให้ผู้คนจัดการกับปัญหานี้ เป็นไปตามอนุสัญญา Sequelize ที่แนะนำที่นี่: http://sequelize.readthedocs.org/en/1.7.0/articles/express/

นอกจากนี้มันยังทำงานคล้ายกับพังพอนอีกเล็กน้อยในแง่ของอินเทอร์เฟซ ช่วยให้คุณระบุชุดของตำแหน่งที่โมเดลของคุณตั้งอยู่และยังช่วยให้คุณกำหนดฟังก์ชันการจับคู่แบบกำหนดเองเพื่อให้ตรงกับไฟล์โมเดลของคุณ

การใช้งานโดยทั่วไปจะเป็นดังนี้:

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

จากนั้นคุณสามารถเข้าถึงโมเดลและต่อเนื่องได้ดังนี้:

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

หวังว่านี่จะช่วยใครบางคนได้


3
การเชื่อมโยงไปยังบทความช่วยได้เล็กน้อย การอ้างอิงเอกสารบางอย่างจะดีกว่า การแสดงข้อมูลโค้ดนั้นยอดเยี่ยมมาก .... แต่จริงๆแล้วการสร้างไลบรารีที่แก้ปัญหาและวางไว้บน NPM นั้นยอดเยี่ยมและสมควรได้รับความรักมากกว่านี้! +1 และจะติดดาวโครงการของคุณ
Stijn de Witt

9

ฉันเริ่มใช้ Sequelize ในแอป Express.js ในไม่ช้าก็พอพบปัญหาเกี่ยวกับธรรมชาติที่คุณกำลังอธิบาย บางทีฉันอาจจะไม่ค่อยเข้าใจ Sequelize แต่สำหรับฉันการทำสิ่งต่างๆมากกว่าการเลือกจากตารางเดียวนั้นไม่สะดวกนัก และโดยปกติแล้วคุณจะใช้ตารางที่เลือกตั้งแต่สองตารางขึ้นไปหรือการรวมกันใน SQL บริสุทธิ์คุณจะต้องเรียกใช้แบบสอบถามแยกกันและด้วยลักษณะการไม่ซิงค์ของโหนดมันเป็นเพียงแค่ความซับซ้อนที่เพิ่มเข้ามา

ดังนั้นฉันจึงย้ายออกจากการใช้ Sequelize นอกจากนี้ฉันกำลังเปลี่ยนจากการใช้การดึงข้อมูลใด ๆ จาก DB ในแบบจำลอง ในความคิดของฉันมันเป็นการดีกว่าที่จะรับข้อมูลแบบนามธรรมอย่างสมบูรณ์ และเหตุผลคือ - จินตนาการว่าคุณไม่ได้ใช้แค่ MySQL (ในกรณีของฉันฉันใช้ MySQL และ MongoDB เคียงข้างกัน) แต่คุณสามารถรับข้อมูลจากผู้ให้บริการข้อมูลและวิธีการขนส่งใด ๆ เช่น SQL, no-SQL, ระบบไฟล์, API ภายนอก, FTP, SSH เป็นต้นหากคุณพยายามทำทุกอย่างในโมเดลในที่สุดคุณจะสร้างโค้ดที่ซับซ้อนและเข้าใจยากซึ่งยากต่อการอัปเกรดและดีบัก

ตอนนี้สิ่งที่คุณต้องการจะทำคือการมีรุ่นที่ได้รับข้อมูลจากชั้นที่รู้และวิธีการที่จะได้รับมัน แต่รุ่นของคุณเท่านั้นที่ใช้วิธีการ API เช่นfetch, save, deleteฯลฯ และภายในชั้นนี้คุณมีการใช้งานที่เฉพาะเจาะจงสำหรับผู้ให้บริการข้อมูลที่เฉพาะเจาะจง เช่นคุณสามารถขอข้อมูลบางอย่างจากไฟล์ PHP บนเครื่องท้องถิ่นหรือจาก Facebook API หรือจาก Amazon AWS หรือจากเอกสาร HTML ระยะไกลเป็นต้น

ปล.แนวคิดเหล่านี้บางส่วนยืมมาจากสถาปนิกโดยCloud9 : http://events.yandex.ru/talks/300/


เหล่านี้เป็นจุดที่ถูกต้อง แต่ผมค่อนข้างจะหลีกเลี่ยงการ reimplementing fetch, save, deleteฯลฯ นอกSequelizeระบุว่ากรอบที่มีอยู่แล้วให้หมายถึง ดีกว่า แต่ไม่ค่อยสะดวกที่จะมีเลเยอร์ดึงข้อมูลแยกต่างหาก ในขณะเดียวกันคุณอาจเพิ่มเลเยอร์ที่เป็นนามธรรมที่ดึงออกมารอบ ๆ Sequelize ได้ แต่การแก้ปัญหานั้นซับซ้อนกว่าสำหรับการชนะที่สามารถโต้แย้งได้
Zorayr

บทช่วยสอนนี้ช่วยได้มาก: sequelize + ตัวอย่างด่วน
Lucas Do Amaral

@ mvbl-fst คุณเพิ่งอธิบายเลเยอร์ DAO สมมติว่าคุณมีผู้ใช้บางคนใน SQL DB และผู้ใช้อื่นในระบบไฟล์ คุณควรมี DAO สองตัวซึ่งเป็นนามธรรมในการรับแต่ละคนจากนั้นเป็นเลเยอร์ธุรกิจที่เชื่อมต่อผู้ใช้เข้าด้วยกัน (อาจปรับคุณสมบัติบางอย่าง) และส่งกลับไปยังเส้นทางของคุณ (เลเยอร์การนำเสนอ)
DJDaveMark

5

ฉันตั้งค่าเป็นฟาร์มและเอกสารอธิบาย

แต่ฉันมีปัญหาเพิ่มเติมว่าในเมธอดอินสแตนซ์และคลาสเมธอดของฉันที่ฉันจะแนบไปกับโมเดลในแต่ละฟังก์ชันฉันจะต้องใช้ไฟล์ดัชนีเพื่อรับอ็อบเจ็กต์ฐานข้อมูลอื่น ๆ

แก้ไขได้โดยทำให้ทุกรุ่นสามารถเข้าถึงได้

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

และในไฟล์โมเดล

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

ฉันทำสิ่งนี้สำหรับเมธอดคลาสเท่านั้น แต่คุณสามารถทำสิ่งเดียวกันนี้ได้เช่นกันสำหรับวิธีการเช่น


+1 สำหรับคลาสต้นแบบนั้นที่ส่งคืนฐานข้อมูล ตรงกับความคิดที่ฉันกำลังมองหาเพื่อให้สามารถโหลด classMethods ระหว่างการกำหนด แต่ยังสามารถอ้างอิง Model ใด ๆ ใน ClassMethod (เช่นสำหรับการรวมความสัมพันธ์)
bitwit

2

ฉันกำลังทำตามคำแนะนำอย่างเป็นทางการ: http://sequelizejs.com/herokuซึ่งมีโฟลเดอร์ model ตั้งค่าแต่ละโมดูลในไฟล์แยกกันและมีไฟล์ดัชนีเพื่อนำเข้าและตั้งค่าความสัมพันธ์ระหว่างกัน


ลิงค์ไม่ถูกต้อง
prisar

2

โมเดลตัวอย่างต่อเนื่อง

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



1

คุณสามารถนำเข้าโมเดลจากไฟล์อื่นด้วยsequelize.import http://sequelizejs.com/documentation#models-import

ด้วยวิธีนี้คุณสามารถมีโมดูลซิงเกิลตันสำหรับการทำภาคต่อซึ่งจะโหลดโมเดลอื่น ๆ ทั้งหมด

อันที่จริงคำตอบนี้ค่อนข้างคล้ายกับคำตอบของ user1778770


1
สิ่งนี้ใช้ได้กับการอ้างอิงแบบวงกลมหรือไม่ ตัวอย่างเช่นเมื่อรุ่น A มี FK เป็นรุ่น B และรุ่น be มี FK เป็นรุ่น A
mkoryak

1

ฉันกำลังมองหาแอป nodejs ตัวอย่างที่ใช้ ORM ต่อเนื่อง

คุณอาจสนใจดูโซลูชันสำเร็จรูปของ PEAN.JS

PEAN.JS เป็นโซลูชันโอเพนซอร์ส JavaScript แบบเต็มสแต็กซึ่งเป็นจุดเริ่มต้นที่มั่นคงสำหรับแอปพลิเคชันที่ใช้ PostgreSQL, Node.js, Express และ AngularJS

โครงการ PEAN เป็นส่วนแยกของโครงการ MEAN.JS (เพื่อไม่ให้สับสนกับ MEAN.IO หรือMEAN stack ทั่วไป )

PEAN แทนที่ MongoDB และ Mongoose ORM ด้วย PostgreSQL และ Sequelize ประโยชน์หลักของโครงการ MEAN.JS คือองค์กรที่จัดเตรียมให้กับสแต็กที่มีชิ้นส่วนเคลื่อนไหวจำนวนมาก


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.