使用随时间变化的模型为 Sequelize 制作可重现的迁移字符串?
Making reproducible string of migrations for Sequelize with models changing over time?
我正在使用 Umzug 和 Sequelize 构建我的第一个迁移系统,用于在后端使用 sequelize 和 express 的节点应用程序。
我的问题是,随着后端模型随着时间的推移而发生变化(模型被删除,一些发生变化,一些被添加),这打破了恰好使用来自 sequelize 的模型的旧迁移。示例:
假设我有一个处理 "UserStats" 的迁移#1。五个版本之后的模型 UserStats 需要从应用程序中删除,因此删除了模型并进行了新的迁移以删除 table.
现在尝试启动新的开发环境会中断,因为当新服务器尝试 运行 通过所有旧迁移时,它会尝试为第一次迁移找到模型 UserStats,但该模型不再存在。
所以根本问题是模型的版本与应用迁移状态不同步。每个迁移都需要 sequelize 模型看起来像最初创建该迁移时那样。处理此问题的最佳方法是什么?
这里有两个想法值得了解。两者都受到我在 Rails 上 Ruby 的背景的启发,所以我已经包含了一些关于 Rails 如何为上下文做的事情:
- 在 Rails 中,当您设置一个新数据库时,它并不期望 运行 每次迁移都可以使数据库正确。 Rails 还维护一个名为
schema.rb
的文件,该文件保存数据库的当前、完整、最新状态(包括具有 运行 get 的迁移名称列表)到那时为止)。因此,当您创建一个新数据库时,它只会读取 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 次迁移,最重要的是。这意味着您始终保留最近几次迁移的记录,以防您需要回滚它们或其他东西。
- Rails 世界中另一个更直接适用于您上述问题的常见做法是在迁移文件中保存模型的副本(即副本 "frozen in time",因此说话),并在您的迁移中使用该副本,而不是您的 "real" 副本,后者可能会被删除。当然,您不必包含包含所有方法等的整个模型的完整副本 - 您可以删除特定迁移不需要的任何内容。
这会使您的迁移文件变大并且有点乱,但谁在乎呢?迁移不会被编辑。他们 运行 曾经被遗忘,而且几乎被遗忘了。所以有点乱也没关系。
我还没有尝试过使用 sequelize 的第二种方法,但它对我来说效果很好 Rails。
希望其中一些有用!
感谢乔希的指点。
我的最终解决方案是继续使用 Umzug 和我一直使用的相同设置,并使用 sequelize.query.
将任何基于模型的查询替换为原始查询
Sequelize 提供了一些很好的功能来自动格式化结果,比如你的模型(JS 对象),所以你唯一需要做的就是编写查询。我的很简单 inserts/updates/deletes.
这允许我使用节点的标准迁移模式,同时仍然具有从应用程序开始到现在的可重现迁移历史,并且不依赖于更改模型,并且还使用 JS 进行迁移而不是在 SQL函数式语言。
希望这对您有所帮助。
(写一个答案,因为它有很多评论字符)
供未来用户使用的更多信息。
我使用类似于 joshua.paling 答案中的 #2 的模式:在迁移级别创建模型的副本,以获取执行迁移时数据库的外观快照。
我最后遇到了一些错误:
SequelizeAssociationError: You have used the alias children in two
separate associations. Aliased associations must have unique aliases.
解决方案是清除模型和每次执行迁移之间的关联。
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));
我正在使用 Umzug 和 Sequelize 构建我的第一个迁移系统,用于在后端使用 sequelize 和 express 的节点应用程序。
我的问题是,随着后端模型随着时间的推移而发生变化(模型被删除,一些发生变化,一些被添加),这打破了恰好使用来自 sequelize 的模型的旧迁移。示例:
假设我有一个处理 "UserStats" 的迁移#1。五个版本之后的模型 UserStats 需要从应用程序中删除,因此删除了模型并进行了新的迁移以删除 table.
现在尝试启动新的开发环境会中断,因为当新服务器尝试 运行 通过所有旧迁移时,它会尝试为第一次迁移找到模型 UserStats,但该模型不再存在。
所以根本问题是模型的版本与应用迁移状态不同步。每个迁移都需要 sequelize 模型看起来像最初创建该迁移时那样。处理此问题的最佳方法是什么?
这里有两个想法值得了解。两者都受到我在 Rails 上 Ruby 的背景的启发,所以我已经包含了一些关于 Rails 如何为上下文做的事情:
- 在 Rails 中,当您设置一个新数据库时,它并不期望 运行 每次迁移都可以使数据库正确。 Rails 还维护一个名为
schema.rb
的文件,该文件保存数据库的当前、完整、最新状态(包括具有 运行 get 的迁移名称列表)到那时为止)。因此,当您创建一个新数据库时,它只会读取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 次迁移,最重要的是。这意味着您始终保留最近几次迁移的记录,以防您需要回滚它们或其他东西。
- Rails 世界中另一个更直接适用于您上述问题的常见做法是在迁移文件中保存模型的副本(即副本 "frozen in time",因此说话),并在您的迁移中使用该副本,而不是您的 "real" 副本,后者可能会被删除。当然,您不必包含包含所有方法等的整个模型的完整副本 - 您可以删除特定迁移不需要的任何内容。
这会使您的迁移文件变大并且有点乱,但谁在乎呢?迁移不会被编辑。他们 运行 曾经被遗忘,而且几乎被遗忘了。所以有点乱也没关系。
我还没有尝试过使用 sequelize 的第二种方法,但它对我来说效果很好 Rails。
希望其中一些有用!
感谢乔希的指点。
我的最终解决方案是继续使用 Umzug 和我一直使用的相同设置,并使用 sequelize.query.
将任何基于模型的查询替换为原始查询Sequelize 提供了一些很好的功能来自动格式化结果,比如你的模型(JS 对象),所以你唯一需要做的就是编写查询。我的很简单 inserts/updates/deletes.
这允许我使用节点的标准迁移模式,同时仍然具有从应用程序开始到现在的可重现迁移历史,并且不依赖于更改模型,并且还使用 JS 进行迁移而不是在 SQL函数式语言。
希望这对您有所帮助。
(写一个答案,因为它有很多评论字符)
供未来用户使用的更多信息。
我使用类似于 joshua.paling 答案中的 #2 的模式:在迁移级别创建模型的副本,以获取执行迁移时数据库的外观快照。
我最后遇到了一些错误:
SequelizeAssociationError: You have used the alias children in two separate associations. Aliased associations must have unique aliases.
解决方案是清除模型和每次执行迁移之间的关联。
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));