经常在网上看到有人在讨论 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 |
|
这个例子很简单,你可以做错误处理,以及设置返回值。这样以便 controller 或者 rake task 调用的地方 使用更加方便。
使用了 service, 可以使 User model 里面不需要和 UserMailer 产生耦合。 User model 的测试代码也将变得轻松,ForgottenPassword 的测试也会变得非常直接。
ForgottenPasswordController 代码将会是下面这个样子:
1 2 3 4 5 6 7 8 9 10 |
|
这样 controller 的测试也将会变得简单,没有必要做交互业务的测试,因为 service 测试以及做了。
什么时候使用?
在我看来,model 间的相互交互处理可以作为独立的 service 来处理。这样做将使你隔离你的测试到 非常离散的和独立的领域。
而且,如果你修改现有的应用, 通过采用 service 会看到这些好处的。开发和维护的功能会更容易。
总结
Service 虽然在 Rails 里面并未提及,但我觉得值得这样去做。这样可以使你的程序更加简单,也有助于 测试和维护,我希望你可以考虑使用 Service。