分层架构的演进与DDD的分层

分层架构是运用广泛的架构模式。从经典的三层架构到Eric Evans在其经典著作《领域驱动设计》中提出的四层架构,再到Vaughn Vernon在《实现领域驱动设计》对Eric的四层架构的改良,以及Alistair Cockburn的六边形架构、Robert C. Martin的整洁架构,分层架构随着软件行业的发展,也在不断的迭代演进。本文就其演进历程给出自己的理解与思考,并对经典的四层分层架构中各层的职责及其关键模型进行说明。

认识分层架构

认识分层架构

依据wikipedia的定义,是一组可在相同环境复用的组件。分层架构是将展现应用处理数据管理功能物理分离的client-server架构。

相同环境意味着每一层的组件处于同一抽象层次,关注同一类问题,承担同一类的职责,拥有相同的变化率。复用是目的,分层是手段,将复杂的问题拆解成展现应用处理数据管理功能等一个个子问题。

分层架构是分治原则和关注点分离原则的体现。

分层架构的演进

经典三层架构

经典三层架构

  1. 最初系统功能比较简单,可能一个类就承担了展示、业务逻辑和数据访问的职责;
  2. 随着系统功能增加,原有的方式会出现重复代码,违背了DRY的原则,我们采用extract class的方式将可以复用的功能提取为单独的类
  3. 功能激增,我们继续extract class,然后我们发现一部分都是和存取数据相关的,一部分都是和业务逻辑相关的,一部分都是和展示相关的。我们按职责分类,就出现了经典三层架构。

底层可以供上层复用,每一层关注一类功能,拥有相似的变化率(关注点分离)。复杂的问题得以分解为展示、业务逻辑、数据管理三个子问题(分而治之)。

分层架构的演进

四层架构演进

  1. 服务化促使数据访问层演进为基础设施层:继续演进,我们的系统逐渐演进成了大泥球,我们开始做服务拆分,将原来的大单体,按照功能相关性拆分成一个个的微服务。额外引入RPC、MQ等通信方式,之前的数据访问层除了处理DB的访问,还处理RPC通信、MQ的通信、cache的集成、config集成等复杂技术问题,演进成为基础设施层(Eric Evans)
  2. 内,以业务为中心;外,提供完整用例:拆分业务逻辑层为领域层和应用层,领域层是系统的核心所在,保留业务对象(实体、值对象、聚合、领域服务等)的状态,维护业务规则;应用层不保留业务对象的状态,依据最小知识原则,通过协调领域层的业务对象提供外部视角的完整用例,起门面的作用。
  3. 依赖倒置,技术依赖业务,依赖稳定的抽象:“高层模块不应依赖于底层模块,二者都应该依赖于抽象”,减少变化对系统产生的影响,抽象的接口标准更多由更靠近业务的模块来进行定义,比如基础设施层与领域层,领域层来定义接口,基础设施层来进行实现。

注:分层是水平维度的拆分,服务化按照业务功能拆分是垂直维度的拆分,都是分而治之原则的体现;分层是依据技术和业务相关职责做分离,服务化是依据业务功能相关性做分离,都是关注点分离原则的体现

六边形架构&整洁架构

  1. 上下层次->内外层次:将依赖倒置的四层架构推到然后叠加,原有的上下层次变成了内外层次。类似的内外层次的架构包括Alistair Cockburn的六边形架构及Robert C. Martin的整洁架构

六边形架构&整洁架构-normal

DDD分层架构的关键模型

四层架构关键模型

用户界面层(user interface)、应用层(application)、领域层(domain)、基础设施层(infrastructure)

  • 在四层架构中,领域层和应用层纯粹表达业务意图和机制,不包含任何技术逻辑;而基础设施层和用户接口层纯粹提供技术实现,不包含任何业务逻辑。在业务和技术之间存在清晰的关注点分离
  • 应用层和领域层合在一起代表了整个业务系统,具备概念上的完整性(包含了全部领域概念,实现了全部的业务行为),但不具备实现上的完整性(没有基础设施层的技术支持,系统不具备可运行性;没有用户接口层支持,系统不具备可访问性)
  • 所有业务逻辑都在领域层实现,业务逻辑泄漏到应用层是一个错误,泄露到基础设施层或用户接口层是严重错误
  • 判断业务层(领域层和应用层)是否被具体技术污染一个方便的方式是检查它们是否有对具体技术框架(例如Spring和Hibernate)的编译时依赖。业务层代码应该只依赖于JDK(java.)、Java规范(javax.),以及一些被广泛使用的类库如commons-lang、Guava、SLF4J、JodaTime等,这些类库本质上可视为对JDK的补充,不是一种具体技术框架。
  • 领域层在履行职责的过程中如果需要技术支持,则在领域层中定义一个表达业务意图的领域服务接口,交由基础设施层采用各种具体技术去实现这一接口。保证领域层(和应用层)不被各种具体技术污染是逻辑分层的第一要务。
  • 应用层和门面层的区别:应用层属于后端,门面层属于前端。应用层方法的参数和返回值可以包含领域对象,门面层方法的参数通常是字符串和数字等简单值,返回值是简单值或DTO

用户界面层

  • 概述: 在底层系统之上封装了一层可访问外壳,为特定类型的外部用户(人或计算机程序)访问底层系统提供访问入口,并将底层系统的状态数据以该类型客户需要的形式呈现给它们。
  • 职责:
    • 接收命令操作,改变底层系统状态
    • 接收查询操作,将底层系统状态以合适的形式呈现给用户
    • 校验、转换、转发
      • 校验:校验外部客户输入的数据是否合法
      • 转换:将外部客户的输入转换成对底层系统的方法调用参数,以及将底层系统的调用结果转换成外部客户需要的形式
      • 转发:将外部客户的请求转发给底层系统
    • 有时我们会把转换工作分离出一个亚层-门面层(Facade)
      • 查询结果通常以数据传输对象(DTO)的形式表示,由用户界面层而不是应用层界定,代表前端需要的数据形式。
      • 将数据装配职责委托给专门的Assembler工具类去执行
  • 特点:
    • 不同类型的用户需要不同形式的用户接口
    • 不同类型的用户需要不同形式的数据表示
    • 用户接口层对应用层进行封装,用户接口层的操作与应用层上定义的操作通常是一一对应的关系。

应用层

  • 概述:整个系统的外观、封装了领域层的复杂性,并隐藏了其内部实现机制;映射到系统用例模型,是系统用例模型在软件中的反映
  • 特点:
    1. 编排和转发:不实现业务逻辑,通过排列组合领域层的领域对象来实现用例
    2. 系统用例模型中的所有用例都可以在应用层接口中找到对应的方法

领域层

  • 概述:领域层实现业务逻辑,映射到领域模型,是问题域的领域模型在软件中的反映
  • 模型:实体、值对象、领域服务,通常这些领域对象和问题域中的概念实体一一对应,具有相同或相似的属性和行为
  • 特点:
    1. 业务规则:在实体、值对象和领域服务等领域对象的方法中封装实现业务规则,有数据有行为。
    2. 完整性约束:领域对象在实现业务逻辑上具备坚不可摧的完整性,意味着不管外界代码如何操作,都不可能创建不合法的领域对象。
    3. 内聚性:任何不涉及业务逻辑的复杂的组合操作都不在领域层而在应用层中实现
    4. 完备性:系统的所有行为都可以由领域层中的领域对象组合实现
    5. 通过仓储(respository)接口定义持久化需求,基础设施层通过采用jdbc、jpa、hibernate、nosql等实现领域层的仓储接口

基础设施层

  • 概述: 为领域层、应用层、用户界面层提供具体的技术支持(如持久化、消息通信)
  • 特点:
    • 提供系统的全部技术性需求。
  • 一些例子:
    • 领域层需要持久化服务,在DDD中,领域层通过仓储(Repository)接口定义持久化需求,基础设施层通过采用jdbc、jpa、hibernate、nosql等实现领域层的仓储接口
    • 领域层需要消息通知服务,在领域层定义了一个NotificationService领域服务接口,基础设施层通过采用手机短信、电子邮件、Jabber等技术实现NotificationService领域服务接口,为领域层提供消息通知服务。
    • 用户界面层需要一个对象序列化服务,可以在用户界面层定义一个ObjectSerializer服务接口,基础设施层通过Gson实现着一接口,为用户界面层提供对象序列化服务