拥抱毒瘤 DDD

啥是 DDD?

Domain-Driven Design
image.png
《领域驱动设计:软件核心复杂性应对之道》
按照作者自己的说法,“DDD 是一种开发复杂软件的方法”
假如你 Java 代码写得特别溜,那么可以说你掌握了面向对象的编程方法;假如你还很熟悉面向对象的设计原则,掌握很多设计模式,那可以说你懂面向对象的设计方法;假如你能为业务概念构建领域模型,那么你就懂了面向对象的分析方法。面向对象的分析、设计、编码三种方法融会贯通,成为一个有机的整体,这个叫面向对象的方法学。而分析方法,或者说领域建模的方法,正是 DDD 的重点
业界有一句话 “DDD 就是 OO Done right”。OO 就是面向对象,也就是说把面向对象做对了, 就是 DDD

DDD 能解决什么问题?

和传统的我们日常用的比较多的 MVC 三层架构相比,DDD能解决的问题在于:
可读性、可维护性
MVC 三层架构写出的代码丢弃了大部分的业务语义,代码混乱,写出的代码自己几个月回头来看都不一定能看懂,接手你项目的人更看不懂
究其原因在于我们大部分基于 MVC 三层架构写代码的人长时间地被数据库思维所熏陶,碰到需求的第一个想法就是该建什么表,然后进行什么 CRUD 来完成业务逻辑,写着写着很容易地就会逐渐偏离业务语义,业务和技术进行了混合,如果是复杂大型项目 Service 层很容易就过于膨胀,久而久之这样的代码就成为了我们口中的“屎山”
如果用比较好的建模思维,去设计和开发代码,写出来的代码,是极为的漂亮的,代码本身就极为的贴合你的原始的业务语义,业务流程。代码自己就好像可以说话一样,它自己就代表了一套业务模型和流程
简单的项目,纯粹就是界面里的一些增删改查,表单的提交,数据更新,没什么太复杂的一个业务逻辑的,不要用 ddd,反而会加深复杂度,做的是得不偿失,ddd 是用来实现复杂的业务逻辑的

如何运用 DDD 去进行系统设计?

简而言之,包含四大步:战略设计、战术设计、代码落地、技术填充。
其中战略设计包含:划分有界上下文、确定上下文之间的映射关系、划分子域类型、建立通用语言。
战术设计则包含:牵扯到了类层面的设计,设计你的上下文里有哪些类,这些类如何配合可以实现上下文里要解决的各种各样的问题。类层面的设计包括:聚合、实体、值对象、仓储、领域服务、业务组件、领域事件、命令。通过这些类去串起完整的业务流程。
代码落地则需要结合:DDD 分层架构、清洁架构、六边形架构、CQRS 架构以及业内最佳实践比如阿里的 COLA 框架等去实现,而不是单纯地生搬硬套某一种框架,DDD 是一个千人千面的东西,你可以做成类 DDD、泛 DDD 的架构也行

战略设计

怎么划分有界上下文?

事件风暴会议
自己跟几个开发同事,负责这个项目的,来进行头脑风暴,必须根据自己对业务的理解,把技术的东西全部剥离掉,把数据库表的设计全部剥离掉,纯粹站在业务的角度,去进行风暴会议,梳理出所有的业务相关的流程和东西,大家一起对这个业务领域的所有事件进行梳理。
1)召集一个会议,一块儿进行头脑风暴,一块儿来梳理和说出来,业务相关的所有的事件(动词、动作),有多少事件就说多少,但凡是跟这个业务事情相关的事件,都可以说出来
2)把所有梳理出来的业务相关的事件,按照时间线进行排列
3)梳理出来业务相关的所有的用户界面和命令,针对上面的所有事件的发生,是否有系统用户界面,有用户参与进来发出一个指令、命令(在系统界面里,点击按钮、提交表单、发起操作),驱动了业务各个事件的执行
4)把用户界面、命令、事件,按照时间线,先后顺序,交互逻辑,全部串联起来,一环一环的
5)在上面的那个大串联的逻辑里,找出来我们业务的有界上下文,哪些事情应该是属于我们业务要解决的问题,哪些事情明显是属于别的有界上下文,是属于别人要去做的事情
这通常来说也可以作为微服务划分的依据。

怎么划分子域类型?

确定了有界上下文之后,一般来说都是去把每个有界上下文跟子域一一对应起来,有界上下文都是一个子域,每个子域都有自己的类型,核心子域、支持子域、通用子域。
核心子域
对于一个电商领域而言,用于完成用户购物这个核心需求,所需要具备的一些核心的子域,就是属于核心子域,商品子域、订单子域、支付子域、履约子域、会员子域、营销子域
支持子域
负责支持核心子域,作用是用于支持核心子域的功能实现,属于支持性的子域,比如仓储子域、物流子域就是支持履约子域的,履约的过程中,需要仓储和物流的支持;财务子域支持报表子域的业务运作
通用子域
不用自己公司来开发了,都是外购的软件,或者是第三方的通用的平台和系统,比如第三方支付平台,第三方物流平台
一般来说,子域和有界上下文就是一一对应。

上下文之间的映射关系有哪些?

separate way
完全没关系
customer-supplier或者upstream-downstream
简称 c-s 或者 u-d,对于几十个人的团队,做一个大的项目,有几个小组,每个小组负责维护一个系统,系统之间有依赖的关系,我要调用你的接口,你是上游,我是下游,我们的关系和距离,组织结构是比较近的,所以这个时候往往就是我有一些接口的需求,我们是可以很好的进行一个商量的
publish-subscrib
p-s,有一方发布一个事件,另外一方监听事件以及处理这个事件,这种关系,说句实话,在企业开发里,非常的常见,一般来说,就是对于你一个有界上下文,如果你要跟别的有界上下文进行交互,你可以选择发布一个事件出去;你作为一个有界上下文,你还可以自己发布事件,自己去监听和处理这个事件
anti corruption layer
acl,防腐层不是一个孤立、单独使用的映射关系,一般是跟其他的映射关系,一起进行使用,CS 关系,PS 关系,CS 关系来举个例子,supplier 给我接口返回的东西,本来这个东西大家约定好了里面有8个字段,但是现在虽然还是8个字段,可是里面有的字段的名称变化了(其实本质没变化,可是名称变化了),或者是字段的类型变化了,或者是字段的值,可选的值变化了。对于我来说,是不是说直接修改我的代码呢?一般来说可以加一个防腐层,acl 层,一般来说会把你返回过来的东西,做一个适配和转化,把它转为我内部的一个我自己固定的一个对象,我固定的对象,它字段的名称、类型、值,都是不变的。如果你那里返回的东西有变化,此时我可以在我的防腐层的代码里,做一个转化,把他转换位我固定的不变的东西就可以了
open host service+published language
OHS+PL,对于第三方支付平台,第三方物流平台,saas 云平台(提供出来 API 接口),open host service,开放主机服务,开放平台接口,他们会定义一套自己的通信协议和数据格式,通信协议都是 HTTP 协议,数据格式一般来说都是他们定义好结构的 JSON 格式的数据。
PL 就是 HTTP 协议+自定义 JSON 数据格式
conformist
u-d,完全没法商量,不同的大的部门之间,系统之间要进行调用,跨了部门了以后,对外提供的给别人调用的接口,可能都是固定死的,一般来说你不太能去跟他们商量,新增一个接口,这种商量可能是极为极为难的,你要用,就直接用就可以了,遵奉他们的调用模式,没法商量。平台技术部,专门提供一些基础性的系统和接口出来,人家是为全公司服务的,他一般是收集大家的普适性的需求,按照自己的计划迭代和升级的
shared kernel(很少见)
共享内核,两个有界上下文之间,共享一个数据模型,数据模型是两个团队一起维护,如果有修改,大家就一起接受这个修改
partnership(很少见)
强耦合,合作。那种小而紧凑的系统,中小型的系统,有几个人独立维护和开发,负责了不同的模块,也按 DDD 划分为了多个子域,每个子域(有界上下文)就对应了一个微服务,系统可以划分为多个微服务,服务之间的耦合性特别的高,做大量的需求,必须一起评审,一起开发,一起修改,一起上线成功,一起上线失败

customer-supplier、publish-subscribe、conformist、ACL、OHS+PL 比较常见。

为何要搞通用语言?

动词和名词的命名规则和规范,都确定下来,这就是一套通用语言,如果你要做 ddd 的话,上下文、子域、映射关系、带着你的团队,把你负责的上下文里的通用语言,一定要制定一套出来,后续的代码开发和编码,就必须要按照这套通用语言里规定的英文单词和命名规范来写

战术设计

如何进行类的设计?

实体
有唯一标识(比如 id)而且他的部分字段数据是允许变化的
值对象
绝对不会变化的,比如 id
聚合
代表了多个 class 绑定在一起,放在一块,最上层包着的 class 叫聚合根。
聚合里面的 classes,生命周期,是一致的,创建的时候,就一起创建了,更新的时候,就一起更新了,删除的时候就一起删除了,一个聚合里面的这些东西的更新,必须放在一个事务里,确保一起成功或者一起失败
主要你是要去观察一个聚合根跟其他各个关联聚合类,他们之间的关系紧密程度,如果大家明显一堆东西一起组成了一个大的生命周期保持一致的,你可以建模成领域上下文里的一个聚合,一个聚合里可以包含一些实体,也可以包含一些值对象,聚合也可以是多个层次的
最与众不同的一点,要把聚合(实体、值对象),设计成充血模型,对于你的聚合而言,要在里面根据你的业务语义,放进去对应的一些核心的符合业务语义的业务逻辑行为。
领域服务
它主要是用来还原这个领域业务逻辑和业务语义的,是一个大的概念
业务组件
有一些业务行为,是没有办法放在聚合里面的,比如多个聚合之间的操作这个时候就需要业务组件来实现,是一个小的概念
命令
人驱动发起的命令:一些核心的业务动作和行为,应该是人驱动的,web 系统的网页界面、手机 app 的界面,通过 UI 界面,人会下发一些指令,执行一些动作,执行一些操作,人驱动的时候他直接下发的就是一个一个的命令,Command 一般来说对应的都是更新数据一类的动作,如果他要进行一些查询类的动作一般会叫做 Query。Command 和 Query,CQRS 命令和查询分离的架构设计里延伸出来的,是用来驱动我们的领域服务的核心业务逻辑和方法的执行的
领域事件
一般来说是异步的,一边发布事件,一件监听事件,比如说可以用 mq 来实现
仓储
就是用来把我们的聚合数据、实体,把我们的数据跟具体的持久层进行一个交互这样,如果说我们要从我们持久层查询一个聚合出来,此时就可以用那个聚合对应的仓储来查询。对我们领域模型层来说,是不关注具体的技术细节,具体的技术细节,都是封装在仓储里面的,crud、缓存、es、建立es索引、写mq、消费mq

验证你的 ddd 做的对不对,成功与否,关键点在于,上面这套东西配合起来,能够实现一个效果就是,用代码,还原出来一套完整的业务语义、业务流程、业务模型-> 这套还原出来的代码,一个验收的标准就是这套代码,主流程,如果找PM产品经理来走读代码,让他结合英文单词的含义,来读代码,结合你的一些注释,是否可以直接通过英文单词的含义,把他理解的业务语义和流程能够理解出来,才算 ddd 成功了,ddd 成功的一个关键的点,在于说你的代码做出来基本贴合业务语义和流程,抛弃掉技术细节不说,底层的 crud,sql,缓存,nosql,mq,es,搜索,数据库连接池,单纯是业务代码主流程,能把业务语义还原出来,ddd 就成功了

代码落地

ddd 的代码如何落地?

清洁架构
image.png
外层严格依赖内层,最外是接口、基础设施这样的层(根外面打交道),中间是网关、接口、展示器层(防腐、适配转换)、再里面是用户用例层(业务编排),然后是实体(领域建模)。
说实话这种比较少,主要还是太死了。
六边形架构
image.png
最外边其实主要是负责对外交互以及与基础设施打交道,然后进行一个防腐、适配转换,最内层是核心应用程序和领域模型负责领域建模以及业务编排
CQRS 架构
image.png
读和写严格分离,写通过命令传入领域层进行处理,然后通过仓储生成事件进行保存,也通过事件同步给读的库,当需要查询的时候通过查询到读库查询
说实话这样严格分离读写导致的同步其实也是很麻烦
ddd 分层架构
interface
接口层,controller(web)、api(rpc)、listener(异步)来组成,直接跟外界进行交互
接口层收到了东西(http 请求、rpc 调用请求、event 事件),做一些转化(防腐层,把外部传递进来的东西,转化为内部的一些东西,ACL 防腐层,比如说把 http request 转化为一个内部的 command 或者是 query,rpc 调用请求也是同理,需要把请求-响应的模型,转化为内部的 command 或者是 query)
application service
应用服务层,主要是业务流程编排
应用服务层,就是用于进行业务流程编排,由ApplicationService 来进行流程编排,它来负责基于仓储、聚合、领域服务,执行各种各样的动作,把这个业务链路/链路里的各个环节和动作都完成
domain
aggregate、entity、value object、domain service、domain event、command、query、business component -> 把业务模型、业务流程、业务语义,完全用代码还原出来,让代码跟业务吻合,而不是完全从技术角度、数据库角度去设计和写出来的代码
infrastructure
基础设施层,repository,它主要是负责跟具体的基础设施进行交互,跟数据库、MQ、缓存、ES、nosql 以及其他的外部的基础设施一类的系统进行一个交互,偏向于技术流的地方,主要就是在这里
COLA
https://github.com/alibaba/COLA
alibaba 开源的一套 cola 思想和框架,这套思想跟 ddd 建模设计的思想有一些相通之处,不是完全一样的,也都是为了业务系统的建模和设计去做的,提供了一套开源的框架,根据这套开源框架,就可以把按照思想建模的代码模型进行落地

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值