什么是 Service 对象? 什么时候使用?

经常在网上看到有人在讨论 Rails Service 对象模型。但是在 Rails 的 guide 里面并未提及这个对象的定义。所以看起来貌似很模糊,到底什 么是 service 对象呢? 什么时候要用呢?

什么是 Service

一般是来说 service 里面包含系统互动的相关处理。简单来说,service 里面一般都是 包含不止一个 model。

例如: 我们包含一个 User 的 Model,这个 User 包含密码。如果用户忘记密码,我们需要 发送一封 email 让他重置密码。这个功能就是 service。

关于重置密码也有可能有其他需求,例如系统管理员可以帮助重置密码。

在上面的例子里面,把 service 放到 MVC 世界解释如下:

  • Model (User) - 一个用户有一个密码。必须不能为空并且长度至少 8 位。这个模型里面并没有 关于各种情况重置密码的处理。
  • View - 忘记密码页面的 form 和成功失败状态的显示
  • Controller - form 提交之后,实例化忘记密码 service 发送邮件并且显示处理的结果。
  • Service - 用户的一个行为,发送邮件并且告诉 controller 处理结果。

目前 Service 存在在哪里?

有下面几种方法是容易把 service 的逻辑封装在 Rails 应用里面了。

  • 臃肿的 Model,紧身的 Controller 因为这个原则迫使很多人把 service 逻辑放在 model 里面。这种会把 model 自己的方法和一些多个 model 之间交互的方法混淆在一起。这些交互的方法可能会成为一个问题,重构和测试将会非常麻烦。

  • Concerns 当很多 model 有一样的方法的时候,我们一般会把这些方法抽取放到一个 module 里面去。concern 在 Rails 4 里面也是被推荐使用的。有时候确实是可以的,但这种方法增加了抽象性,有时候会发现去哪里找一个方法会变得困难。 而且会产生潜在的互相依赖的问题。

  • Observer 和 Callbacks 这是 model 生命周期的 hook 处理。对于上面那个例子,我们可以创建和保存一个 ForgottenPassword 对象, 然后在它的 callback 里面做发送邮件处理。这种缺点是和 model 的生命周期绑定太紧,这种行为可以非常迅速地变得难以跟踪,尤其是与正在运行的请求周期之外的观察者, 而回调可以使事情很难测试或预测行为。

  • 臃肿的 Controller 很多人使用 controller 用来连接对象之间的交互。这种方式看起来可以,但缺点是导致 controller 变得 太大变得笨重,测试也是非常麻烦。如果你在 worker 里面也需要这样的功能,可能会复制导致重复代码。

Service 对象

Service 对象实现了独立的功能。上面的忘记密码的例子可以写成这样:

1
2
3
4
5
6
7
class ForgottenPassword
  def initialize(user_id)
    user = User.find user_id
    mail = UserMailer.password_reset @user
    mail.deliver
  end
end

这个例子很简单,你可以做错误处理,以及设置返回值。这样以便 controller 或者 rake task 调用的地方 使用更加方便。

使用了 service, 可以使 User model 里面不需要和 UserMailer 产生耦合。 User model 的测试代码也将变得轻松,ForgottenPassword 的测试也会变得非常直接。

ForgottenPasswordController 代码将会是下面这个样子:

1
2
3
4
5
6
7
8
9
10
class ForgottenPasswordController
  def create
    begin
      ForgottenPassword.new(params[:user_id])
      # render the view
    rescue
      # Handle exceptions and render the view
    end
  end
end

这样 controller 的测试也将会变得简单,没有必要做交互业务的测试,因为 service 测试以及做了。

什么时候使用?

在我看来,model 间的相互交互处理可以作为独立的 service 来处理。这样做将使你隔离你的测试到 非常离散的和独立的领域。

而且,如果你修改现有的应用, 通过采用 service 会看到这些好处的。开发和维护的功能会更容易。

总结

Service 虽然在 Rails 里面并未提及,但我觉得值得这样去做。这样可以使你的程序更加简单,也有助于 测试和维护,我希望你可以考虑使用 Service。

评论