关于可组装开发理念
Gartner
在其发布的《企业机构在2022年需要探索的重要战略技术趋势》中提出了“Composable Applications
组装式应用
”的概念,指出“组装式应用由以业务为中心的模块化组件
构成,具备更易使用和可重复使用的代码,可加速新软件解决方案的上市时间,并释放企业价值。”
那么具体如何实现组装式应用呢?其核心是“封装业务能力
”(Packaged Business Capability,简称PBC
)。在不断变化的业务环境中,业务适应性需求将引导企业转向使用支持快速、安全和高效应用变化的技术架构。在这种背景下,需要将一个个的业务对象或功能模块化,以实现快速“组装”或替换。组装式应用增强了这种适应性。有利于更安全
、更高效
和更快
的变更和迭代,提升敏捷性
、减少重复劳动
。
在这点上,领域驱动设计(
DDD
)
与之不谋而合。
领域驱动设计的“新”
传统的系统架构多采用MVC三层模型设计
,即Controller
->Service
->Dao
分层结构,所有的业务逻辑都集中在Service层。随着业务持续发展,这种面向事务脚本进行编码的方式,会使代码臃肿
,可复用性差
,架构腐化
很快。
领域驱动设计
是对面向事务脚本编程的进一步升华:
- 它强调“领域”的概念,将
服务提供(
Service
)
层划分为domain
层和App
层 - 把现实对象映射到代码中的领域对象,提升代码可读性
- 通过稳定的领域模型来提供代码复⽤,提升研发效率和代码质量
Infrastructure
层包括所有对基础设施和其他服务的依赖。在Mapper之上封装Gateway,用于领域层调用;调用MQ库发送消息等逻辑都也在Infrastructure层-
Domain
层对基础设施的依赖通过DIP(依赖倒转)解耦,Domain层可以独⽴存在
目前使用较多的是由阿里DDD专家 张建飞设计的COLA架构,详见https://github.com/alibaba/COLA,这里不做赘述
领域驱动设计是YYDS吗?
领域驱动模型设计能够构造高扩展性
、高可维护性
、快速迭代
的架构。在实体比较复杂、各实体关联比较多,操作中可能涉及其他实体方法比较紧密的时候,用领域驱动设计是比较好的选择。它能使功能操作逻辑易于理解,模块分界清晰,易于后期扩展改造,较好地支撑产品演进。
如果是一个较小型的项目后期演进一般,操作多是简单的crud,实体属性比较少且并不涉及复杂业务逻辑。这时使用领域驱动设计我倒觉得有点大材小用,反而传统的MVC架构就足以应对,且开发起来更加简洁。因为领域驱动设计开发会涉及比较多的DO<->领域模型<->VO/CO/DTO对象
的转换,在简单的业务逻辑背景下,这些转换看起来占比比较重,使得代码不是那么简洁,俗话说:杀鸡焉用牛刀?
当然,在现阶段和未来,绝大多数的应用都不只是“简单”的,领域模型驱动设计会得到更好的发展和普及。也不排除将来会出现更好的开发设计模式,因为哪里有需求,哪里就有解决问题的办法。
我们怎么更好地去用它?
比如某个应用以公司的“资产”为核心对象之一,衍生出很多相关的功能和操作。“资产”这个对象本身包含二三十种属性,围绕“资产”的业务逻辑包含:资产查验,标准化管理,属性列表完善,上、下架,审批,用户对该资产是否有权限等等。这些业务逻辑往往比较复杂,如果都通过dao->Service层来处理,会带来以下的种种问题:service层非常臃肿,代码比较散乱,重复性代码较多,业务方法不够内聚,维护梳理障碍,扩展/变更性差……
如果将“资产”作为领域模型,除了它的属性外,将其涉及的业务操作抽象成方法也放在里面,那么对于代码可读性、对象及对象操作的范围边界、业务模块等将会比较清晰。
用领域模型设计后,service层的代码是这样的:
我们将多变的业务逻辑从service层拆出来,整合进领域和gateway,而服务层专注于服务本身
。从上面的图中可以看到,一个业务操作涉及了多个领域的多个业务方法单元,不仅实现了业务逻辑复用,是不是还很简洁明了呢?
这里要提示一点:要注意领域驱动设计的思考抽象方法,要尽可能遵循其约定,避免在后续的迭代更新过程中沦为之前的实体作用。
下面是一些使用心得分享:
领域类需要完整的构造
我们通常是通过两种方法来构造领域类
:(1)数据库的表结构(2)抽象业务操作的实体和相关业务方法。
要注意的是:领域类一定要构造完整,对于不同的使用领域类的场景下,领域类对外的暴露都是完整且一致的。 不要出现部分属性没有初始化的情况。
尽量将逻辑沉淀为领域类中的方法,保持较好的结构和复用率
避免写着写着就绕开领域模型,增加逻辑直接调用Mapper处理业务。这是大多数习惯传统MVC架构的程序员在使用DDD之初一段时间内常犯的问题。
Gateway不要揉太多业务逻辑
在底层infrastructure模块,gateway的作用主要是为了让domain类与基础设施层解耦,如领域类的保存行为不应该感知后端存储是MySQL还是其他数据库等,所有的业务逻辑应该沉淀在domain类中,通过gateway来对接不同的基础设施。
懒加载技巧
领域对象会与其他的领域对象产生关联,每次构造领域对象会导致大量关联领域对象的初始化。大多数的领域对象初始化不仅会消耗内存和计算量,还会进行数据库IO,从而影响响应速度,导致领域对象初始化速度慢。 所以尽可能将这种依赖通过方法懒加载的方式暴露。
抛弃部分CQRS,走领域模型做查询
我们有些习惯在查询场景下绕过领域层,直接select多条记录,存储为DO返回给Client端。但是这会导致越来越多的冗余逻辑存在,破坏代码结构,影响领域层的沉淀。所以不管出于何种考虑,既然选择了DDD,应该尽量去遵守其约定,走领域模型做查询。