使用 Ruby 模型类、服务对象和交互器

发布: (2026年1月16日 GMT+8 05:47)
5 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的文章正文内容(除代码块和链接外),我将为您翻译成简体中文并保持原有的 Markdown 格式。

Source:

为什么要为功能使用模型类?

在 Rails 中使用 模型类(即使是 app/models 中的一个简单 PORO)来实现功能有多种优势。开发者通常更倾向于使用“模型类”,而不是把逻辑散落在控制器、帮助方法或初始化文件中。

以下是 主要优势

1. 集中且可复用的逻辑

如果功能的逻辑封装在模型类中(例如 FeatureFlagBetaAccessOnboarding),你可以在以下位置复用它:

  • 控制器
  • 视图
  • 后台任务
  • 服务对象
  • Pundit 策略

…而不是在多个地方重复相同的逻辑。

2. 让控制器和视图保持简洁

Rails 的控制器和视图应该保持轻量。把领域逻辑放在模型中可以让设计更清晰(Fat Model, Skinny Controller)。

而不是

if user.admin? && SomeConfig.beta_enabled?
  # …
end

应该

if BetaAccess.allowed_for?(user)
  # …
end

3. 更好的可测试性

模型是最容易测试的:

RSpec.describe BetaAccess do
  describe ".allowed_for?" do
    # …
  end
end

不需要启动控制器或模拟 Web 请求。

4. 规则的封装

如果功能的逻辑可能会增长,模型可以把所有规则集中在一个地方。

class Onboarding
  def completed?(user)
    user.profile_filled? && user.verified? && user.tutorial_done?
  end
end

以后想添加新的 onboarding 规则,只需更新这个类。

5. 更好的命名 + 可读性提升

专用模型能够清晰地表达意图:

if FeatureFlag.enabled?(:new_ui)
  # …
end

…比下面的写法更易读:

if Rails.configuration.x.new_ui_enabled
  # …
end

6. 以后可以轻松支持持久化

你可以先从一个简单的 PORO 开始:

class FeatureFlag
  FLAGS = { new_ui: false }
end

以后可以在不改变公共接口的情况下切换为 ActiveRecord 模型:

class FeatureFlag  e
    context.fail!(error: e.message)
  end
end

模型 vs. 服务 vs. 交互器

概念职责示例
模型表示领域状态和规则;封装属性和行为FeatureFlag, User, Subscription
服务执行离散操作;可以使用多个模型PaymentProcessor, EmailSender
交互器使用模型和服务编排工作流/事务;处理成功/失败CreateOrder, SendWeeklyReport, EnrollUserInCourse

关键区别

  • 服务 = 只做 一件事
  • 交互器 = 编排多个事务 作为单一业务操作。

Interactors 在模型和服务之间的定位

  • 模型 → 保存状态和领域逻辑

    FeatureFlag.enabled?(:new_ui)
  • 服务 → 执行与单个模型或领域相关的操作

    PaymentProcessor.charge(order)
  • Interactors → 将多个模型和服务协调成一个事务性工作流

    CreateOrder.call(params: order_params)

类比

  • 模型 = 乐高积木
  • 服务 = 单个乐高作品(例如门或轮子)
  • Interactor = 完整的乐高套装(把多个作品组合成一个可运行的系统)

何时使用 Interactors、Services 与 Models

用例推荐模式
状态、规则、计算或查询Model
对模型执行的单一操作Service
可能失败且需要清晰编排的多步骤工作流Interactor

示例工作流

# Model
class User; end
class FeatureFlag; end

# Service
class WelcomeEmailSender; end

# Interactor
class OnboardNewUser
  include Interactor

  def call
    user = User.create!(context.params)
    WelcomeEmailSender.send(user)
    context.success_message = "Welcome #{user.name}!"
  rescue => e
    context.fail!(error: e.message)
  end
end

Source: https://devblog.launchzilla.net/Model-Classes-and-Service-Objects/

摘要

模式适用场景不适用场景
模型(PORO 或 ActiveRecord)领域概念、规则、状态一次性操作
服务可执行的动作(“执行 X”)表示领域对象
初始化器 / 配置静态规则可能会增长或需要依赖的规则
交互器编排多步骤工作流/事务单一状态或简单规则

原文发布于 DevBlog.

Back to Blog

相关文章

阅读更多 »

发布 Gon v7.0.0

发布 v7.0.0 Gon v7.0.0 发布 https://github.com/gazay/gon/releases/tag/v7.0.0 – 这次重大版本升级引入了不兼容的更改。不兼容的更改:reque...