引言:
目前我们的系统基本都是业务驱动,代码边界是以业务为准,所以领域驱动设计,作为一种架构风格,如何结合业务场景落地,如何进行技术填充,就是重中之重。
(注:请不要套用任何技术栈、一定要对业务熟悉)
- 什么是DDD
领域驱动设计(Domain-Driven Design)
软件是不区分行业的,软件只是给各个行业赋能,比如电商、物流、游戏、金融、医疗、物业等等。业务的建立一般是由:运营传递到产品,然后产品设计交给研发。整个链路中会出现消息传递不准确,或者说消息失真,那么最终做出来的可能和原本的需求会有偏差,那么就会有更多的需求变更,代码修改。而不断的代码修改会导致代码的复杂度增加,最终必须要重构。为了解决这个问题,就提出了领域。
早期的领域模型:数据库设计。根据业务建表,然后建立对象,任何需求的变更都有可能导致字段的调整,也会使得业务的复杂度上升,级联关系变得很重,耦合度增加。这种适合瀑布式开发。
而现在更多的是敏捷开发。实际上,项目在每个月2-3次发版的过程中,始终处于实验阶段,也就是需求会频繁的变更。所以就不能用表结构去代表模型,而是需要一个更加抽象的东西去代表。在软件设计的过程中,已经开始出现。首先定义业务边界,然后识别上限,进行架构设计。
DDD一般的流程会分为三个阶段:概念设计(上图123)、逻辑设计(生成业务模型,业务架构)、物理设计(所有开发人员,重新定义模型)。
领域驱动并不完全是概念,而是基于个人的经验落地,但是要使用DDD仍有几个要求:
- 项目中的架构师要求高,不仅技术要好,也要懂业务,才能更好的建立模型
- 对项目中其他角色要求也很高,要能看懂
- 项目要有一定的体量
所以学习DDD,了解这些名词含义只是一部分,还要能够根据学习到的理论知识,建立一套属于自己的方法论,一个解决复杂业务场景的设计方案。
- 领域驱动与微服务
2004年 Eric Evans发表了《领域驱动设计-软件核心复杂性应对之道》,确定了领域驱动设计的核心思想:通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。
但当时并未落地,直到2015年3月5日,springcloud 1.0.0 发布,出现了微服务的解决方案,2016年又推出了spring cloud Netflix第一个release版本。解决了很多问题,比如集群下的拓展性问题,弹性伸缩,敏捷开发等等。
但是微服务也暴露了一个很重要的问题:微服务的“微”到底有多小。每个人都是根据自己的经验进行拆分,而且都觉得自己很有道理,比如大部分会按照商品、订单拆分,虽然看上去是分离的,但直接的相互调用,与面向服务没有区别。还有一种是新写一个功能都会拆分成一个微服务,就会造成一个项目具有成百上千个微服务。服务越多,运维越难。
领域驱动设计定义领域模型,从而划分领域边界,然后再根据领域边界,从业务的角度去进行微服务边界的定义。
(但这不代表领域驱动设计不适合单体应用的设计,只是微服务架构更贴合领域设计的思想)
同样,领域驱动设计也不是银弹,这只是工具,大家要学会灵活的使用。比如当年的SSH(SSM)现在也并不是可以解决任何问题。
那么,用什么方式定义边界?
战略设计:从业务角度出发,建立业务模型,划分业务边界
战术设计:根据业务模型进行技术实现,完成软件开发及落地
2.1 战略设计
包含以下几个部分:
领域、子域(核心子域、通用子域、支撑子域)、通用语言、限界上下文
领域:一个领域本质上可以理解为是一个问题域。也就是指核心的业务,比如传统的电商就包括:商品、物流、订单、支付、库存等等,也就是一个项目其实可以认为是一个领域。
子域:更加细分的领域,甚至子域也可以更加细分。如果电商可以作为一个领域,那么商品、订单、物流这些就是子域,订单子域还可以继续拆分成商品订单、增值订单等等。很像微服务,微服务是架构风格,DDD是思想。
核心子域:整个系统的核心。受限于预算,人员
插入问题:相同领域的领域模型,能不能复制?
--不可以。比如淘宝和京东。
淘宝 C2C 交易保证 确保卖家能卖出去,买家能买到(让天下没有难做的生意)
京东 B2C 口碑 突出了产品质量 采购 运输 仓储
苏宁 线下选货 线上卖货 更注重线下选址和流量入口
所以要做到兼顾,淘宝也注重产品质量,但是更重要的是交易。
所以,如何明确业务核心,也就是如何精炼业务域?
分为以下五种方式: - 领域愿景说明(Domain Vision Statement)立项的目标,挣钱的方式
- 突出核心(Highlight Core)不要在复杂业务中丢掉了核心
- 内聚机制(Cohensive Mechanism)抽取主流程,辅助的放到其他工具包中
- 分离的核心(Segregated Core)不断调整子域,调整核心
- 抽象核心(Abstract Core)把基本概念抽取,只用来描绘而非实现
核心子域并不会一次识别产生,而是一次次的调整慢慢产生的。
通用子域:整个领域都会用到的子域。
支撑子域:不包含核心的功能,也并不通用,但必须支撑的部分。
所以领域驱动就是将问题细化,不断拆分找到核心,倾斜资源保证核心子域。
问题:项目中的产品、技术、测试、架构师、运营如何保证理解统一?
语义问题:能穿多少穿多少 YYDS
限界上下文:用来封装通用语言和领域对象,提供上下文环境,保证一些术语有明确的含义,没有二义性,边界定义了模型的范围。
领域建模尽量不要用UML建模,因为UML只适用于小范围复杂度小的需求,否则会极其复杂。
建议使用:四色建模法、限界纸笔法、事件风暴。
业务模型的建立,有三种:
- 业务角色 表示角色和所承担的责任
- 业务实体 表示与业务角色交互所需要的工作、资源、事件2
- 业务用例 角色与实体的业务链路、工作流程
有四种模型实现方式:失血模型、贫血模型、充血模型、胀血模型。
贫血模型:spring推荐使用,比如User模块,目前将固有属性及操作放到user中,而把行为属性放到userManage中,比如userManage.save()。只适合简单逻辑场景,代码易于理解和实现。
比如:下单过程中,需要校验商品库存,那么校验库存的逻辑,按照贫血模型就会放到生单的模块中,而非在商品模块中。
充血模块:将固有属性和持久行为都放到了实体中。也就是user.save();缺点也存在:业务不好划分,而且模型中有大量操作,实例化时增加了不必要的损耗。
DDD分层架构设计
要进行设计,代码分层,让开发人员可以根据我们的标准写代码。
MVC(设计模式) 和 三层架构(软件架构)
分层架构就是为了达到四个目的:高内聚、低耦合、复用性设计、拓展性设计。
领域驱动的四层架构设计:用户界面层、应用层、领域层、基础设施层。
现在spring推崇MVC模式,所以大家不再进行系统框架设计,而是直接使用,controller service dao的模式,model作为其中传输的对象。但是无法避免,很多程序员会把业务逻辑写到了controller里。而且对于交互,model对象实用性很高,model一定要简洁,所以必须使用贫血模型。另外,model作为传输介质,也会产生多对象耦合。
所以,James O. Coplien 《C++多泛型设计》作者和 Trygye Reenskaug MVC模式的提出者,两位发表了一个新的设计模式《DCI架构:面向对象编程的新构想》。
DCI:Data Context Interaction
比如电影,你也是需要关注三个方面,人(物)&场景&发生了什么事。
2.2 战术设计
实体:是指描述了领域中唯一的且可持续变化的抽象模型。通常建模时,名词用于给概念命名,形容词用于描述这些概念,而动词则表示可以完成的操作。
比如交友APP中,有 唯一标识、具有可变性的状态、有角色和职责 的就是---人。人的唯一标识肯定不会是姓名,比如使用身份证、手机号或者一串数字。可变性就是年龄,性格,喜好等等。角色和职责就是指年龄层,已婚未婚,性别等等,肯定不能随便匹配。
值对象:描述了领域中的一件东西,将不同的相关属性组合成一个概念整体,当度量和描述发生改变时,可以用另一个值对象进行代替,属性判等,固定不变。(只关心值的类)
比如定外卖,要首先选择送餐地址,那么地址就是一个值对象,地址可能包括城市、街道、小区、楼栋等等,但是只关心这个值。
聚合:实体和值对象会形成聚合。每个聚合一般是在一个事务中操作,一般都有持久化操作。聚合中,根实体的生命周期决定了聚合整体的生命周期。
订单中也包括商品,那么订单中的商品就是聚合。
工厂和仓库 Factories & Repositories
方法:
四色建模法:
暂时无法在文档外展示此内容
用例分析法
事件风暴
领域故事讲述法
需注意: