最后写迁移
#编程 #database #rails #elixir

开始从事新功能时,假设:在餐厅清单应用程序中添加评论时,我们的肌肉记忆通常会告诉我们从生成迁移开始。大多数教程如何指导我们来支持这一点。我个人认为,在专业环境中,这不是最好的方法,我想向您展示为什么。

关于迁移的本质

迁移是我们存储库中非常具体的代码部分。它们与那里几乎所有其他代码都有很大的不同。这是一些使它们与众不同的迁移特征:

  • 它们是不变的。这是唯一永远不会更改的代码,除非您可能在修改林格的设置时。该代码的其余部分是更改的主题:您添加方法,删除未使用的东西,改善样式。但是您不会在迁移中这样做,因为它不会产生任何影响。
  • 他们永远留在代码中。除非您定期挤压它们或删除旧的迁移(您需要基于某种模式转储以做到这一点),否则随着时间的推移,迁移积累在您的migrations目录中。然后,目录很难导航,它们会使搜索结果混乱。所有这些Witohut都带来了任何真正的价值。当然,您可以查找创建数据库的列,但是您必须爬网所有最近的迁移才能检查是否以后没有更改。
  • [可选]如果您将每次运行套件(某些团队发誓)在干净的数据库上应用迁移,则它会越来越长。即使是简单的迁移也需要时间。将这几毫秒乘以一千个,您需要进行大量的等待,然后才能进行测试。
  • 他们需要在部署管道中进行特殊处理,这有时会导致周围的特殊仪式。这里的一个示例可能是一个规则,即拉请求只能包括迁移或代码更改,但这两者绝不包括在一起。顺便说一句,这是一个明智的规则。您应该考虑。

所有这些因素都区分了迁移代码,并需要特殊处理。我什至会说迁移是该代码的极其非敏捷的一部分。有了这样的异常值,也许我们应该对我们如何处理它们进行更多思考。

“经典方法”的问题

正如我之前提到的,我们经常将迁移作为构建新功能的第一步。以此评论功能为例。从架构开始是不费吹灰之力的:

ROM::SQL.migration do
  change do
    create_table(:comments) do
      primary_key :id
      foreign_key :listing_id, :listings
      column :title, String
      column :nickname, String, null: false
      column :email, String, null: false
      column :body, String, null: false
    end
  end
end

如果我们想快速推动一些工作,请保持PRS较小或遵循将代码更改和迁移分开的规则,此时,我们可能可以创建PR,使其非常快地批准,合并并移动开始实际实现功能。

这是事情开始发生发生的时候 ...

您正在不懈地在模型,控制器和表单上工作。所有这些都可以融合在一起,因此您可以旋转快速预览/登台环境并将其显示给产品所有者。玩了一下之后,他们明显说明:

为什么作为签名用户,我必须填写昵称和电子邮件?它应该从我的用户数据中获取并实际链接到我的个人资料。

好吧,如果很明显,他们为什么不首先按要求指定它,对吗?事实是,根据我的经验,人们确实很难预测一条简单的快乐道路。即使是经验丰富的PO也常常会忘记某些情况,而这些情况确实会很明显。

您可以通过创建第二个迁移来快速解决该问题:删除非编号约束并添加author_id参考。那是第二个迁移。

“好的”,您可能会问:“但是为什么不只是更改第一个迁移呢?毕竟尚未部署到生产中呢?可能是真的。根据您的设置,它可能根本没有部署,或者仅部署到某种阶段环境中,但没有促进生产。如果要突变迁移文件,则需要手动向后滚动,添加更改并再次迁移。公司的每个开发人员已经拉了main,也必须这样做。最重要的是,您必须重置每个舞台或预览环境。如果人们可以根据要求将它们旋转,可能会有很多。

无论如何,您修复了数据模型,创建了PR,获得了合并,瞧。

在产品评论的第二轮中,产品副总裁来了,并说比赛还具有“拇指向上/拇指向下”功能。我们也需要。有点刺激,您创建了第三个迁移,添加了此功能并对其余的代码进行更改。

在完成该功能的最后阶段,您刚刚雇用的驳船的UX作家说评论太无聊了。您需要更时髦的东西,例如“叮咬”。面对一系列代码词汇,不像产品词汇或进行其他更改,您决定编写第四次迁移,将comments重命名为bites

现在,该功能已完成和部署,每个人都在庆祝。但是结果我们有四个新的迁移。可以避免这种情况吗?

是什么选择。

实体方法

我有时使用的方法是逆转流。不关心数据库和存储,而是关注该功能。 从实体开始编码

实体是具有业务逻辑但不一定连接到存储层的数据结构。在铁轨背景下,这可能是从ActiveModel::Model继承的poro或类。在ROM中,实体是内置的。在长生不老药中,这可能是结构或嵌入式模式。每种技术都有代表实体的方式。

module Entities
  class Bite < ROM::Struct
    def name
      author ? author.username : nickname
    end

    def displayed_email
      author ? author.email : email
    end
  end
end

还记得我之前说过的关于如何与有形的东西打开眼睛?就我们而言,开发人员也适用于书面代码。 仅通过将一个模糊的想法纳入具体的代码中,您可能会发现一些潜在的问题,困难或改进机会。与他们一起,您可以提出更多问题,并在第一次迭代中更好地使功能更好。

完成建模后,将此实体插入视图中。只需在控制器中进行硬码即可。您可以轻松提交以这种方式进行评论列表,以及如果您的产品团队合作,则可以使用新表格和更新表格。您只需要向他们解释这是模拟数据,并且不会更新。或者,如果需要,您可以使用Phoenix LiveView之类的内容来模拟内存中的简单存储。或将其存储在Redis或其他一些短暂/示意图存储中。但是,这通常是一事无成的。仅使用模拟数据几乎总是有效的。

comments = [
  Entities::Bite.new(body: "Clean and tasty", author: current_user),
  Entities::Bite.new(body: "Disgusting", thumb: :down, nickname: "just joe", email: "owner@competition.org")
]

记住要使您的模拟有些动态。如果您检测到签名用户,请创建该用户编写的模拟评论,因此您可以证明“编辑”按钮显示。您可以将一些餐厅的餐厅精心化以显示空状态。

完成产品讨论后,您需要更多时间来编写实际的迁移,将存储插入实体,编写存储库或任何ORM所需的内容。但是您最终只有一个迁移:一个迁移:一个代表咨询后的功能状态,并在行动中看到它。

但这不是太多吗?

我不会撒谎,直接编写迁移的方法可能会稍快,需要从客户/产品方面进行较少的合作,并且抛出的代码更少。我知道这对某些程序员来说是一种精神障碍。编写您知道的代码最终会被删除的代码,感觉很糟糕,以至于他们试图像瘟疫一样避免它。如果您是这些人之一,您可能会决定尝试挑战这种本能,或者只是坚持“旧方式”。毕竟,有时不值得权衡。

但是说到权衡,“实体方法”可能会给您一些我之前没有提及的其他好处:

  • 您实际上拥有实体!许多人努力添加实体,但不知道何时在过程中介绍它们。与存储断开连接的数据结构通常是有益的,因为重新建模业务逻辑更容易,并且测试它们的数量级可以更快。尝试爱上整个业务逻辑的次秒测试运行。
  • 它让您在数据库之外思考。几年前,我开始注意到一种模式。在谈论业务概念时,人们会立即用指数,外国钥匙和数据库类型来思考。这可能是限制的。有时,一个业务实体可能会受到两个或三个数据库表的支持。有时,一个表可能会支持多个实体(即使没有STI!)。

这就是关于我写作功能的替代方法。鉴于它解决的迁移数量越来越多,其带来的额外好处,我认为至少值得一试。如果您不喜欢它,请不要强迫它。毕竟,在软件工程中始终取决于