什么领域驱动模型
领域驱动模型一种设计思想,我们又称为DDD设计思想。是一种为了解决传统设计思想带来的维护困难,沟通困难和交互困难而产生的一种新的思想。
架构模式的演进
单体架构
采用面向对象的设计方法,系统包括业务接入层、业务逻辑层和数据库层,采用经典的三层架构。
这种架构的问题:
- 贫血模型:只起到数据类的作用,业务逻辑散落到 service,可维护性越来越差
- service 类承接的所有的业务逻辑,越来越臃肿,很容易出现几千行的 service 类
- 对外接口直接暴露实体模型,导致不必要开放内部逻辑对外暴露,就算有 DTO 类一般也是实体类的直接 copy
- 外部依赖层直接从 service 层调用,字段转换、异常处理大量充斥在 service 方法中
这种架构容易使系统变得臃肿,可扩展性和弹性伸缩性差
- 贫血模型:只有get、set方法,所有的业务逻辑都不包含在内,而是放在service中
- 充血模型:数据和对应的业务逻辑被封装到同一个类中
分布式微服务架构
随着微服务架构理念的提出,单体架构向分布式微服务架构演进。微服务的设计目的是为了让大型软件系统解耦。将不同职责的服务独立部署,从而实现服务内部高内聚,服务之间低耦合的效果,让开发变得更为灵活。
这种架构的问题:
微服务的拆分困难,微服务到底应该如何拆分和设计呢?微服务的边界应该在哪里?
我们可以利用 DDD 设计方法来建立领域模型,划分领域边界,再根据这些领域边界从业务视角来划分微服务边界。
DDD的优点
- 领域模型中的实体只反映业务,和任务技术实现无关,像MVC架构中的贫血模型无法反映业务,不知道这个实体跟哪些业务相关联,使得我们不敢修改这实体。
- 领域模型确保了我们的业务逻辑都在一个模型中,都在一个地方,这样对提高软件的可维护性,业务可理解性以及可重性都有很好的帮助。
- 领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;领域模型是有边界的,只反应了我们在领域内所关注的部分。
- 领域专家、设计人员、开发人员通过领域模型进行交流,彼此共享知识与信息因为大家面向的都是同一个模型,所以可以防止需求走样,可以让软件设计开发人员做出来的软件真正满足需求;
领域驱动设计的最大价值是让我们告别从面向过程式的思想(天马星空,想到哪写到哪)转化为基于系统化的模型驱动思维。
基本概念
领域
领域:
DDD 的领域就是这个边界内要解决的业务问题域 。
领域往往就是业务的某一个部分 , 例如电商的销售部分、物流部分、供应链部分等, 这些对于电商来说就是各个领域(模块),领域主要作用就是用来驱动范围, DDD 会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。
子域:相对的一个概念, 我们可以将领域进行进一步的划分 , 这时候就是子域, 甚至可以对子域继续划分形成 子子域(依旧叫子域)。
实体和值对象
实体:
实体以 DO(领域对象)的形式存在,拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致(唯一标识)。实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现。简单来说就是有唯一标识+业务行为方法。
值对象:
实体相对应的就是值对象,如果没有唯一标识就是值对象。值对象一般是嵌套在实体里面的。嵌套方式可能有两种,一种是直接嵌套整个Java对象,二是嵌套一个json对象。其次值对象一般是不会改变的,变化的时候也是整体替换。值对象没有太多的业务行为更多的是对实体的一个修饰和描述,离开了实体就没有任何存在的业务意义
实体可以理解为一张数据库表,必须有主键。值对象没有主键,依附于实体而存在,比如用户实体下住址对象,一般在数据库中已 json 字符串的形式存在;最常见的值对象是枚举。
案例 1:以属性嵌入的方式形成的人员实体对象,地址值对象直接以属性值嵌入人员实体中。
案例 2:以序列化大对象的方式形成的人员实体对象,地址值对象被序列化成大对象 Json 串后,嵌入人员实体中。
聚合和聚合根
聚合根
聚合本身也是一个实体,聚合可以包含其他实体,其他实体不能脱离聚合而单独提供服务,比如一篇文章下的评论,评论必须从属于文章,没有文章也就没有评论。仓库层(repository)也必须是以聚合为核心提供服务的。
聚合
聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。
领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。
如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。
限界上下文
用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。这个边界定义了模型的适用范围,使团队所有成员能够明确地知道什么应该在模型中实现,什么不应该在模型中实现。
正如电商领域的商品一样,商品在不同的阶段有不同的术语,在销售阶段是商品,而在运输阶段则变成了货物。同样的一个东西,由于业务领域的不同,赋予了这些术语不同的涵义和职责边界,这个边界就可能会成为未来微服务设计的边界。看到这,我想你应该非常清楚了,领域边界就是通过限界上下文来定义的
边界体现在以下几方面:
- 领域逻辑层:确定了领域模型的业务边界,维护了模型的完整性与一致性,从而降低系统的业务复杂度;
- 团队合作层:限界上下文一般也是用户换分团队的依据;
- 技术实现层:限界上下文可当成是微服务的划分边界;
领域服务
单个实体对象能处理的逻辑放到实体里,多个实体或有交互的场景放到领域服务里。
领域服务可不可以调用仓储层或外部接口? 可以,但不能直接和领域服务代码放一起,领域服务模块存放API,实现放基础层(infrastructure)。
领域服务对象不建议直接以聚合名+DomainService命名,而要以操作命令关联,比如用户保存服务命名为:UserSaveService, 审核服务:UserAuditSerivce。
可以使用领域服务的情况:
- 执行一个显著的业务操作
- 对领域对象进行转换
- 以多个领域对象作为输入参数进行计算,结果产生一个值对象
DDD分层架构
从三层架构向 DDD 分层架构演进的过程
- DDD 分层架构在用户接口层引入了 DTO,给前端提供了更多的可使用数据和更高的展示灵活性。
- DDD 分层架构对三层架构的业务逻辑层进行了更清晰的划分,改善了三层架构核心业务逻辑混乱,代码改动相互影响大的情况。
- DDD 分层架构将业务逻辑层的服务拆分到了应用层和领域层。应用层快速响应前端的变化,领域层实现领域模型的能力。
- 三层架构数据访问采用 DAO 方式;DDD 分层架构的数据库等基础资源访问,采用了仓储(Repository)设计模式,通过依赖倒置实现各层对基础资源的解耦。
仓储又分为两部分:仓储接口和仓储实现。仓储接口放在领域层中,仓储实现放在基础层。原来三层架构通用的第三方工具包、驱动、Common、Utility、Config 等通用的公共的资源类统一放到了基础层。
整体架构
- 接口层
- 接口服务位于用户接口层,用于处理用户发送的Restful请求和解析用户输入的配置文件等,并将信息传递给应用层。
- 应用层{application}
- 应用服务位于应用层。用来表述应用和用户行为,负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装。
- 应用服务可对微服务内的领域服务以及微服务外的应用服务进行组合和编排,或者对基础层如文件、缓存等数据直接操作形成应用服务,对外提供粗粒度的服务。
- 领域层{domain}
- 领域服务对同一个实体的一个或多个方法进行组合和封装,或对多个不同实体的操作进行组合或编排,对外暴露成领域服务。**领域服务封装了核心的业务逻辑。实体自身的行为在实体类内部实现,向上封装成领域服务暴露。 **
- 基础层{infrastructure}
- 基础服务位于基础层。为各层提供资源服务(如数据库、缓存等),实现各层的解耦,降低外部资源变化对业务逻辑的影响。
- 基础服务主要为仓储服务,通过依赖反转的方式为各层提供基础资源服务,领域服务和应用服务调用仓储服务接口,利用仓储实现持久化数据对象或直接访问基础资源。