DDD领域驱动设计实战-服务和数据在微服务各层协作的最佳实践

1 服务协作

1.1 服务的类型

按照分层架构设计出来的微服务,其内部各层服务主要功能和职责如下:

Facade服务

位于用户接口层,包括接口和实现两部分。用于处理用户发送的Restful请求和解析用户输入的配置文件等,并将数据传递给应用层。或者在获取到应用层数据后,将DO组装成DTO,将数据传输到前端应用。

应用服务

位于应用层。用来表述应用和用户行为,负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果拼装,对外提供粗粒度的服务。

领域服务

位于领域层。领域服务封装核心的业务逻辑,实现需要多个实体协作的核心领域逻辑。它对多个实体或方法的业务逻辑进行组合或编排,或者在严格分层架构中对实体方法进行封装,以领域服务的方式供应用层调用。

基础服务

位于基础层。提供基础资源服务(比如数据库、缓存等),实现各层的解耦,降低外部资源变化对业务应用逻辑的影响。基础服务主要为仓储服务,通过依赖倒置提供基础资源服务。领域服务和应用服务都可以调用仓储服务接口,通过仓储服务实现数据持久化。

1.2 服务调用

  • 微服务的服务调用场景

微服务内跨层服务调用

微服务架构采用前后端分离,前端应用独立部署。前端应用调用发布在API网关上的Facade服务,Facade定向到应用服务。应用服务作为服务组织和编排者,它的服务调用有如下两种路径:

  • 应用服务调用组装领域服务

    领域服务会组装实体和实体方法,实现核心领域逻辑。领域服务通过仓储服务获取持久化数据对象,完成实体数据初始化。

  • 应用服务直接调用仓储服务

    主要针对像缓存、文件等类型的基础层数据访问。这类数据主要是查询操作,没有太多的领域逻辑,不经过领域层,不涉及数据库持久化对象。

微服务之间的服务调用

微服务间的应用服务可直接访问,也可通过API网关。由于跨微服务操作,在进行数据新增和修改操作时,注意保证数据一致性。

领域事件驱动

领域事件驱动包括微服务内和微服务之间的事件。微服务内通过事件总线完成聚合之间的异步处理。微服务之间通过MQ完成。异步化的领域事件驱动机制是一种间接的服务访问方式。

当应用服务业务逻辑处理完成后,如果发生领域事件,可调用事件发布服务,完成事件发布。

当接收到订阅的主题数据时,事件订阅服务会调用事件处理领域服务,完成进一步的业务操作。

服务的封装与组合

  • 微服务的服务是从领域层逐级向上封装、组合和暴露

基础层

服务形态主要是仓储服务。仓储服务包括接口和实现:

  • 仓储接口服务供应用层或领域层服务调用

  • 仓储实现服务,完成领域对象的持久化或数据初始化

领域层

领域层实现核心业务逻辑,负责表达领域模型业务概念、业务状态和业务规则。主要的服务形态有实体方法和领域服务。

实体采用充血模型,在实体类内部实现实体相关的所有业务逻辑,实现的形式是实体类中的方法。实体是微服务的原子业务逻辑单元。在设计时我们主要考虑实体自身的属性和业务行为,实现领域模型的核心基础能力。不必过多考虑外部操作和业务流程,这样才能保证领域模型的稳定性。

业务规则和逻辑校验在领域层。

DDD提倡富领域模型,尽量将业务逻辑归属到实体对象,实在无法归属的部分则设计成领域服务。领域服务会对多个实体或实体方法进行组装和编排,实现跨多个实体的复杂核心业务逻辑。

对于严格分层架构,如果单个实体的方法需要对应用层暴露,则需要通过领域服务封装后才能暴露给应用服务。

应用层

表述应用和用户行为,负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装,负责不同聚合之间的服务和数据协调,负责微服务之间的事件发布和订阅。

通过应用服务对外暴露微服务的内部功能,这样就可以隐藏领域层核心业务逻辑的复杂性以及内部实现机制。

应用层的主要服务形态有:

  • 应用服务

  • 事件发布

  • 订阅服务

应用服务内用于组合和编排的服务,主要来源于领域服务,也可以是外部微服务的应用服务。

应用服务内还可完成

  • 安全认证

  • 权限校验

  • 初步的数据校验

  • 分布式事务控制

为了实现微服务内聚合之间的解耦,聚合之间的服务调用和数据交互应通过应用服务来完成。原则上我们应该禁止聚合之间的领域服务直接调用和聚合之间的数据表关联。

用户接口层

前端应用和微服务之间服务访问和数据交换的桥梁。处理前端发送的Restful请求和解析用户输入的配置文件等,将数据传递给应用层 或获取应用服务的数据后,进行数据组装,向前端提供数据服务。主要服务形态是Facade服务。

Facade服务分为接口和实现两个部分。完成服务定向,DO与DTO数据的转换和组装,实现前端与应用层数据的转换和交换。

两种分层架构的服务依赖关系

松散分层架构

  • 领域层的实体方法和领域服务可直接暴露给应用层和用户接口层,而无需逐级封装。

缺陷

  • 易泄露领域层核心业务逻辑

  • 当实体方法或领域服务发生变更,由于服务同时被多层服务调用和组合,难以找出哪些上层服务调用和组合了它,不方便通知到所有的服务调用方

  • 该分层架构中,实体A的方法在应用层组合后,暴露给用户接口层aFacade。abDomainService领域服务直接越过应用层,暴露给用户接口层abFacade服务。松散分层架构中任意下层服务都可以暴露给上层服务。

严格分层架构

每层服务只能向直接上层提供服务。虽然实体、实体方法和领域服务都在领域层,但实体和实体方法只能暴露给领域服务,领域服务只能暴露给应用服务。

服务如果需要跨层调用,下层服务需要在上层封装后,才可以提供跨层服务。比如实体方法需向应用服务提供服务时,需先封装成领域服务。

通过封装:

  • 可避免泄露核心业务逻辑的实现

  • 避免在应用层沉淀过多本属领域层的核心业务逻辑,防止应用层臃肿

  • 当服务发生变更时,由于服务只被紧邻上层的服务调用和组合,只需逐级告知直接上层

  • A实体的方法需封装成领域服务aDomainService才能暴露给应用服务aAppService。abDomainService领域服务组合和封装A和B实体的方法后,暴露给应用服务abAppService

数据对象视图

微服务的数据对象

  • 数据持久化对象PO(Persistent Object) 与数据库结构一一映射,是数据持久化过程中的数据载体。

  • 领域对象DO(Domain Object) 微服务运行时的实体,是核心业务的载体。

  • 数据传输对象DTO(Data Transfer Object) 用于前端与应用层或者微服务之间的数据组装和传输,是应用之间数据传输的载体。

  • 视图对象VO(View Object) 用于封装展示层指定页面或组件的数据。

微服务各层数据对象的职责和转换过程

基础层

  • 入参是DO,内部将DO转化成PO进行数据库的增删改查

  • 执行结果用PO去映射,再转化为DO作为基础层的返回值

先建立DO和PO的映射:

  • 当DO数据需持久化时,仓储服务会将DO转换为PO

  • 当DO需初始化时,仓储服务从数据库获取数据形成PO,并将PO转换为DO

大多数情况下PO和DO一一对应。但也存在DO和PO多对多,在DO和PO数据转换时,需数据重组。

比如时间范围查询时,会有辅助字段,如:beginTime和endTime,PO这怎么处理?我们的处理方式是增删改用PO,查询时候用QueryPO,QueryPO继承了PO并额外增加用于查询的辅助字段(比如时间、集合、模糊查询等)。

  • 有的查询功能,比如按照名称查询,查询条件就是name,DTO、DO和PO是一样的,也需要在每一层都去转化一下么?我们把查询时的对象命名为QueryPO,从用户接口层到基础层的入参都是这一个,这样可以么?是否要做数据转换?主要是考虑解耦,这样各层不必受其它层的数据限制,它类似齿轮,通过数据转换来做适配。如果从前端到后端数据对象都是一样的,用一个对象其实也是可以的。要结合自己的场景来分析。

一般聚合根用的少,很多情况聚合根要作为对象传到基础层。

领域层

入参是DO,返回值是DO。

DO是实体和值对象的数据和业务行为载体,承载基础的核心业务逻辑。通过DO和PO转换可完成数据持久化和初始化。

应用层

入参是DO,返回值是DO。

如果需调用其它微服务的应用服务,DO会转换为DTO,完成跨微服务的数据组装和传输。用户接口层先完成DTO到DO的转换,然后应用服务接收DO进行业务处理。如果DTO与DO是一对多的关系,就需进行DO数据重组。

用户接口层

  • 入参是DTO,内部将DTO转化为DO后调用应用层

  • 将应用层的结果转化为VO后返回给前台

完成DO和DTO的互转,完成微服务与前端应用数据交互及转换。

Facade服务会对多个DO对象进行组装,转换为DTO对象,向前端应用完成数据转换和传输。

前端应用

主要是VO。展现层使用VO进行界面展示,通过用户接口层与应用层采用DTO对象进行数据交互。

 

猜你喜欢

转载自blog.csdn.net/q66562636/article/details/125618935