Screen_Shot_2014-05-30_at_1.37.33_PM

智能工程博客

Bye Bye STI, Hello Discriminable Model

发布的兰迪·伯克

2015年7月24日上午11:26:14


在Salsify,我们管理产品信息在制造商和分销商之间的流动,通过大型零售商店,如沃尔玛和谷歌Shopping。我们灵活的模式允许用户以他们认为合适的方式描述他们的产品。我们将产品属性存储在单个表中,但每个产品属性都有可能不同的数据类型和关联的特定于应用程序的逻辑。

当我们第一次引入产品属性的概念时,我们只支持文本和选择列表数据类型。最初的实现存在于单个活动记录模型类中,对于每一个新的数据类型,特别是那些引入了独特的新功能(如图像)的数据类型,我们很快意识到“一刀切”的属性模型无法扩展。对具有不同应用程序逻辑的模型层次结构建模的一个常见选择是使用单表继承(STI)。

性传播感染的使用通常是少数警告陷阱,但是仍然有一些情况,比如我们的属性表,似乎完全符合设计的用例。Rails的STI实现涉及到指定列(默认为type),它存储了类型化子类的字符串化类名。我们的产品属性数据模型没有在任何地方存储类名,因此我们只好编写迁移来支持向单表继承的转换。

问题

我们怎么做有效地向我们最大和最广泛使用的表中引入一个模型类的层次结构?

虽然迁移通常编写起来很简单,但部署起来通常只会稍微复杂一点,但当我们认为可能需要迁移时,我们喜欢考虑我们的选择。这是清单我们想到的可能方法:

  • 引入另一列来存储子类:
    • 在我们最大的表中添加和填充列需要迁移。
    • 维护数据类型和类类型的同步的容易出错的应用程序逻辑。
    • 要求我们在数据库中存储不必要的代码细节(类名),不希望耦合。
  • 重写应用程序代码,将类存储在数据类型列中:
    • 需要将存储的数据类型迁移到类名。与不希望的代码-数据库耦合相同的问题。
    • 需要重写大量使用数据类型(或应用程序代码将类转换为数据类型)的元编程,以支持对数据模型的更改。
  • 利用我们的数据类型列已经包含了所有必要的信息来确定一个子类,而不使用rails STI的事实:
    • 重新发明框架概念很容易出错,并在未来的升级中引入潜在的摩擦。
    • 可以在不更改数据模型的情况下完成。
    • 不需要我们在数据库中存储/维护代码的细节。

经过一番思考后,我们决定采用第三种方案。在不需要更改数据模型(或重写一堆应用程序逻辑)的情况下添加一个完整的类层次结构似乎非常有趣。另外,它不需要停机或复杂的在线数据迁移!!

解决方案

这个概念很简单;指定一个模型属性,该属性可用于确定所需的实例类型,并与rails模型实例化挂钩,以确保我们构建适当的类型。类active_record / inheritance.rb包括了我们需要的大部分接口(discriminate_class_for_recordsubclass_from_attributes),在做了一些工作之后,我们得到了一个通用的关注点,DiscriminableModel

在实践中,它最终看起来像这样:

在rails从数据库加载数据之后,它会调用一系列回调函数,其中一个回调函数钩子到我们上面指定的继承类中。我们的担心超过了要求discriminate_class_for_record并使用定义的类型列的值调用配置的标识符。discriminator可以是接受单个参数的任何块;我在我们的示例中,我们根据数据类型(如'image')进行区分,并返回一个类,例如。ImageProductAttribute.我们的实现(链接如下)包括一些您可能期望的其他不错的特性,比如能够通过查询只查找图像属性,例如ImageProductAttribute.where(…)

结论

我们已经使用可识别模型几个月了,并注意到一些我们最初没有预料到的额外功能。

  • 模型移动/重命名更不容易出错,而且不需要迁移。只是对鉴别器的更新。
  • 改进原型变更时的敏捷性,因为可以在不改变数据模型的情况下快速地向模型添加一个区别子类。想为有/没有为关联存储ID的模型引入区别类型吗?你能做到的!
  • 改进了建立在区别列上的索引的效率。整数枚举比字符串化的类名更紧凑,并提供更快的比较。

到目前为止,我们支持9种数据类型,包括但不限于图像、文本、选择列表、日期、数字和html。特定于数据类型的逻辑被恰当地封装在子类中,我们的数据库不需要任何理解或代码,所有这些都无需对数据模型进行任何更改即可完成。我们还在等一些可鉴别的模型在我们美化它之前,但如果你愿意看一下源代码让我们知道你的想法,我们很乐意听到你的想法!


评论的Disqus

最近的帖子

    Baidu