领域驱动设计系列文章(3)——有选择性的使用领域驱动设计
本系列的第一篇博文抛砖引玉,大谈领域驱动设计的优势,这里笔者还是希望以客观的态度,谈谈领域驱动设计的缺点及其不适合使用的场景,以让读者可以有选择性的使用领域驱动设计。
我们知道,没有最好,只有最合适,设计也是一样。因此,所谓设计,就是以你和你的团队的知识、经验和智慧,全面充分的考虑各种内外因素后,在你们的设计方案中作出合理的选择的过程。而这些影响你们选择的因素主要有:
- 技术框架的特征和约束(如果你的项目决定使用C语言进行开发,那么首先在设计方法上,就需要使用面向过程而非面向对象的设计方法)。
- 时间的压力和约束(你永远不可能告诉你的老板,给我10年时间,我和我的团队将为你设计出世界上最优秀的软件)。
- 你的团队的能力、经验、性格、价值观等因素的约束(你不能期望一个长期从事遗留系统维护项目或大部分成员是缺乏经验的高校毕业生的团队能很好的按照你的设计意图去实现你的高度抽象的优秀设计,同时你也别指望一帮家里经济条件不错,本着过来熬时间的家伙会乐意与你一起刻苦钻研、精益求精)。
- 你的系统的特征(如果你想把一个足够简单而且在可以预计的将来都不存在很大规模的需求变更的系统设计得很复杂,很精妙,具有很好的扩展性,但要为此付出巨大的时间、人力成本,这显然是一种不理智的过度设计(Over design))。
- 其他外在因素的约束(你的项目需要参与投标,你必须压缩人力、时间等以让你的项目成本成为巨大的竞争资本)。
当然,上述的考虑因素站在比较高的角度,通常是项目经理、架构师需要考虑的问题,但这当中你应该会得到一些启发。回到我们的主题,我们首先看看,领域驱动设计相对于传统的面向过程式的设计,有什么缺点:
- 复杂化:面向过程思维之所以一直很受欢迎,是因为它很直观,非常符合大部分人的思维习惯,大部分人拿到一个问题,通常都是会很直观的想第一步做什么、第二步做什么,如果怎样,应该怎样,否则怎样……,可以说,任何水平的程序员,都能很好的使用面向过程的方法进行设计和开发。同时,由于我们教育水平的落后和整体IT环境的制约,可以这样说,真正掌握面向对象思维和设计方法的程序员的比例非常低(虽然绝大部分都使用面向对象的语言和工具),而本身面向对象思维要求人有很好的抽象思维能力,因为你需要把一个复杂的系统一层层的抽象为简单的部分,需要把现实世界的事物(有些是可见的,但有些是不可见)合理的抽象为计算机世界的不同元素。这些都不是一些很容易做的事情,要做得好,就更难。
- 团队的抗拒:如果你的团队(很大可能)大部分人都习惯于用很直观的面向过程的方式进行设计和开发,你需要推动你的团队转换思维来采用面向对象的方式进行领域驱动设计,通常会遭到多数人的抗拒。因为人都是有惰性的,他们习惯安于现状,而改变是痛苦的,他们要付出额外的努力,需要进行学习,但以笔者的经验,相当一部分程序员,特别是有一定工作年限的程序员,他们从事IT工作都只是为了获得一份不错的报酬,因此他们的学习动力非常有限,而且,他们都相当自负,被说服的难度比较大。
- 管理、开发和维护的成本高:复杂度更高,意味着你需要花更多的时间进行设计,同时需要花出额外的时间进行必要的培训,而且需要有更完善的文档(设计文档,API文档,培训文档等)。领域驱动设计的抽象程度比较高,因此必需有良好的文档,否则,随着项目的不断迭代、升级和维护,它很容易因为后来者的误解而慢慢回归面向过程的设计,甚至会变得“四不象”,领域驱动设计的成本优势是随着时间的推移慢慢体现的(见下图),如果出现这种情况,所有前面付出的努力都会付诸东流。
系统的初始阶段,领域驱动设计需要付出更大的成本,但随着时间的推移,领域驱动设计的成本效益优势会逐步显现
- 性能比较低:使用纯面向对象的方式进行领域驱动设计的程序,其系统开销通常要比面向过程设计的程序高,从而性能相对较低(关于具体的例子,后续的博文会提及)。
那么,假设我们在时间、团队能力及各种资源都允许的情况下,是否就可以麻木的全盘使用领域驱动设计呢?正如本博文的标题一样,答案是否定的,我们需要有选择性的使用。让我们来看看一些指导性原则:
- 使用领域驱动设计,并不代表整个系统的方方面面都必须遵从领域驱动设计的原则,需要根据实际情况,让适合的部分使用领域驱动设计,让不适合的部分使用面向过程的设计。
- 对于那些业务规则非常简单,通常只有增、删、改、查的简单操作,而且也不大可能发生大规模需求变更的模块,可以让业务实体成为一个“贫血模型”,例如一些基础数据:系统参数、商品类型、国家、地址信息(注:对于这点,本人持保留态度,因为这些业务虽然非常简单,但既然选择了领取驱动设计,即使把这些业务实体设计为“充血模型”,即把极其简单的业务逻辑也封装在业务实体中,也并不比使用“贫血模型”成本高,而它却带来了统一设计风格的好处)。
- 对于查询操作,特别是复杂的查询操作,出于性能的考虑,可以用结构化查询逻辑一次性完成,并把这些逻辑封装在Repository(即技术上的DAO)中(这方面的具体例子,后面关于“查询通道”和“领域对象仓库”的博文会更具体的阐述)。我们可以看到,对于一些业务视图,以及报表模块,很明显不适合使用面向对象的方式设计,因为这些“视图”和“报表”,本质上就不是业务实体。
- 同样出于性能的考虑,在业务实体的实现逻辑中,某些操作不适合过度偏执的使用面向对象方式。例如,在“订单”的“新增订单明细”(order.addOrderItem(orderItem))中,如果业务逻辑规定一张订单中包含优惠商品的明细数目不能超过20条,使用面向对象的方式,就需要把订单中的所有订单明细全部加载,然后逐个明细判断其对应的商品是否优惠商品,再统计出优惠商品的数目,这样很明显是低效率和高开销的,这里只需要使用Repository提供的一个统计方法,用一个结构化查询逻辑返回统计结果即可,而这就是非面向对象的方式。
本博文给有志于领域驱动设计的读者泼了一下冷水,提出一些“反模式”(Bitter),是为了让读者冷静一下,在领域驱动设计过程中作出更灵活和更合理的选择。关于这方面的论述,笔者在这里浅尝则止,限于水平、经验和表达能力,不敢胡乱卖弄,建议读者可以参考阅读Martin Fowler的《Patterns of Enterprise Application Architecture》一书的相关观点。