Ruby 这个 repository 模式实现看上去还不错

chenge · 2016年11月12日 · 最后由 darkbaby123 回复于 2016年11月24日 · 3263 次阅读

领域驱动设计中,也就是 Domain Driven Development, 简称 DDD 的一种设计方法,Repository 是一个重要的支撑模式。

这个代码演示了内存方式,很简单,是用 hash 来模拟的。


class InMemoryBackend
  def initialize
    @counter = 0
    @map = {}
  end

  def create(record)
    @counter = @counter + 1
    record.id ||= @counter
    map_for(record)[record.id] = record
  end

  def update(record)
    map_for(record)[record.id] = record
  end

  def delete(record)
    map_for(record).delete record.id
  end

  def find(klass, id)
    map_for_class(klass).fetch id
  end

  def all(klass)
    map_for_class(klass).values
  end

  def query(klass, selector)
    send "query_#{selector.class.name.underscore}", selector
  end

  private
  def map_for_class(klass)
    @map[klass.to_s.to_sym] ||= {}
  end

  def map_for(record)
    map_for_class(record.class)
  end
end

接下来,可以用 User 来实验

class User
  attr_accessor :id, :name 
end

db = InMemoryBackend.new
user = User.new
user.id = 1
user.name = 'Bill'
db.create(user)
db.find(User, 1)

文章中有比较完整的介绍,英文链接

作者 Hawkins 写了很多不错的博客,值得一读。

这段时间在看 Domain-Driven Design Quickly,感觉 DDD 最核心的思想的还是划分 domain model,让领域概念和技术实现同步,减少系统分析师和软件工程师的沟通误解。

Repository pattern 我还挺喜欢,但目前 Ruby 圈子里的实现都有点复杂,包括 ROM 和 Lotus。唯一看得上的是 Elixir 的 Ecto,兼具严谨和灵活。

感觉 DDD 更适合大型项目,而且大型项目中也不是所有地方都要用到 DDD 的那些模式。

#1 楼 @darkbaby123 我有一个思路,就是用贫血模型的 AR,也就是只负责数据,不写业务规则,这样就符合 SRP 原则了。

业务规则放在 service 上,也许加 helper 什么的。

确实 Repository 没有看到好用的,所以没流行起来。

#2 楼 @chenge 其实我感觉正好反了。DDD 的 domain model 最开始提出并不是为了干掉 Active Record pattern,而是为了干掉贫血模型。可以看看 Martin Fowler 的 AnemicDomainModel 。我是看了这个才知道贫血模型是什么意思 😃 感觉 DDD 还是有很深的东西可以挖掘,很多衍生概念都跟它的这种思路有关,不过近期最热的应该是 CQRS 了。

更好的做法应该是 Entity 包含一定的业务逻辑,但不包含数据库存储逻辑。如果有些操作很难以某个 Entity 为中心就单独抽一个 Service。这跟 7 patterns 里面抽象 Service Object 是基于同样的理由。

我之前有个项目做过把所有操作都抽成 Service Object 的事情,校验不在 model 里面,极少写 callback。代码是好维护很多,副作用可预测,构建测试数据会非常方便。但代码量会稍微多一点,而且有时候感觉一个 Service Object 只操作某一个 model 有点怪,不如 model.do_something() 。只是我觉得单独写一个 Object 也能起到分离关注点的作用就继续这么干了。总体来说还是利大于弊的。

#3 楼 @darkbaby123 主要是要确保可修改性,也就是根据需要,快速地判断修改点和影响面。 避免搞成大泥球,宁可多写点代码,不能坏了结构。

#3 楼 @darkbaby123 补充一下,AR 不方便测试,这也是用贫血的理由。

#4 楼 @chenge 我现在也这么觉得,清晰易懂,职责明确更重要。OO 那一套其实就是合理的划分边界,不要让不相干的知识 leak 出去。DDD 只是在此之上明确了几个 pattern 和它们的应用场景。

#6 楼 @darkbaby123 我感觉 Hanami 框架(原来名 Lotus)值得了解,我看目前是 v0.9, github 也有三千多粉丝了。

#7 楼 @chenge 是个挺有意思的东西。之前一直在跟,还跟他们提了一个 conditional validation 的 issue,现在偶尔看下 changlog。他们改动也挺大的,后来好像用 dry-validation 替换之前自己做的类似 AR 的 validation。现在又直接集成 ROM 了。

除了它之外,dry-rb, ROM 和 Trailblazer 三个都值得看看。Trailblazer 里面的一些代码分层方式也比较适合借鉴到现有 Rails 项目中去。

看了原文再对比 Ecto 突然豁然开朗的感觉。 不过 LZ 你的 example 不太准确,不应该直接在 Adapter 上举例子。

#3 楼 @darkbaby123 我说的贫血类似于这个 jdanger 博客说的DAO 模式,我觉得他那个思路不错。测试的时候完全不需要 AR。

老实说,这篇博客的例子举得有点不到位,作者的目的是试图把逻辑从 model 层挪出去,从而简化 validation 和 callback。如果照这个思路思考,会发现这篇文章的核心思路就是 Service Object(文章里面的 Signup)。至于单独弄出来的 persistence,测试的时候建立一个 Fake Object 也没问题,不过我觉得这跟 stub 了 model 的 save! 方法没有任何区别,只是前者通常自我感觉比较 pattern,而后者有时候会被鄙视为肮脏。

说完这个再说 DAO,作者对 AR 的处理确实 DAO 了,这恰恰是我贴的 Martin Fowler 那篇文章所反对的,而他推荐的解决方案恰恰是 DDD 中的 Domain Model。

我们来看看 Martin 老爷子说 DAO 这种模式有什么特点:

The catch comes when you look at the behavior, and you realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters. Indeed often these models come with design rules that say that you are not to put any domain logic in the the domain objects. Instead there are a set of service objects which capture all the domain logic. These services live on top of the domain model and use the domain model for data.

然后 DAO 这种方式的问题:

The fundamental horror of this anti-pattern is that it's so contrary to the basic idea of object-oriented design; which is to combine data and process together. The anemic domain model is really just a procedural style design, exactly the kind of thing that object bigots like me (and Eric) have been fighting since our early days in Smalltalk.

然后看看博客里把 model 的逻辑抽到所谓的 StateMachine ,不觉得有一样的问题么?model 本身空空如也,抽出来的逻辑不知道放在那里,或许也不知道起什么名字,只好起一个 "状态机" 这种完全没有业务表达力的名词。

我赞同把 validation 抽离成单独的 Object,赞成把独立的业务逻辑(比如发 email)从 model callback 中抽成单独的 Object,也赞成把这些不同的逻辑在 Service Object(文章里的 Signup)里聚合,不是因为这些符合某种架构 pattern,而是因为这些都是 OO 的正常做法。我唯独不赞成 StateMachine 的逻辑。这本就应该是 User 这个 model 干的事情。

Rails 的 AR 复杂已经被吐槽过很多次了,但为什么至今仍然还有很多人把逻辑往 callback 里写,我觉得有部分原因是例子举得不到位,他们对 pattern 一知半解,把事情变得复杂,却没有获得应有的好处,相反让一些不明就里的开发者连一些好的 pattern 都开始抵制起来。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号