领域建模
Martin Fowler在他的《企业应用架构模式》书中谈到了两种开发方式“事务脚本”和“领域模型”:
事务脚本:对每一个业务处理,编写一个脚本,直接通过SQL来读写最底层的数据库,在脚本中进行逻辑的处理,最终完成业务服务。存储过程就是一个很好的“事务脚本”,相比使用Java等高级语言而言,它有很多不可替代的优势,其中REPL的开发模式就非常的高效。
领域模型:采用领域模型时,我们将数据(表)和行为(逻辑)封装在一起,形成“Domain”,每个Domain有明确的职责定义。在完成一个复杂的业务逻辑时,我们并不直接的和底层数据进行交互,而是和Domain进行互动,在一个更高的抽象层次来完成最中功能。
这个划分和”模块化“、”结构化“、”面向对象“等设计都是异曲同工的,都是“高内聚、底耦合”原则的一个自然结果。”事务脚本“这种模式对我们来说,是很自然的一个方法,当业务逻辑的复杂度完全在一个有限的范围之内,在我们的完全掌控之中时,这是最简单、最直接的一个做法,也会是最高执行效率的一种方法,相当于我直接管理每一个士兵,按照我的指令来进行操作,所有的业务逻辑都在“事务脚本”这一个控制点上。
但商业领域的复杂性(包括时间复杂性、空间复杂性),很快就会超过你的控制能力,单个“脚本”就会变得非常复杂,大量的“脚本”中,对数据的各种操作,很快就会支离破碎:每个脚本访问不同的数据,执行多个业务规则点,每个数据被多个脚本访问,每个业务逻辑点在很多脚本中分散。系统中出现大量的重复代码、重复逻辑,有重复就必然导致不一致。
这样的复杂性很快就会让我们难以驾驭,再添加一个小的功能,修改一个小的Bug,都有如最后一根稻草一般。我们很难修改之前的代码,只能不断继续的添加代码,打补丁,让系统变得更加复杂,更加不一致。然后。。。就没有然后了。
从广义的角度来看,基本上稍微有些规模的应用,都会有分析设计的过程,这个过程都是一种建模,都会带有“结构化“、”面向对象“化的一些改进,从而让复杂逻辑更加的”提高内聚、降低耦合“,都能够更好的和业务专家进行沟通。但一般的,我们认为,从狭义的角度来看,我们认为,采用《领域驱动设计》中的核心思想、实践方法的,才算是“领域建模”:
- 领域模型应该成为业务专家、软件开发团队共享的一个模型,并构成团队的一个一致沟通语言。如果领域模仅仅是一个纸面模型,和开发模型之间还有一个复杂的映射,这个并不算“DDD”
- 有一个限定的上下文。
DDD需要解决的核心问题包括:
- 与业务专家达成一致的概念、和交流语言。采用符合行业、复合特定应用领域的理论模型。比如,财务记账相关的内容,最佳的模式就是复式记账的模型。
- 识别领域内的实体(这个弱化一下就是ER建模)
- 定义实体的生命周期,以及在生命周期内的行为。(相似的概念:状态图、活动图、数据流)
- 定义实体的核心业务规则,包括:
- 唯一性。让商品库中存在两个不同的SKU实体,现实中却实际上是同一个实体,这是一个BUG,这个问题并不是能够通过ID能解决的。
- 字段约束。
- 字段间约束。
- 定义实体与实体之间的关系。
- 导航关系。包括外键定义、N to N 关系。
- 实体和实体之间的数据一致性关系。比如说,订单的金额 == ∑子弹的金额。
- 不同领域间的数据一致性关系。比如说,使用资金账户的钱来支付订单,则订单支付成功,资金账户的钱必须扣除,反之,订单取消,钱也应该退回。
- 定义合理的粒度,以及不同粒度下的数据一致性
- 不是所有的数据库表都需要建模成为实体(Entity),有得数据库表,并没有生命周期、不需要从外部进行引用(或者不合适进行引用),这些应该使用ValueObject(VO)来建模
- 单有实体是不够的,很多实体之间有很强的关联性,比如Order和OrderItem,这个时候,应该建立Aggregation。在一个Aggregation中有一个根实体,其他的实体都需要从根实体出发,进行访问。并且使用Aggregation来建立聚合级别的数据一致性,在同一个聚合内的任何操作,都必须保证这个一致性。一般来说,这个一致性是通过数据库级的强事务来实现的。
- Aggregation的粒度也不适合与太大,如果过大,复杂性就会变得更高。而且,在实现层面,可能会一次Load太多的数据,导致性能方面的问题(理论上可以通过Lazy Load来解决),但是,由于使用强事务的机制,会导致并发行降低,无法满足业务的需求。所以,需要在一致性处理和性能、并发之间,在一致性处理和简单性之间进行平衡。不适合建立一个超级Aggregation。
- 比Aggregation更高的是的Package(也可以理解为乏超级集合),一系列相同领域的聚合,可以组合在一个Package中。以订单为例,Order + OrderItem可以成为一个聚合,但物流OrderDelivery,金融OrderFinance、收付款等,每一个都有自己独立的业务逻辑,但同时又与订单有强关联(此处场景为B2B模式),在一个Package中包括相关的聚合,并且尽可能保证同一个Package中的数据强一致性,同时避免不必要的锁定导致的性能损失。(我们会限定一个package存在在同一个数据库上,以保证强一致性)
- 跨聚合、跨package层面,还会存在数据的一致性问题。比如订单、库存,这个时候,使用强一致性就无法解决了,我们需要采用MQ的方式来实现最终一致性(这个最终一致性可以在second的级别上达成),单纯的MQ还难以保证,还需要通过跨系统间的对账来实现最终一致性(这个就可能是以day为单位了)。
- 在DBC模式中,Invariants定义如下:
In mathematics, an invariant is a property, held by a class of mathematical objects, which remains unchanged when transformations of a certain type are applied to the objects.
如果实体的某个操作导致了这些约束失败,那就意味着操作中存在BUG,逻辑存在错误。在一个复杂的系统中,要控制每一个操作都没有BUG,是一件非常困难的事情,但如果坚守对这些约束的检查,那么,就可以快速的暴露代码中的BUG,不让错误继续下去,系统的质量才能够得以保证。在我们进行领域服务开发的过程中,我们发现最多的一类问题就是不同领域之间的数据的不一致性,或者不同阶段段的数据的不一致性,带来了非常巨大的问题,极大的影响了系统的开发效率,也会给系统的持续升级带来障碍。如果实施了有效的不变量的管理,我相信服务的质量会有巨大的提升。