为了账号安全,请及时绑定邮箱和手机立即绑定

DDD学一步写一篇--DDD开发实践流程

标签:
大数据

DDD大家讨论的比较多的一般都是DDD的思想和理论,很少有文章讨论具体是如何实施和落地,所以这也导致很多同学看完了Evans经典巨著后对DDD还是不知道如何去实施。这篇文章我们讨论下DDD的实施步骤,聊聊怎么一步步在项目中实施DDD。

在习惯了传统的数据驱动开发模式后,View、Service、dao这种三层分层模式,开发者会很自然的写出过程式代码,这种开发方式中的对象只是数据载体,而没有行为,是一种贫血对象模型。以数据为中心,以数据库ER图为设计驱动,分层架构在这种开发模式下可以认为是数据处理和实现的过程。

数据驱动模式业务逻辑都是写在Service中的,定义的实体模型充其量只是个数据载体,没有任何行为。简单的业务系统采用这种贫血模型和过程化设计是没有问题的,但在业务逻辑复杂了,业务逻辑、状态会散落到在大量方法中,原本的代码意图会渐渐不明确,我们将这种情况称为由贫血症引起的失忆症。

所以才有了目前很多大牛推崇的DDD-领域驱动开发模式,将实体模型的数据和行为封装在一起,并与现实世界业务领域中的业务对象进行映射。将领域业务逻辑分散到领域对象中。

在目前主流的微服务开发过程中,其实可以把DDD中的限界上下文看作一个微服务,这个微服务对外提供的服务是高内聚、低耦合的。微服务架构强调的是从业务纬度去拆分系统,而DDD也是同样看重业务领域。

DDD的核心诉求就是将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构。而微服务追求业务层面的复用,设计出来的系统架构和业务一致;在技术架构上则系统模块之间充分解耦,可以自由地选择合适的技术架构,去中心化地治理技术和数据。

DDD设计流程

按照实现领域驱动设计一书中描述的DDD步骤主要有4步:

  1. 根据业务需求划分出初步的领域和限界上下文,以及上下文之间的关系;

  2. 进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象;对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;

  3. 为聚合根设计仓储,并思考实体或值对象的创建方式;

  4. 在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构。

1. 战略建模

在拿到一个业务需求的时候,DDD首先需要进行战术建模,战术建模其实就是DDD的第一步,根据具体业务区分子域,搞清楚各个限界上下文之间的关系。

限界上下文之间的映射关系主要有下面这几种:

  • 合作关系(Partnership):两个上下文紧密合作的关系,一荣俱荣,一损俱损。

  • 共享内核(Shared Kernel):两个上下文依赖部分共享的模型。

  • 客户方-供应方开发(Customer-Supplier Development):上下文之间有组织的上下游依赖。

  • 遵奉者(Conformist):下游上下文只能盲目依赖上游上下文。

  • 防腐层(Anticorruption Layer):一个上下文通过一些适配和转换与另一个上下文交互。

  • 开放主机服务(Open Host Service):定义一种协议来让其他上下文来对本上下文进行访问。

  • 发布语言(Published Language):通常与OHS一起使用,用于定义开放主机的协议。

  • 大泥球(Big Ball of Mud):混杂在一起的上下文关系,边界不清晰。

  • 另谋他路(SeparateWay):两个完全没有任何联系的上下文。

通过绘制全局的限界上下文的关系,能够梳理清楚业务需求中的各个领域、子领域之间的关系。

2. 战术建模—细化上下文

战术建模很重要的一步就是区分出整个限界上下文中的实体、值对象、聚合,战术建模其实对应了Entity、ValueObject、Aggregate。我们要提炼出业务中的精华,合理的抽象为这3个概念,并且这种抽象是需随着领域里的概念变化而变化的。这3者的结合运用会让我们的项目活起来,这是DDD的核心。这里再把这3个概念重新梳理一下。

Entity(实体): 每个实体是唯一的,并且可以相当长的一段时间内持续地变化。我们可以对实体做多次修改,故一个实体对象可能和它先前的状态大不相同。但是,由于它们拥有相同的身份标识,他们依然是同一个实体。例如一件商品在电商商品上下文中是一个实体,通过商品中台唯一的商品id来标示这个实体。

ValueObject(值对象):值对象用于度量和描述事物,当你只关心某个对象的属性时,该对象便可作为一个值对象。实体与值对象的区别在于唯一的身份标识和可变性。当一个对象用于描述一个事物,但是又没有唯一标示,那么它就是一个值对象。例如商品中的商品类别,类别就没有一个唯一标示,通过图书、服装、3C这些值就能明确表示这个商品类别。

不同上下文领域中的实体,在当前上下文中需要建模为值对象,因为当前领域无法直接修改这些对象的内部属性。

Aggregate(聚合):聚合类是实体的升级,是由一组与生俱来就密切相关实体和值对象组合而成的,这整个组合的最上层实体就是聚合。聚合类建议设计成小聚合,可以只包含根实体,而不需要包含其他实体,即使一定要包含,可以考虑将其设计成值对象。通过唯一标识来引用其他聚合或者实体,如果是外部上下文中的实体,引用其唯一标识或将需要的属性构造成值对象。如果聚合类创建过于复杂可以将其的创建动作封装在工厂方法里。

聚合内部多个组成对象的关系可以用来指导数据库建表,但是肯定会存在一些问题,例如最常见的问题,一个聚合类中有一个List的值对象,那么在数据库中建立1:N的关联需要将值对象单独建表,这时候值对象表的id就不要暴露到资源库外部,让外部不可见。

领域服务:一些重要的领域行为可以定义为领域服务,简单的原则可以认为一些操作不适合放在实体或值对象,那么就可以把这些领域的组合行为定义为领域服务,这里的领域服务也有点类似于我们常用的3层架构的service层,但是不同的是,领域服务中是不包含实体类中对实体自己操作的行为,实体自操作的行为都是封装在实体类内部的。一切领域逻辑的对外暴露都需要通过领域服务来完成。

3. 数据仓储设计

DDD的设计过程中很多同学对于数据仓储的设计存在疑问,不知道DO如何存储到数据库中,其实业务DO最后落表到数据库中也没有什么特殊的处理方式,主要还是将DO转成PO(持久化对象)来进行数据存储,PO的结构是和存储表结构对应的,这样就将DO的业务模型保存到了技术数据库中。下面介绍下整个DDD中数据转换的流程:


webp

DDD数据转换流程2

首先领域的开放服务通过信息传输对象(DTO)来完成与外界的数据交互;在领域内部,我们通过领域对象(DO)作为领域内部的数据和行为载体;在资源库内部,我们沿袭了原有的数据库持久化对象(PO)进行数据库资源的交互。同时,DTO与DO的转换发生在领域服务内,DO与PO的转换发生在资源库内。

与以往的业务服务相比,当前的编码规范可能多造成了一次数据转换,但每种数据对象职责明确,数据流转更加清晰。

//数据库资源import com.shrb.mobile.pay.card.repo.dao.CardDao;//数据库访问对象-银行卡import com.shrb.mobile.pay.card.repo.dao.po.CardPO;//数据库持久化对象-银行卡import com.shrb.mobile.pay.card.repo.dao.po.CardTransferPO;//数据库持久化对象-奖池import com.shrb.mobile.pay.card.repo.cache.CardCacheAccessObj;//分布式缓存访问对象-银行卡缓存访问import com.shrb.mobile.pay.card.repo.repository.CardRepository;//资源库访问对象-银行卡资源库

4. 工程实施

通过模块将限界上下文进行区分,可以通过com.公司名.归属团队.业务.上下文.* 这样的包命名结构进行命名,这样可以将一个上下文限定在一个包的内部。

import com.shrb.mobile.pay.card.*;//支付卡上下文import com.shrb.mobile.pay.riskcontrol.*;//支付交易风控上下文import com.shrb.mobile.pay.route.*;//支付路由上下文import com.shrb.mobile.pay.thirdparty.*;//外部三方支付上下文

对于模块内的组织结构,一般情况下我们是按照领域对象、领域服务、领域资源库、防腐层等组织方式进行定义,这样也可以和DDD中各个概念对应起来。

import com.shrb.mobile.pay.card.domain.valobj.*;//领域对象-值对象import com.shrb.mobile.pay.card.domain.entity.*;//领域对象-实体import com.shrb.mobile.pay.card.domain.aggregate.*;//领域对象-聚合根import com.shrb.mobile.pay.card.service.*;//领域服务import com.shrb.mobile.pay.card.repo.*;//领域资源库import com.shrb.mobile.pay.card.acl.*;//领域防腐层

5. 防腐层

上面介绍了DDD在具体实施过程中的4个主要步骤,但是在有些情况下还需要引入防腐层,有以下几种情况会考虑引入防腐层:

  • 需要将外部上下文中的模型翻译成本上下文理解的模型。

  • 不同上下文之间的团队协作关系,如果是供奉者关系,建议引入防腐层,避免外部上下文变化对本上下文的侵蚀。

  • 该访问本上下文使用广泛,为了避免改动影响范围过大。

将领域行为封装到领域对象中,将资源管理行为封装到资源库中,将外部上下文的交互行为封装到防腐层中。能够发现领域服务本身所承载的职责也就更加清晰了,即就是通过串联领域对象、资源库和防腐层等一系列领域内的对象的行为,对其他上下文提供交互的接口。

其实我的理解防腐层就是一个不同限界上下文之间的Adapter,主要做的工作就是数据转换,将其他领域的实体转换成当前限界上下文可以处理的实体,在不同上下文中做了一层隔离,这样当其他限界上下文业务实体有改动的时候,可以将对本限界上下文的影响降到最小。

总结

这是写的关于DDD的第二篇文章,这端时间主要看了领域驱动设计和领域驱动设计实现两本书,两本书总体还是偏抽象理论,但是一旦理解了核心理念,再结合目前自己非常熟悉的业务领域,那么对DDD会有更深入的理解。



作者:monkey01
链接:https://www.jianshu.com/p/8392c63b0136


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消