DDD 分层架构与微服务代码模型
通过领域驱动设计可以很有效的指导微服务的拆分,设计和实现. 通过合理的拆分微服务,分层结构可以有效的实现系统复杂性的治理,构建高内聚,低耦合,可演进的分布式系统.
用户接口层:面向前端提供服务适配,面向资源层提供资源适配。这一层聚集了接口适 配相关的功能。
应用层职责:实现服务组合和编排,适应业务流程快速变化的需求。这一层聚集了应用 服务和事件相关的功能。
领域层:实现领域的核心业务逻辑。这一层聚集了领域模型的聚合、聚合根、实体、值 对象、领域服务和事件等领域对象,以及它们组合所形成的业务能力。
基础层:贯穿所有层,为各层提供基础资源服务。这一层聚集了各种底层资源相关的服 务和能力。
[图片上传失败...(image-2d1270-1627378304899)]
微服务代码模型
微服务一级目录结构
微服务一级目录是按照 DDD 分层架构的分层职责来定义的。从下面这张图中,我们可以看 到,在代码模型里分别为用户接口层、应用层、领域层和基础层,建立了 interfaces、 application、domain 和 infrastructure 四个一级代码目录。
各层目录结构
-
用户接口层
Interfaces 的代码目录结构有:assembler、dto 和 façade 三类。
Assembler:实现 DTO 与领域对象之间的相互转换和数据交换。一般来说 Assembler 与 DTO 总是一同出现。
Dto:它是数据传输的载体,内部不存在任何业务逻辑,我们可以通过 DTO 把内部的领域 对象与外界隔离。
Facade:提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。
-
应用层
Application 的代码目录结构有:event 和 service。
Event(事件):这层目录主要存放事件相关的代码。它包括两个子目录:publish 和 subscribe。前者主要存放事件发布相关代码,后者主要存放事件订阅相关代码(事件处理 相关的核心业务逻辑在领域层实现)。
Service(应用服务):这层的服务是应用服务。应用服务会对多个领域服务或外部应用服 务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,是 一段独立的业务逻辑。你可以将所有应用服务放在一个应用服务类里,也可以把一个应用服 务设计为一个应用服务类,以防应用服务类代码量过大。
-
领域层
Domain 是由一个或多个聚合包构成,共同实现领域模型的核心业务逻辑。聚合内的代码 模型是标准和统一的,包括:entity、event、repository 和 service 四个子目录。
Aggregate(聚合):它是聚合软件包的根目录,可以根据实际项目的聚合名称命名,比 如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内 实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。
Entity(实体):它存放聚合根、实体、值对象以及工厂模式(Factory)相关代码。实体 类采用充血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码 在领域服务中实现。
Entity(实体):它存放聚合根、实体、值对象以及工厂模式(Factory)相关代码。实体 类采用充血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码 在领域服务中实现。
Service(领域服务):它存放领域服务代码。一个领域服务是多个实体组合出来的一段业 务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服 务设计为一个类。如果领域服务内的业务逻辑相对复杂,我建议你将一个领域服务设计为一 个领域服务类,避免由于所有领域服务代码都放在一个领域服务类中,而出现代码臃肿的问 题。领域服务封装多个实体或方法后向上层提供应用服务调用。
Repository(仓储):它存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接 口和仓储实现方法。为了方便聚合的拆分和组合,我们设定了一个原则:一个聚合对应一个 仓储。
-
基础层
Infrastructure 的代码目录结构有:config 和 util 两个子目录。
Config:主要存放配置相关代码。
Util:主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、 通用算法等基础代码,你可以为不同的资源类别建立不同的子目录。
从领域模型到微服务的设计
领域层的领域对象
-
设计实体
大多数情况下,领域模型的业务实体与微服务的数据库实体是一一对应的。但某些领域模型 的实体在微服务设计时,可能会被设计为多个数据实体,或者实体的某些属性被设计为值对 象。
我们分析个人客户时,还需要有地址、电话和银行账号等实体,它们被聚合根引用,不容易 在领域建模时发现,我们需要在微服务设计过程中识别和设计出来。
在分层架构里,实体采用充血模型,在实体类内实现实体的全部业务逻辑。这些不同的实体 都有自己的方法和业务行为,比如地址实体有新增和修改地址的方法,银行账号实体有新增 和修改银行账号的方法。
实体类放在领域层的 Entity 目录结构下。
-
找出聚合根
聚合根来源于领域模型,在个人客户聚合里,个人客户这个实体是聚合根,它负责管理地 址、电话以及银行账号的生命周期。个人客户聚合根通过工厂和仓储模式,实现聚合内地 址、银行账号等实体和值对象数据的初始化和持久化。
聚合根来源于领域模型,在个人客户聚合里,个人客户这个实体是聚合根,它负责管理地 址、电话以及银行账号的生命周期。个人客户聚合根通过工厂和仓储模式,实现聚合内地 址、银行账号等实体和值对象数据的初始化和持久化。
-
设计值对象
根据需要将某些实体的某些属性或属性集设计为值对象。值对象类放在代码模型的 Entity 目录结构下。在个人客户聚合中,客户拥有客户证件类型,它是以枚举值的形式存在,所以 将它设计为值对象。
有些领域对象可以设计为值对象,也可以设计为实体,我们需要根据具体情况来分析。如果 这个领域对象在其它聚合内维护生命周期,且在它依附的实体对象中只允许整体替换,我们 就可以将它设计为值对象。如果这个对象是多条且需要基于它做查询统计,我建议将它设计 为实体。
-
设计领域事件
如果领域模型中领域事件会触发下一步的业务操作,我们就需要设计领域事件。首先确定领 域事件发生在微服务内还是微服务之间。然后设计事件实体对象,事件的发布和订阅机制, 以及事件的处理机制。判断是否需要引入事件总线或消息中间件。 在个人客户聚合中有客户已创建的领域事件,因此它有客户创建事件这个实体。 领域事件实体和处理类放在领域层的 Event 目录结构下。领域事件的发布和订阅类我建议 放在应用层的 Event 目录结构下。
-
设计领域服务
如果一个业务动作或行为跨多个实体,我们就需要设计领域服务。领域服务通过对多个实体 和实体方法进行组合,完成核心业务逻辑。你可以认为领域服务是位于实体方法之上和应用 服务之下的一层业务逻辑。
按照严格分层架构层的依赖关系,如果实体的方法需要暴露给应用层,它需要封装成领域服 务后才可以被应用服务调用。所以如果有的实体方法需要被前端应用调用,我们会将它封装成领域服务,然后再封装为应用服务。
个人客户聚合根这个实体创建个人客户信息的方法,被封装为创建个人客户信息领域服务。 然后再被封装为创建个人客户信息应用服务,向前端应用暴露。
领域服务类放在领域层的 Service 目录结构下。
-
设计仓储
每一个聚合都有一个仓储,仓储主要用来完成数据查询和持久化操作。仓储包括仓储的接口 和仓储实现,通过依赖倒置实现应用业务逻辑与数据库资源逻辑的解耦。
仓储代码放在领域层的 Repository 目录结构下。
服务的协作
-
服务的类型
Facade 服务:位于用户接口层,包括接口和实现两部分。用于处理用户发送的 Restful 请 求和解析用户输入的配置文件等,并将数据传递给应用层。或者在获取到应用层数据后,将 DO 组装成 DTO,将数据传输到前端应用。
应用服务:位于应用层。用来表述应用和用户行为,负责服务的组合、编排和转发,负责处 理业务用例的执行顺序以及结果拼装,对外提供粗粒度的服务。
领域服务:位于领域层。领域服务封装核心的业务逻辑,实现需要多个实体协作的核心领域 逻辑。它对多个实体或方法的业务逻辑进行组合或编排,或者在严格分层架构中对实体方法 进行封装,以领域服务的方式供应用层调用。
基础服务:位于基础层。提供基础资源服务(比如数据库、缓存等),实现各层的解耦,降 低外部资源变化对业务应用逻辑的影响。基础服务主要为仓储服务,通过依赖倒置提供基础 资源服务。领域服务和应用服务都可以调用仓储服务接口,通过仓储服务实现数据持久化。
-
服务的调用
微服务的服务调用包括三类主要场景:微服务内跨层服务调用,微服务之间服务调用和领域事件驱动