【问题标题】:Sequelize with asynchronous configuration in nodejs在nodejs中使用异步配置续集
【发布时间】:2020-07-11 12:01:08
【问题描述】:

因为我在 Sequelize 中找不到有效的异步配置示例,所以几天来我一直在抨击自己的头

所以你可能知道,你可以像这样简单地配置一个 Sequelize 实例

const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname')

然后声明你的模型

const User = sequelize.define('User', {
  // Model attributes are defined here
  firstName: {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName: {
    type: DataTypes.STRING
    // allowNull defaults to true
  }
}, {
  // Other model options go here
});

但是,当 db 凭据来自外部服务时会发生什么?

const credentials = await getDbCredentials();
const sequelize = new Sequelize({credentials})


由于 sequelize 模型的创建与实例创建相结合(与许多其他 ORM 不同),这成为一个大问题。

我目前的解决方案如下:


const Sequelize = require("sequelize");

// Models
const { User } = require("./User");

const env = process.env.NODE_ENV || "development";
const db = {};

let sequelize = null;

const initSequelize = async () => {
  if (!sequelize) {
      let configWithCredentials = {};

      if (env === "production") {
        const credentials = await getDbCredentials();
        const { password, username, dbname, engine, host, port } = credentials;
        configWithCredentials = {
          username,
          password,
          database: dbname,
          host,
          port,
          dialect: engine,
          operatorsAliases: 0
        };
      }

      const config = {
        development: {
          // Dev config 
        },
        production: configWithCredentials,
      };

      sequelize = new Sequelize(config[env]);

      sequelize.authenticate().then(() => {
         console.log("db authenticated")
        });
      });
  }

  db.User = User;

  db.sequelize = sequelize;
  db.Sequelize = Sequelize;
};

initSequelize().then(() => {
  console.log("done");
});

module.exports = db;

但是我觉得这不是一个好方法,因为初始化的异步性质,有时db 是未定义的。 有没有更好的方法来处理这件事? 谢谢

【问题讨论】:

    标签: javascript node.js amazon-web-services asynchronous sequelize.js


    【解决方案1】:

    我认为你的数据库有时是未定义的,因为在你的异步函数中你没有“等待” sequelize.authenticate() 的解析。改变这个:

    sequelize.authenticate().then(() => {
             console.log("db authenticated")
            });
    

    到这里:

     await sequelize.authenticate()
    
     console.log("db authenticated")
    

    发生的事情是,您的 initSequelize 异步函数将在 sequelize.authenticate 承诺之前解决。这是 JS 中的常见陷阱。我认为这个调整会解决你的问题。关于“最好的方法”,我看不出这里有什么可以做的,但我当然没有全貌。

    【讨论】:

    • 但是异步函数一直在运行
    • 哦,我明白你的意思了....authenticate() 不需要设置续集连接。
    • 它确实会启动一个连接来测试它,但应用程序将能够在没有它的情况下使用数据库。
    • 等等..但是我对“then”问题是对还是错?让我们把续集的东西放在一边,这里有一个非常基本的东西我必须理解 :D 你的分数很高,我相信你 :D
    • 您指出的是正确的,对不起! :) 异步函数没有等待该承诺完成。在这种情况下,它恰好不会影响任何事情。
    【解决方案2】:

    sequelize 模型定义实际上只是一个普通对象,因此可以尽早设置。模型初始化确实需要传入sequelize 实例。

    在对模型使用 ES6 类定义时,设置对我来说更加清晰。 sequelize.define 被替换为对Model.init 的调用,这都可以在异步设置函数中完成。

    const Sequelize = require('sequelize')
    const { Model } = Sequelize
    
    class User extends Model {
      static get modelFields(){
        return {
          id: {
            type: Sequelize.UUID,
            primaryKey: true,
            defaultValue: Sequelize.UUIDV4,
          },
          name: {
            type: Sequelize.STRING,
            allowNull: false,
            unique: true,
          }
        }
      }
      static get modelOptions(){
        return {
          version: true,
        }
      }
      static init(sequelize){
        const options = { ...this.modelOptions, sequelize }
        return super.init(this.modelFields, options)
      }
      static associate(models) {
        this.hasMany(models.Task)
      }
    }
    
    module.exports = User
    
    const User = require('./User')
    
    class Database {
       async static setup(){
         const credentials = await getCredentials()
         this.sequelize = new Sequelize(credentials)
         User.init(this.sequelize)
         this.User = User
         // When you have multiple models to associate add:
         this.User.associate(this)
       }
    }
    
    module.exports = Database
    

    由于异步凭据要求,您的应用程序的其余部分只需要处理延迟,直到设置数据库。例如,如果这是一个 koa/express 应用程序,您可以延迟服务器 .listen(),直到 Database.setup() 承诺解决。

    【讨论】:

    • 但是每次我需要访问 db 对象时都需要运行 setup() 方法吗?
    • 我有轻微的感觉,我需要通过应用上下文传递它
    • Database.sequelizeUser 可以在连接建立后使用。
    【解决方案3】:

    因为这会改变我的很多代码。我最终在 golang 中创建了一个脚本,该脚本在运行我的服务器之前“异步”获取我的凭据。 我使用了这个包中的一些代码:https://github.com/telia-oss/aws-env

    然后将我的启动脚本作为命令参数传递,这样我就可以“继承”环境变量 ./getEnv exec -- node index.js

    【讨论】:

    • 我也有这个问题。我希望 sequelize 允许我们以异步方式传递来编写配置。 Knex 允许这样做。
    【解决方案4】:

    您可以使用 beforeConnect 挂钩来实现这一点,如下所示:

    sequelize = new Sequelize(config.database, '', '', config);
    sequelize.beforeConnect(async (config) => {
        config.username = await getSecretUsername();
        config.password = await getSecretPassword();
    });
    

    将初始凭据留空,然后使用 beforeConnect 更改配置。不确定这是否是最干净的使用方式,但似乎可以正常工作。

    https://sequelize.org/master/manual/hooks.html

    【讨论】:

    • 我认为这应该是现在公认的答案。文档明确指出“如果您需要异步获取数据库凭据,或者需要在创建低级数据库连接后直接访问它,这些钩子会很有用。”
    【解决方案5】:

    我找到了一种“纯粹”的续集方式,可以在整个生命周期中做到这一点hooks

    基本上,db.js 文件中的通用设置如下所示:

    const { Sequelize } = require('sequelize');
    const asyncFetch = require('../util/async-fetch');
    
    const sequelize = new Sequelize({
      dialect: 'mysql',
      database: 'db_name',
      host: '127.0.0.1'
    });
    
    sequelize.beforeConnect(async (config) => {
      const [username, password] = await Promise.all([
        asyncFetch('username'),
        asyncFetch('password')
      ]);
      config.username = username;
      config.password = password;
    });
    
    module.exports = sequelize;

    【讨论】:

      猜你喜欢
      • 2021-11-07
      • 1970-01-01
      • 2021-10-11
      • 1970-01-01
      • 1970-01-01
      • 2019-01-21
      • 2016-12-14
      • 1970-01-01
      • 2021-10-10
      相关资源
      最近更新 更多