gpt4 book ai didi

ruby-on-rails - 在设计 Rails 数据库模式时如何避免使用关注点?

转载 作者:搜寻专家 更新时间:2023-10-30 20:26:34 25 4
gpt4 key购买 nike

我阅读了很多博客,经常遇到的主题之一是担心(至少 Rails 定义它们的方式)正在损害软件。总的来说,我同意——简单地将行为包含到模型中违反了单一责任原则。你最终得到了一个做太多事情的神级。

但与从博客中收集的许多意见一样,很少提供替代架构。

那么让我们举一个示例应用程序,大致基于我必须维护的应用程序。它本质上是一个 CMS,就像许多 Rails 应用程序一样。

目前每个模型都有大量的关注点。让我们在这里使用一些:

class Article < ActiveRecord::Base
include Concerns::Commentable
include Concerns::Flaggable
include Concerns::Publishable
include Concerns::Sluggable
...
end

您可以想象“可评论”只需要在文章中添加少量代码。足以与评论对象建立关系并提供一些实用方法来访问它们。

Flaggable,允许用户标记不当内容,最终会向模型添加一些字段:例如 flagged、flagged_by、flagged_at。以及一些添加功能的代码。

Sluggable 添加了一个用于在 URL 中引用的 slug 字段。还有一些代码。

Publishable 添加发布日期和状态字段,以及更多代码。

现在如果我们添加一种新的内容会怎样?

class Album < ActiveRecord::Base
include Concerns::Flaggable
include Concerns::Sluggable
include Concerns::Publishable
...
end

专辑有点不同。您不能对它们发表评论,但您仍然可以发布它们并标记它们。

然后我添加更多内容类型:比方说事件和个人资料。

我发现这个架构目前存在一些问题:

  • 我们有多个具有完全相同字段的数据库表(flagged_by, published_on 等)
  • 我们无法使用单个 SQL 查询同时检索多种内容类型。
  • 每个模型都支持包含关注点的重复字段名称,为每个类赋予多重职责。

那么什么是更好的方法呢?

我看到装饰器被提升为在需要的地方添加功能的一种方式。这可能有助于解决包含代码的问题,但不一定会改进数据库结构。它还看起来不必要地繁琐,并且涉及向代码添加额外的循环来修饰模型数组。

目前我的想法是这样的:

创建一个通用的“内容”模型(和表格):

class Content < ActiveRecord::Base
end

关联表可能很小。它可能应该有某种类型的“类型”字段,并且可能有一些对所有内容都通用的东西——比如 URL 的类型 slug。

然后我们可以为每个行为创建一个关联模型,而不是添加关注点:

class Slug < ActiveRecord::Base
belongs_to :content
...
end

class Flag < ActiveRecord::Base
belongs_to :content
...
end

class Publishing < ActiveRecord::Base
belongs_to :content
...
end

class Album < ActiveRecord::Base
belongs_to :content
...
end
...

这些中的每一个都与一个内容相关联,因此外键可以存在于特征的模型中。所有与该功能相关的行为也可以单独存在于该功能的模型上,这让 OO 纯粹主义者更开心。

为了实现通常需要模型 Hook 的行为(例如 before_create),我认为观察者模式更有用。 (一旦发送“content_created”事件等,就会创建一个 slug。)

这看起来会无休止地清理一切。我现在可以使用单个查询搜索所有内容,数据库中没有重复的字段名称,也不需要将代码包含到内容模型中。

在我愉快地在我的下一个项目中释放它之前,有没有人尝试过这种方法?行得通吗?或者将事情拆分成这么多最终会创建大量的 SQL 查询、连接和困惑的代码?你能推荐一个更好的选择吗?

最佳答案

关注点基本上只是混合模式的一层薄薄的包装。对于围绕可重用​​特性编写软件片段而言,这是一种非常有用的模式。

单表继承

跨多个模型具有相同列的问题通常通过单表继承来解决。然而,STI 仅在模型非常相似时才真正适合。

那么让我们考虑一下您的 CMS 示例。我们有几种不同类型的内容:

Page, NewsArticle, BlogPost, Gallery

它们具有几乎相同的数据库字段:

id
title
content
timestamps
published_at
published_by
# ...

因此我们决定摆脱重复并使用公用表。将其称为 contents 很诱人,但这非常含糊 - content 的内容 ...

因此让我们复制 Drupal 并调用我们的通用类型 Node

class Node < ActiveRecord::Base
include Concerns::Publishable
end

但是我们希望每种类型的内容都有不同的逻辑。所以我们为每种类型创建子类:

class Node < ActiveRecord::Base
self.inheritance_column = :type
include Concerns::Publishable
end

class NewsArticle < Node
has_and_belongs_to_many :agencies
end

class Gallery < Node
has_and_belongs_to_many :photos
end

# ...

在 STI 模型之间开始出现太大差异之前,这种方法一直有效。那么数据库模式中的某些重复问题可能比试图将所有内容硬塞到同一个表中所导致的大量复杂问题小得多。大多数基于关系数据库构建的 CMS 系统都在努力解决这个问题。一种解决方案是使用无模式的非关系数据库。

组成关注点

这里面没有任何内容表明关注点需要您存储在模型表中。让我们看看您列出的几个问题:

Flaggable
Sluggable
Commentable

其中每一个都将使用一个表flagsslugscomments。关键是使与他们标记、标记或注释的对象的关系成为多态的。

comment:
commented_type: string
commented_id: int
slugs:
slugged_type: string
slugged_id: int
flags:
flagged_type: string
flagged_id: int
# ...

class Comment
belongs_to: :commented, polymorphic: true
end

module Concerns::Commentable
# ...
has_many: :comments
end

我建议您查看一些解决此类常见任务的库,例如 FriendlyId , ActsAsTaggedOn等等,看看它们是如何构成的。

结论

并行继承的思想从根本上没有错。并且您应该放弃它只是为了安抚某种极端的 OO 纯度理想的想法是荒谬的。

Traits 是面向对象的一部分,就像任何其他组合技术一样。然而,担忧并不是许多博客文章想让您相信的万能药。

关于ruby-on-rails - 在设计 Rails 数据库模式时如何避免使用关注点?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30956825/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com