1

我正在使用 Umzug 和 Sequelize 为在后端使用 sequelize 和 express 的节点应用程序构建我的第一个迁移系统。

我的问题是,随着后端模型随着时间的推移提交而发生变化(模型被删除、一些变化、一些被添加),这会破坏碰巧使用来自 sequelize 的模型的旧迁移。例子:

假设我有一个处理“UserStats”的迁移#1。五个版本之后的模型 UserStats 需要从应用程序中删除,因此模型被删除并进行新的迁移以删除表。

现在尝试启动新的开发环境会中断,因为当新服务器尝试运行所有旧迁移时,它会尝试为第一次迁移找到模型 UserStats,但该模型不再存在。

所以根本问题是模型的版本与应用迁移状态不同步。每次迁移都要求 sequelize 模型看起来像最初创建迁移时所做的那样。处理这个问题的最佳方法是什么?

4

3 回答 3

2

这里有两个值得了解的想法。两者的灵感都来自我在 Ruby on Rails 中的背景,所以我包含了一些关于 Rails 如何在上下文中实现它的内容:

  1. 在 Rails 中,当您设置一个新数据库时,它并不期望每次都运行一次迁移,以获取正确的数据库。Rails 还维护一个名为的文件schema.rb,该文件保存数据库的当前、完整、最新状态(包括为达到该点而运行的迁移名称的列表)。因此,当您创建一个新数据库时,它只会读取schema.rb文件中的任何内容并创建这些表。

然后,rails 将在该数据库上运行 FUTURE 迁移,继续前进。但是在创建最新版本之前已经运行的那些schema.rb- 好吧,您根本不需要它们。事实上,一旦它们在需要的地方运行,您可以根据需要删除它们。

现在,sequelize 不这样做(尽管我希望它这样做!)。但是,您可以经常将整个数据库结构转储为 SQL。将其保存为例如。20170427051240-initial-structure.sql,在您的迁移文件中,时间戳记为它包含的最新迁移之后的一秒。所以,现在你有了类似 Rail 的东西scehma.rb

下一步是:编辑直接在该时间戳之前运行的迁移,以便它所做的只是导入整个数据库结构。像这样的东西:

'use strict';

module.exports = {
  up: function(queryInterface, Sequelize) {
    if (!['test', 'ci'].includes(process.env.NODE_ENV)) {
      // In all other environments, the database will already have been set up, so
      // we don't need to import the full structure
      return;
    }
    return queryInterface.sequelize.query(`
      BIG FAT STRING OF SQL GOES HERE.
      Note: in my app I've actually got it done table by table,
      in separate chained promises, but don't remember why.
    `)
  }
}

好的。因此,现在您可以删除该迁移之前的所有迁移 - 因为无论如何该迁移都会处理之前运行的所有迁移。

有一些考虑,例如。你可能想保存20170427051240-initial-structure.sql,然后等待一两个月,让另外 15 次迁移累积,然后执行上述步骤,这样设置一个新数据库就会像一个月前一样导入初始结构,在第一次迁移中,然后在此之上运行最近的 15 次迁移。这意味着您始终保留最后几次迁移的记录,以防您需要回滚它们或其他什么。

  1. Rails 世界中另一个更直接适用于上述问题的常见做法是将模型的副本保存在迁移文件中(即“及时冻结”的副本,可以这么说),并在您的迁移,而不是您的“真实”迁移,它可能会被删除。当然,您不必包含整个模型的完整副本以及所有方法等 - 您可以删除特定迁移不需要的任何内容。

这会使您的迁移文件变大而且有点混乱,但谁在乎呢?不编辑迁移。它们只运行一次,而且大部分都被遗忘了。因此,它们是否有点乱也没关系。

我还没有尝试过使用 sequelize 的第二种方法,但它对我来说在 Rails 中效果很好。

希望其中一些有用!

于 2017-05-18T10:52:48.393 回答
0

感谢乔希的指点。

我的最终解决方案是继续使用 Umzug 和我一直在使用的相同设置,并使用 sequelize.query 将任何基于模型的查询替换为原始查询。

Sequelize 提供了一些很好的功能来自动格式化结果,比如你的模型(JS 对象),所以你唯一要做的就是编写查询。我的是非常简单的插入/更新/删除。

这允许我使用节点的标准迁移模式,同时仍然具有从应用程序开始到现在的可重现迁移历史,而不依赖于更改模型,并且还使用 JS 进行迁移,而不是在 SQL 函数语言中操作数据。

希望这会有所帮助。

于 2017-05-28T22:25:37.167 回答
0

(写一个答案,因为它有很多字符的评论)

为未来的用户提供更多信息。

我使用类似于 joshua.paling 答案中的 #2 的模式:在迁移级别创建模型的副本,以获取迁移执行时数据库应该是什么样子的快照。

我最终遇到了一些错误:

SequelizeAssociationError:您在两个单独的关联中使用了别名子项。别名关联必须具有唯一的别名。

解决方案是清除模型以及每次迁移执行之间的关联。

function clearModels(sequelize) {
  Object.keys(sequelize.models).forEach(m => {
    Object.keys(sequelize.models[m].associations).forEach(a => {
      delete sequelize.models[m].associations[a];
    });
    delete sequelize.models[m];
  });
}
function clearCache(sequelize) {
  sequelize.importCache = {};
}

function migrated(sequelize) {
  return (name, migration) => {
    clearModels(sequelize);
    clearCache(sequelize);
  };
}

const {umzug, sequelize} = getUmzug();
umzug.on('migrated', migrated(sequelize));
umzug.on('reverted', migrated(sequelize));
于 2019-03-25T17:47:39.283 回答