DDD架构
1.前言:
现今的软件开发完成基本的功能是比较简单的,但在开发的过程中如何保证代码的可维护和可扩展是比较复杂的一个问题。不管是从技术角度还是业务角度,如何解耦是一个比较值得深入探索的问题。在进入公司的一刻起,我的导师就告诉我实现功能固然简单,但如何保证代码的可维护性和系统的独立可扩展才是最重要的,而学习了DDD架构,本文记录一些DDD架构的理解。
2.概述:
DDD架构不是一个框架,而是一种架构思维,在开发系统的过程中,如何降低系统的复杂度是一个比较大的挑战,尤其是应对一些复杂、业务繁多的大型系统,系统架构的好坏直接影响该系统是否可以不断拓展,在更新迭代的过程中防止烂尾,减少重构升级的复杂度。按我理解来说其实就是基于传统的三层架构,划分更细,拆分更关注自己的领域,从而在后续升级系统或拆分服务时减少复杂度。
3.特点:
1.领域划分
2.统一语言
3.方便拆分
4.结构:
用户接口层(Interfaces):主要用于存放用户接口层与前端交互、展示数据相关代码,处理用户Restful请求。包含:assembler、dto 和 facade 三类。
·Assembler:实现 DTO 与领域对象之间的相互转换和数据交换。一般来说 Assembler 与DTO 总是一同出现。
·Dto:它是数据传输的载体,内部不存在任何业务逻辑,我们可以通过 DTO 把内部的领域对象与外界隔离。
·Facade:提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。
应用层(Application):主要用于存放应用层服务和编排相关代码,应用服务向下基于微服务内的领域服务或外部服务的应用服务完成服务的编排和组合,向上为用户接口层提供各种应用数据服务。包含:event 和 service
·Event(事件):聚合是聚合软件包的根目录,可以根据实际项目的聚合名称命名,如权限聚合。在聚合内定义聚合根、实体、值对象和领域服务之间的关系和边界,确保高内聚的业务逻辑。代码可以独立拆分为微服务。以聚合为单位放置代码是为了业务内聚,更重要的是为了将来微服务的重组。清晰的代码边界有助于实现以聚合为单位的微服务重组,在微服务架构演进中起重要作用。
·Service(应用服务):这层的服务是应用服务。应用服务会对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,是一段独立的业务逻辑。你可以将所有应用服务放在一个应用服务类里,也可以把一个应用服务设计为一个应用服务类,以防应用服务类代码量过大。
领域层(Domain):主要存放领域层核心业务逻辑相关代码,实体、方法。包含:entity、event、repository 和 service 四个子目录。
Aggregate(聚合):它是聚合软件包的根目录,可以根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。以聚合为单位的代码放在一个包里的主要目的是为了业务内聚,而更大的目的是为了以后微服务之间聚合的重组。聚合之间清晰的代码边界,可以让你轻松地实现以聚合为单位的微服务重组,在微服务架构演进中有着很重要的作用。
Entity(实体):它存放聚合根、实体、值对象以及工厂模式(Factory)相关代码。实体类采用充血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码
在领域服务中实现。
·Event(事件):它存放事件实体以及与事件活动相关的业务逻辑代码。
·Service(领域服务):它存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服务设计为一个类。如果领域服务内的业务逻辑相对复杂,我建议你将一个领域服务设计为一个领域服务类,避免由于所有领域服务代码都放在一个领域服务类中,而出现代码臃肿的问题。领域服务封装多个实体或方法后向上层提供应用服务调用。
·Repository(仓储):它存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,我们设定了一个原则:一个聚合对应一个仓储。
基础层(Infrastructure):主要存放基础资源相关代码,为其他各层提供通用技术支持,配置、三方jar包、数据库服务。包含::config 和 util 两个子目录。
·Config:主要存放配置相关代码。
·Util:主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码,你可以为不同的资源类别建立不同的子目录。
5.数据对象视图:
数据持久化对象 PO(Persistent Object):
持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应 PO 的一个(或若干个)属性。最形象的理解就是一个 PO 就是数据库中的一条记录,好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。也有团队使用DO(Data Object)表示数据对象
领域对象 DO(Domain Object):
领域对象,就是从现实世界中抽象出来的有形或无形的业务实体,使用的是充血模型设计的对象。也有团队使用用 BO(Business Objects)表示业务对象的概念。
数据传输对象 DTO(Data Transfer Object):
数据传输对象,主要用于远程调用之间传输的对象的地方。比如我们一张表有 100 个字段,那么对应的 PO 就有 100 个属性。但是客户端只需要 10 个字段,没有必要把整个 PO 对象传递到客户端,这时我们就可以用只有这 10 个属性的 DTO 来传递结果到客户端,这样也不会暴露服务端表结构。到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为 VO。DTO泛指用于展示层与服务层之间的数据传输对象,当然VO也相当于数据DTO的一种。
视图对象 VO(View Object):
视图对象,主要对应界面显示的数据对象。对于一个WEB页面,小程序,微信公众号等前端需要的数据对象。也有团队用VO表示领域层中的Value Object值对象,这个要根据团队的规范来定义。
简单对象POJO(Plain Ordinary Java Object):
简单对象,是只具有setter getter方法对象的统称。
基础层
基础层的主要对象是 PO 对象。我们需要先建立 DO 和 PO 的映射关系。当 DO 数据需要持久化时,仓储服务会将 DO 转换为 PO 对象,完成数据库持久化操作。当 DO 数据需要初始化时,仓储服务从数据库获取数据形成 PO 对象,并将 PO 转换为 DO,完成数据初始化。大多数情况下 PO 和 DO 是一一对应的。但也有 DO 和 PO 多对多的情况,在 DO 和 PO数据转换时,需要进行数据重组
领域层
领域层的主要对象是 DO 对象。DO 是实体和值对象的数据和业务行为载体,承载着基础的核心业务逻辑。通过 DO 和 PO 转换,我们可以完成数据持久化和初始化。
应用层
应用层的主要对象是 DO 对象。如果需要调用其它微服务的应用服务,DO 会转换为DTO,完成跨微服务的数据组装和传输。用户接口层先完成 DTO 到 DO 的转换,然后应用服务接收 DO 进行业务处理。如果 DTO 与 DO 是一对多的关系,这时就需要进行 DO数据重组。
用户接口层
用户接口层会完成 DO 和 DTO 的互转,完成微服务与前端应用数据交互及转换。Facade服务会对多个 DO 对象进行组装,转换为 DTO 对象,向前端应用完成数据转换和传输。
前端应用
前端应用主要是 VO 对象。展现层使用 VO 进行界面展示,通过用户接口层与应用层采用DTO 对象进行数据交互。
6.总结
DDD 是基于各种开发考虑,有很多场景下的设计原则,也用到了很多的设计模式。在使用的时候,我们总是担心或犹豫这是不是原汁原味的 DDD。其实我们不必追求极致的 DDD,这样做反而会导致过度设计,增加开发复杂度和项目成本。DDD 的设计原则或模式,是考虑了很多具体场景或者前提的。有的是为了解耦,如仓储服务、边界以及分层,有的则是为了保证数据一致性,如聚合根管理等。在理解了这些设计原则的根本原因后,有些场景你就可以灵活把握设计方法了,你可以突破一些原则,不必受限于条条框框,大胆选择最合适的方法