DDD-分层架构

前言

DDD更重要的是战略设计,DDD分层架构只是实现DDD的一种方式,但不限这一种,如六边形架构、整洁架构等。

六边形架构、整洁架构、DDD分层架构,虽然表现形式不同,但是设计思想都是相同的,都是以领域模型为中心,都体现了高内聚低耦合原则。

下面对比六边形架构、整洁架构、DDD分层架构三种架构模型,以及DDD分层架构的代码结构、命名规则。

六边形架构

img

六边形架构是Alistair Cockburn在2005年提出的,它把系统分为内部和外部。内部代表业务逻辑,外部代表业务逻辑的入口调用和底层依赖。

不同形态的外部要顺利访问到内部之前,或者内部需要依赖外部之前都需要经过必要的适配实现。因此六边形架构又名端口适配器架构。

因为端口可能有多个,比如入口侧有用户界面、API、命令行、消息流、甚至是系统定时任务脚本等方式访问内部业务逻辑,出口侧可能会依赖数据库、缓存系统、中间件系统、外部应用系统等等,因此架构图就是一个多边形形状,只是六个端口比较形象因而得名为六边形架构。

各层职能

六边形架构将系统分为内六边形和外六边形两层,这两层的职能划分如下:

  • 红圈内的六边形实现应用的核心业务逻辑。
  • 外六边形完成外部应用、驱动和基础资源等的交互和访问,对前端应用以API主动适配的方式提供服务,对基础资源以依赖倒置被动适配的方式实现资源访问。

优点

  • 对分层做了进一步明确,隔离了内部的业务逻辑和外部的主动或被动的依赖。很好地体现了DDD的思想,分开了业务和技术实现。带来很多好处,比如可测试性高,外部可替代性高。

  • 解决了业务逻辑不应该依赖基础设施层的问题,实现了依赖倒置,即外部各适配器依赖内部的端口,适配器是对端口的实现。

整洁架构

img

整洁架构又名“洋葱架构”。从外部到内部每层都用圆圈表示,因为架构图很像洋葱而得名。

洋葱架构是2008年Jeffrey Palermo结合六边形架构和DDD提出的,在洋葱架构里,同心圆代表应用软件的不同部分,从里到外依次是领域模型、领域服务、应用服务和最外围的容易变化的内容,比如用户界面和基础设施。

整洁架构最主要的原则是依赖原则,它定义了各层的依赖关系,越往里依赖越低,代码级别越高,越是核心能力。外圆代码依赖只能指向内圆,内圆不需要知道外圆的任何情况。

各层职能

在洋葱架构中,各层的职能是这样划分的:

  • 领域模型实现领域内核心业务逻辑,它封装了企业级的业务规则。领域模型的主体是实体,一个实体可以是一个带方法的对象,也可以是一个数据结构和方法集合。
  • 领域服务实现涉及多个实体的复杂业务逻辑。
  • 应用服务实现与用户操作相关的服务组合与编排,它包含了应用特有的业务流程规则,封装和实现了系统所有用例。
  • 最外层主要提供适配的能力,适配能力分为主动适配和被动适配。主动适配主要实现外部用户、网页、批处理和自动化测试等对内层业务逻辑访问适配。被动适配主要是实现核心业务逻辑对基础资源访问的适配,比如数据库、缓存、文件系统和消息中间件等。
  • 红圈内的领域模型、领域服务和应用服务一起组成软件核心业务能力。

DDD分层架构

四层架构

imgimage.png

从上到下依次是:用户接口层、应用层、领域层和基础设施层。

四层架构是限定型松散分层架构,即Infrastructure层的任意上层都可以访问该层(“L”型),而其它层遵守严格分层架构

各层职能

  • 用户接口层:

    用户接口层负责向用户显示信息和解释用户指令。这里的用户可能是:用户、程序、自动化测试和批处理脚本等等。

  • 应用层:

    应用层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。但应用层又位于领域层之上,因为领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。

    此外,应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。

  • 领域层:

    领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。

    领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。

  • 基础设施层:

    基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。

    基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。

三层架构与四层架构对应

传统三层架构,如何演进到四层架构,可以参考下图对应关系:

img

三层架构向DDD分层架构演进,主要发生在业务逻辑层和数据访问层。

DDD分层架构对三层架构的业务逻辑层进行了更清晰的划分,改善了三层架构核心业务逻辑混乱,代码改动相互影响大的情况。DDD分层架构将业务逻辑层的服务拆分到了应用层和领域层。应用层快速响应前端的变化,领域层实现领域模型的能力。

另外一个重要的变化发生在数据访问层和基础层之间。三层架构数据访问采用DAO方式;DDD分层架构的数据库等基础资源访问,采用了仓储(Repository)设计模式,通过依赖倒置实现各层对基础资源的解耦。

仓储又分为两部分:仓储接口和仓储实现。仓储接口放在领域层中,仓储实现放在基础层。原来三层架构通用的第三方工具包、驱动、Common、Utility、Config等通用的公共的资源类统一放到了基础层。

五层架构

五层架构是基于四层架构和DCI架构扩展而来的,先了解一下DCI架构

DCI

James O. Coplien和Trygve Reenskaug在2009年发表了一篇论文《DCI架构:面向对象编程的新构想》,标志着DCI架构模式的诞生。

DCI是数据Data 场景Context 交互Interactions的简称,DCI是一种特别关注行为的模式(可以对应GoF行为模式),而MVC模式是一种结构性模式,MVC模式由于结构化,而可能忽视了行为事件。

DCI目前广泛被看作是对DDD的一种发展和补充,用在基于面向对象的领域建模上。

DCI 包括三层架构:

  • Data层:

    述系统有哪些领域概念及其之间的关系,该层专注于领域对象的确立和这些对象的生命周期管理及关系,让程序员站在对象的角度思考系统,从而让“系统是什么”更容易被理解。

  • Context层:

    是尽可能薄的一层。Context往往被实现得无状态,只是找到合适的role,让role交互起来完成业务逻辑即可。但是简单并不代表不重要,显示化context层正是为人去理解软件业务流程提供切入点和主线。

  • Interactive层:

    主要体现在对role的建模,role是每个context中复杂的业务逻辑的真正执行者,体现“系统做什么”。role所做的是对行为进行建模,它联接了context和领域对象。由于系统的行为是复杂且多变的,role使得系统将稳定的领域模型层和多变的系统行为层进行了分离,由role专注于对系统行为进行建模。该层往往关注于系统的可扩展性,更加贴近于软件工程实践,在面向对象中更多的是以类的视角进行思考设计。

引入DCI后,DDD四层架构模式中的Domain层变薄了,以前Domain层对应DCI中的三层,而现在:

  1. Domain层只保留了DCI中的Data层和Interaction层,我们在实践中通常将这两层使用目录隔离,即通过两个目录object和role来分离层Data和Interaction
  2. DCI中的Context层从Domain层上移变成Context层

因此,DDD分层架构模式就变成了五层,如下:

img

各层职能

  • 用户接口层:

    主要用于处理用户发送的Restful请求和解析用户输入的配置文件等,并将信息传递给Application层的接口。

  • 应用层:

    负责多进程管理及调度、多线程管理及调度、多协程调度和维护业务实例的状态模型。当调度层收到用户接口层的请求后,委托Context层与本次业务相关的上下文进行处理。

  • 环境层:

    以上下文为单位,将Domain层的领域对象cast成合适的role,让role交互起来完成业务逻辑。

  • 领域层:

    定义领域模型,不仅包括领域对象及其之间关系的建模,还包括对象的角色role的显式建模。

  • 基础实施层:

    为其他层提供通用的技术能力:业务平台,编程框架,持久化机制,消息机制,第三方库的封装,通用算法,等等。

六层架构

在面向控制面或管理面且消息交互比较多的系统中,五层架构可以变体为六层架构,如下:

img

各层职能

  • 用户接口层:

    主要用于处理用户发送的Restful请求和解析用户输入的配置文件等,并将信息传递给Scheduler层的接口。

  • 调度层:

    负责多进程管理及调度、多线程管理及调度、多协程调度和维护业务实例的状态模型。当调度层收到用户接口层的请求后,委托Transaction层与本次操作相关的事务进行处理。

  • 事务层:

    对应一个业务流程,比如UE Attach,将多个同步消息或异步消息的处理序列组合成一个事务,而且在大多场景下,都有选择结构。万一事务执行失败,则立即进行回滚。当事务层收到调度层的请求后,委托Context层的Action进行处理,常常还伴随使用Context层的Specification(谓词)进行Action的选择。

  • 环境层:

    以Action为单位,处理一条同步消息或异步消息,将Domain层的领域对象cast成合适的role,让role交互起来完成业务逻辑。环境层通常也包括Specification的实现,即通过Domain层的知识去完成一个条件判断。

  • 领域层:

    定义领域模型,不仅包括领域对象及其之间关系的建模,还包括对象的角色role的显式建模。

  • 基础实施层:

    为其他层提供通用的技术能力:业务平台,编程框架,持久化机制,消息机制,第三方库的封装,通用算法,等等。

三种架构对比分析

虽然DDD分层架构、整洁架构、六边形架构的架构模型表现形式不一样,但这三种架构模型的核心思想是一致的,都是以领域模型为中心的设计思想,都完美体现了高内聚低耦合的原则。

img

关注图中的红色线框,它们是非常重要的分界线,这三种架构里面都有,它的作用就是将核心业务逻辑与外部应用、基础资源进行隔离。

红色框内部主要实现核心业务逻辑,但核心业务逻辑也是有差异的,有的业务逻辑属于领域模型的能力,有的则属于面向用户的用例和流程编排能力。按照这种功能的差异,我们在这三种架构中划分了应用层和领域层,来承担不同的业务逻辑。

领域层实现面向领域模型,实现领域模型的核心业务逻辑,属于原子模型,它需要保持领域模型和业务逻辑的稳定,对外提供稳定的细粒度的领域服务,所以它处于架构的核心位置。

应用层实现面向用户操作相关的用例和流程,对外提供粗粒度的API服务。它就像一个齿轮一样进行前台应用和领域层的适配,接收前台需求,随时做出响应和调整,尽量避免将前台需求传导到领域层。应用层作为配速齿轮则位于前台应用和领域层之间。

DDD分层架构代码结构

来自极客时间中的DDD实战课:https://time.geekbang.org/column/article/165248

按最常用的四层架构,目录结构如下:

  1. 用户接口层

    img

    Assembler:实现DTO与领域对象之间的相互转换和数据交换。一般来说Assembler与DTO总是一同出现。

    Dto:它是数据传输的载体,内部不存在任何业务逻辑,我们可以通过DTO把内部的领域对象与外界隔离。

    Facade:提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。

  2. 应用层

    img

    Event(事件):这层目录主要存放事件相关的代码。它包括两个子目录:publish 和 subscribe。前者主要存放事件发布相关代码,后者主要存放事件订阅相关代码(事件处理相关的核心业务逻辑在领域层实现)。

    虽然应用层和领域层都可以进行事件的发布和处理,但为了实现事件的统一管理,建议将微服务内所有事件的发布和订阅的处理都统一放到应用层,事件相关的核心业务逻辑实现放在领域层。通过应用层调用领域层服务,来实现完整的事件发布和订阅处理流程。

    Service(应用服务):这层的服务是应用服务。应用服务会对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,是一段独立的业务逻辑。

  3. 领域层

    img

    Aggregate(聚合):它是聚合软件包的根目录,可以根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。

    Entity(实体):它存放聚合根、实体、值对象以及工厂模式(Factory)相关代码。实体类采用充血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。

    Event(事件):它存放事件实体以及与事件活动相关的业务逻辑代码。

    Service(领域服务):它存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服务设计为一个类。如果领域服务内的业务逻辑相对复杂,我建议你将一个领域服务设计为一个领域服务类,避免由于所有领域服务代码都放在一个领域服务类中,而出现代码臃肿的问题。领域服务封装多个实体或方法后向上层提供应用服务调用。

    Repository(仓储):它存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,我们设定了一个原则:一个聚合对应一个仓储。

  4. 基础设施层

    img

    Config:主要存放配置相关代码。

    Util:主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码,你可以为不同的资源类别建立不同的子目录。

还可以参考下面项目:

DDD分层架构命名规范

可以参考下面命名规范,来做到见名之意

img

参考

分享到: