使用 Ruby 模型类、服务对象和交互器
Source: Dev.to
请提供您希望翻译的文章正文内容(除代码块和链接外),我将为您翻译成简体中文并保持原有的 Markdown 格式。
Source: …
为什么要为功能使用模型类?
在 Rails 中使用 模型类(即使是 app/models 中的一个简单 PORO)来实现功能有多种优势。开发者通常更倾向于使用“模型类”,而不是把逻辑散落在控制器、帮助方法或初始化文件中。
以下是 主要优势:
1. 集中且可复用的逻辑
如果功能的逻辑封装在模型类中(例如 FeatureFlag、BetaAccess、Onboarding),你可以在以下位置复用它:
- 控制器
- 视图
- 后台任务
- 服务对象
- 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.