怎样改造现有系统为领域驱动型系统

经过了一段时间(前后大致5个月)关于领域驱动设计的学习,也初略读了几本书:Vaughn Vernon的《实现领域驱动设计》,《领域驱动精髓》以及Eric Evans的《领域驱动设计》。脑中对领域驱动大致有了一个概念和轮廓,比如其中几个重要的概念包括限界上下文(BC),上下文映射图(CM),核心域,战术模式与战略模式等等。这对于在设计微服务系统的整体架构和模块分布策略上来讲都是很关键的一些因素,也促成了服务化设计向领域设计过渡的根本原因。

什么是领域驱动设计?它相对于传统的设计思路有什么优势?

领域驱动设计就是非面向数据的,而是面向领域模型出发,以业务规则为范本,增强代码中关于模型和业务的抽象能力,形成非事务脚本而是面向对象的软件分层设计。

领域驱动相对于传统的面向过程式的web框架设计,更讲求模型的内聚性和服务的个体化。比方说,订单中会包含订单项,每个订单项中会包含多个sku(单品),我们在看到这样的关系模型时,往往立刻去想数据库是怎样设计的,这里需要三张表,一个是订单表,里面包含订单的相关信息;另一个是订单项表,每个订单项会对应多个sku id(这里可以设计为一行,一个独立的字段存储skuid的集合),最后是sku表,里面包含sku的详细信息。这样在程序设计上没什么问题,但是没有任何业务上的表现,我只知道数据和数据之间的映射关系,也就是说系统中的实体之间是通过数据来串联起来的,而业务规则的体现统统放在service层,一个很大的关于模块的类中,放置了成百上千个业务规则方法。对于阅读代码的人来说,看到的都是一个个过程式的编码,无非就是到处获取数据,然后存储下来,或者查询数据后返回给表现层。这样的设计很好理解,也最容易被普通程序员接受,因为人类的思维就是面向过程的,这和人类记忆的形成有关。

而领域驱动设计对于这样的模型,设计的出发点是不同的,他会将订单作为一个实体,因为每个订单都是严格唯一的,而订单项是随着订单的创建生成的,不具备唯一性(可能存在着两个包含一模一样的sku的订单项),所以将订单项作为值对象,而将同样是实体的sku聚合在值对象中,形成订单这样一个语义丰满的聚合类型实体。这样的设计即具备了数据的关联性(或者说是隐含在业务逻辑中的数据),也体现了真实的业务规则。

为什么最近这些年领域驱动变得越来越火了呢?是大家真的想故意找麻烦吗?那我们就要从领域驱动的设计思路出发,分析下为什么人们会爱上这样“麻烦”的设计。

那么可以先从面向对象的设计出发,为什么java被设计成面向对象的语言呢?这一切源于另外一门语言——Smalltalk。1976年,Smalltalk的诞生预示着面向对象风格的崛起,人们发现继承和多态可以使得一些概念表现得更像现实中的世界一样,一切皆对象,就是在这样的情况下说出的。之后又接连兴起了设计模式,极限编程(XP)和重构等概念。那么面向对象除了可以更好地模拟现实世界之外到底有什么好处呢?

第一个,也是最大的优点就是模块化,面向对象可以实现逻辑与对象的分离,各个对象实现了严格意义上的职责单一,自己负责自己领域内的事情,与其他对象之间的交互通过发送消息来实现。第二个优点就是复用,继承就是这个思想最大的实现方式,通过复用父类的方法,程序可以实现子类在公共方法上的复用和自定义的扩展,实现同样中保持着不一样。第三个也就是多态,有多重语言都实现了多态,通过面向接口的编程,只定义接口和契约保证了各个类之间没有任何的耦合,并且实现了更加高级的模块化,保证了上下游系统的信息传输。

那么领域驱动设计和面向对象又有什么联系?

领域驱动说白了就是基于领域的业务模型去思考设计问题,而不是原本基于领域的数据来设计系统,但它并不是银弹,只是面向数据的设计具有它不得不面对的问题:随着业务复杂度的提升,工程中的重复代码将会大大增多,这导致了维护起来的困难,开发一个很小的需求就需要花费很大精力。研发人员的效率也会降低很多,并且极易出错。尤其是在组织中的人员流动很大时这样的问题尤为突出。而面向对象的设计追求代码职责的单一,追求模型的内聚性和充血性,在业务频繁变化的过程中还能保持领域模型的简单性。通过消息传递对象之间的交互信息,读取写入分离(CQRS),事件溯源等手段使得模型的创建不仅仅表现了数据的设计方案,还能体现出业务和产品设计的灵魂和思想。

怎样改造现有系统为领域驱动型系统?

首先,从现状出发,如果系统处于迭代速度很快(平均一周两次发布频率),业务模型和逻辑流程复杂到存在50个用例以上,系统模块划分达到10个以上的话,就应当考虑使用领域驱动。

而后思考是采用战术模式、战略模式还是两者皆用。

如果你设计的是传统的一体化系统,那么强烈建议使用战术模式,聚合,实体,值对象以及领域服务和领域事件,能使得你的系统不会流于混杂的业务流程交织在一起的情况,也可以使得领域对象变得更加具有内聚性,而不是传输在多个用例中的“过客”。如果你设计的是微服务系统,那么优先考虑的是战略模式的应用,从限界上下文,上下文映射图和核心域,子域这些概念出发,将系统中的各个模块设计的耦合度更小,并且识别出上下游和对外暴露的服务。如果你的团队已经在微服务系统设计上有了一些前期的投入,并且保持了良好的模块划分策略,那么战术模式就基本不需要投入太多精力去做了(但可以小范围微调模块划分和职责),精力可以放在单个核心域中的战略设计。比如一个新零售系统中包含了订单,商品,促销,客服,发票,餐饮等,在餐饮模块中就可以应用战略模式,将核心域中再分出多个子域,更进一步划分出业务流程中最重要的概念,抽象出实体和值对象。

最后再思考对象之间的交互策略,是采用基于事件驱动的架构,还是传统的MVC模式架构。这里简单比较下MVC与基于事件架构的优缺点:

1 MVC是面向过程式的,架构分层比较简单,从入口处的表现层,通过逻辑复杂的服务层,将信息流转到模型层进行存储和抽取

2 事件驱动可以实现最终一致性,免除了ACID操作带来的事务复杂度的提升

3 事件驱动可以减少事务性代码,逻辑被封装在事件处理器中,解耦了业务和对象

4 MVC更适合前后端不分离的场景,甚至后来发展为MVVM也是由于全栈的需求

5 MVC模式不易构造面向对象的程序设计

在这里我们采用事件驱动的架构模式,需要加入的几个角色分别是:事件发布者,事件订阅者,领域事件以及命令模型。其中,命令模型更像是DTO,而发布者通过单例进行事件的发布,而订阅者通过监听事件发布器上的事件来决定是否是自己感兴趣的主题,如果是,则进行事件的订阅。正式发布的时候会将订阅了该事件的所有订阅者取出,并进行事件的处理。

参考资料:《实现领域驱动设计》、《设计模式》

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181029G0SIOD00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励