像梦一样奔驰|谈DDD领域驱动设计和建模( 六 )


对于Service对象中的每一个方法最好都是对应明确的业务方法 , 这些业务方法往往是对应到业务系统前台具体的业务功能或业务操作的 。 如一个转账操作可以是service层的一个方法 , 但是在转账操作的实现过程中需要判断用户账户是否有效 , 用户是否有欠款 , 那么这些就是业务规则 。
对于业务规则不需要暴露为Service对象中的具体方法 , 在不考虑Specification模式的时候可以将具体的业务规则直接写到Service方法里面 , 但是可以看到会导致Service对象变重 , 而对于Service对象更多应该只是下层对象的方法调用和方法组合 。 因此才会出现将业务规则单独抽取为独立的方法 , 同时新增加一个规则类类存储这些规则和方法 。
在这样处理后 , 整个逻辑和思路和常见的SOA架构方法论就能够更好的对应和映射 , 即:

  • 实体和仓储类:更多的是承载对象的CRUD数据操作 , 不承载过多的业务规则 。
  • 规格类:承载业务规则 , 是在实体和仓储类外的业务规则和逻辑校验实现等 。
  • Service类:对上面两类对象中方法的调用和组合 , 本身并没有太多的业务和规则实现 。
如果按照这种方法来实现 , 那么Service中的方法更多都可以转化为后期的BPEL服务编排方式来实现 。 另外对于规则类是否可以直接访问DAO层 , 在书里面是可以的 , 即这部分规则实现是不走实体和仓储类的 。
领域模型-工厂和仓储首先为什么需要工厂Factory , 因为有了聚合的概念 , 很多时候我们需要创建Aggregate整个聚合 , 创建过程很复杂 , 如果我们把这个创建职责分配给聚合里面的任何一个Entity来说都是不合适的 , 一个是暴露了聚合内部的结构破坏了边界 , 一个是聚合内的实体承担了本身不应该自己承担的职责 。
因此应该将创建复杂对象的实例和聚合的职责转移给一个单独的对象 , 这个对象本身在领域模型中可能没有职责 , 但是它仍然是领域模型的一部分 。 提供一个封装所有复杂装配操作的接口 , 而且这个接口应该不需要客户引用要被实例化的对象的具体类 。 在创建Aggregate的时候要把它作为一个整体 , 并确保它满足固定规则 。
而设计模式里面的工厂模式 , 则强调的是工厂提供一个公共的接口 , 可以根据我们的需求灵活的返回不同的实现该接口的对象实例 。 对象的创建和实例化由工厂来完成 , 而不是简单的由对象的构造函数完成 。 而实际领域模型里面的Factory往往并没有这么复杂 , 仅仅是接管复杂聚合对象的创建和实例化而已 。
对于简单的Entity , 或者本身不存在聚合的时候是不需要再加上Factory类的 。
对于仓储Repository需要说明两个方面的内容 。 一个是解决持久化的问题 , 一个是对数据层做屏蔽 , 避免应用或展现层直接跳过领域层对数据库进行操作而使领域模型最终无用 。 在有了Repository后 , 我们不再关心对象的存储和访问操作 , 而将重心真正转移到领域模型本身 。 或者叫使应用程序和领域设计与持久化技术解耦 。
对于工厂和仓储的关系 , 工厂负责对象生命周期的开始 , 而仓储负责对象生命周期的中间或结束 。 当对象驻留在内存或对象数据库的时候很好理解 。 但是至少有一部分数据会持久化存在到类似关系型数据库或文件中 , 这样检索出来的数据就必须重建为对象形式 。
对于工厂和仓储的协同 , 有些理解和书上有些不一致 。 个人理解工厂不仅仅应该关注复杂对象的创建 , 同时也应该关注复杂对象的保存 。 工厂不负责对象的持久化 , 工厂将持久化职责委托到仓储来完成 。 仓储不应该直接和应用层打交道 , 对于整个领域层来说 。 和应用层打交道的是Service接口 , 而和持久化层打交道的是Repository接口而已 。 和书里面理解最大的差异就是Repository没有保留给Client , 也不是Repository委托Factory来重建对象 。


推荐阅读