分层架构

最近在项目设计时采用了领域驱动设计,在观摩《领域驱动设计》这本书时提到了layered architecture这个单词,也就是分层体系架构,觉得自己有必要对此分层架构进行一次系统的复习,记录下来以备后用

分层架构

      • 什么是分层架构?
      • 分层架构的优劣
      • 详解四层架构
        • 表现层
          • 门面层
        • 应用层
        • 领域层
          • 什么是业务逻辑?
        • 基础设施层
        • 综合说明

什么是分层架构?

按照字面意思理解,即将程序分成多层的一种架构模式。那需要分成几层合适呢?Eric Evans建议分为四层,分别对应表现层,应用层,领域层以及基础设施层。而最基本的是分层架构是三层,即表现层,领域层和数据持久层。
分层架构_第1张图片


三层架构

名称 用途
表现层 复杂向用户显示信息和解释用户指令,用户可以是使用界面的人,也可以时另一台计算机
领域层 负责表达业务概念,业务状态信息以及业务规则,是整个系统的核心层
数据持久层 为领域层提供持久化服务,提供数据的查询和存储

四层架构

名称 用途
表现层 与三层架构中表现层一致
应用层 定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题,应用层要尽量简单,不包含业务规则,只为下一层的领域对象协调任务,分配工作
领域层 与三层架构中领域层一致
基础设施层 为上面各层提供通用化技术,为表现层绘制屏幕组件,为应用层传递消息,为领域层持久化对象

上述分层架构可以按照需要多分几层,但是分层体系架构需要遵循每层中的任何元素都仅依赖于本层的其他元素或者其下层的元素,向上通讯必须经过间接的传递机制进行。

从书上截取的一个关于网上转账的四层架构案例:
分层架构_第2张图片

分层架构的优劣

分层架构的目的是通过关注点分离来降低系统的复杂度,同时满足单一职责、高内聚、低耦合、提高可复用性和降低维护成本。

  • 单一职责:每一层只负责一个职责,职责边界清晰,如持久层只负责数据查询和存储,领域层只负责处理业务逻辑。

  • 高内聚:分层是把相同的职责放在同一个层中,所有业务逻辑内聚在领域层。这样做有什么好处呢?试想一下假如业务逻辑分散在每一层,修改功能需要去各层修改,测试业务逻辑需要测试所有层的代码,这样增加了整个软件的复杂度和测试难度。

  • 低耦合:依赖关系非常简单,上层只能依赖于下层,没有循环依赖。

  • 可复用:某项能力可以复用给多个业务流程。比如持久层提供按照还款状态查询信用卡的服务,既可以给申请信用卡做判断使用,也可以给展示未还款信用卡使用。

  • 易维护:面对变更容易修改。把所有对外接口都放在对外接口层,一旦外部依赖的接口被修改,只需要改这个层的代码即可。

以上这些既是分层的好处也是分层的原则,大家在分层时需要遵循以上原则,不恰当的分层会违背了分层架构的初衷。

分层架构的缺点

  • 开发成本高:因为多层分别承担各自的职责,增加功能需要在多个层增加代码,这样难免会增加开发成本。但是合理的能力抽象可以提高了复用性,又能降低开发成本。
  • 性能略低:业务流需要经过多层代码的处理,性能会有所消耗。
  • 可扩展性低:因为上下层之间存在耦合度,所有有些功能变化可能涉及到多层的修改。

详解四层架构

每个逻辑层之间有着明确的职责划分。

表现层

为外部用户提供了访问界面与数据展示。主要职责为接收用户指令对底层数据进行修改或展示。
详解:

  • 典型的用户是人类用户,但是也可能是别的计算机系统。例如如果ERP系统要访问我们的系统获取信息,它也是一种用户。
  • 不同类型的用户需要不同形式的用户接口,例如为人类用户提供Web界面和手机App,为ERP软件用户提供REST服务接口。
  • 不同类型的用户需要不同形式的数据表示,包括表现形式的不同(XML、JSON、HTML)和内容的不同(例如手机App中呈现的数据内容往往比Web页面中呈现的少)。
  • 用户接口层对应用层进行封装,用户接口层的操作与应用层上定义的操作通常是一一对应的关系。用户接口层从外部用户处接受输入,转换成应用层方法的参数形式,调用应用层方法将任务交由底层系统执行,并将返回结果转换成合适的形式返回给外部用户。
  • 用户界面层的典型任务是下面三个:校验——校验外部客户输入的数据是否合法;转换——将外部客户的输入转换成对底层系统的方法调用参数,以及将底层系统的调用结果转换成外部客户需要的形式;转发——将外部客户的请求转发给底层系统。

有时候,为了某些需要,我们可以从用户接口层中分离出一个亚层,可命名为门面层(Facade)。位于真正的用户接口层和应用层之间。

门面层

门面层隔离前台和后台系统,定义特定于用户接口层的数据结构,从后台获取数据内容并转化为用户接口层的数据形式。
从用户接口层中分离出专门的门面层,具有下面的优势:

  • 使得用户接口层能够独立于后台系统,与后台系统并行开发。
    用户接口层通过门面层接口与应用层和领域层解耦,意味着用户接口层可以独立开发,不必等待后台系统的完成,亦不受后台系统重构的影响,在需求调研阶段系统原型出来并得到用户确认之后,就可以开始用户接口层的开发了。可以根据界面原型定义用户接口层需要的数据结构,该数据结构与底层数据结构解耦,不需要知道底层数据类型和数据之间的关联关系。将底层数据和界面数据连接起来并相互转换是门面层实现类的职责,这方面工作可以等待前后台系统分别完成之后进行。
  • 使得分布式部署成为可能。
    如果没有门面层的隔离,用户接口层只能直接使用领域层的领域对象作为自己的数据展现结构。这样我们就不能将系统进行分布式部署,将用户接口层和后台系统(领域层、应用层等)分别部署到不同的服务器上。因为在JPA和Hibernate等技术实现中,领域实体绑定到当前服务器的持久化上下文中,必须脱管之后才能够跨越JVM进行传输。更大的问题是事务问题,事务要跨越服务器的边界,复杂性增加,性能严重下降。门面层的存在使得实体和事务都限制在后台系统,不需要扩展到前台服务器。
  • 避免Hibernate中“会话已关闭”的问题,消除成本巨大的“Open Session in View”模式的需要。
    在采用JPA或Hibernate作为持久化手段的系统中存在臭名昭著的“会话已关闭”问题,对付这一问题的主要手段是Open Session in View这一存在潜在性能问题的方案。如果不采用门面层隔离后台数据结构,在前端展现数据需要访问实体的延迟初始化属性时就会遇到“会话已关闭”问题,而采用Open Session in View模式处理这个问题就意味着事务不是在后端完成而是扩展到前端用户接口层,在大访问量的网站上会遭遇严重的性能问题并降低吞吐量。采用门面模式的话,有关联关系的数据在后台拼装完毕再一次性返回给前端,事务局限在后端范围,不再有“会话已关闭”和性能问题。

门面层说明:

  • 门面层特定于用户接口层,由用户接口层定义和控制(包括操作和数据的形式和内容),这意味着需要为不同类型的用户接口层开发专门的门面层。
  • 查询结果通常以数据传输对象(DTO)的形式表示。DTO的结构由用户接口层而不是后端决定,代表前端需要的数据形式,与底层数据结构脱耦。
  • 通过门面层实现类访问后端的应用层。实现类将后端数据拼装为DTO并返回给前端,它可以将数据装配职责委托给专门的Assembler工具类去执行。
  • 在分布式系统中,可以在前端和后端分别部署门面层。前后端的门面层接口相同,但后端的门面层实现类负责数据装配和发布,前端的门面层实现类负责通过某种通信机制(Web Service等)与后端门面层通讯,获取后者装配好的数据。传输过程中DTO可能序列化为JSON或XML等形式。

应用层

应用层定义系统的业务功能,并指挥领域层中的领域对象实现这些功能。
应用层是整个系统的功能外观,封装了领域层的复杂性并隐藏了其内部实现机制。

  • 应用层映射到系统用例模型,是系统用例模型在软件中的反映。
  • 应用层接口描述了系统的全部功能,意味着系统用例模型中的所有用例都可以在应用层接口中找到对应的方法。
  • 应用层实现类不实现业务逻辑,它通过排列组合领域层的领域对象来实现用例,它的职责可表示为“编排和转发”,即将它要实现的功能委托给一个或多个领域对象来实现,它本身只负责安排工作顺序和拼装操作结果。

领域层

领域层实现业务逻辑。

什么是业务逻辑?

什么是业务逻辑?业务逻辑就是存在于问题域即业务领域中的实体、概念、规则和策略等,与具体的实现技术无关,主要包含下面的内容:

  • 业务实体(领域对象)。例如银行储蓄领域中的账户、信用卡等等业务实体。
  • 业务规则。例如借记卡取款数额不得超过账户余额,信用卡支付不得超过授信金额,转账时转出账户余额减少的数量等于转入账户余额增加的数量,取款、存款和转账必须留下记录,等等。
  • 业务策略。例如机票预订的超订策略(卖出的票的数量稍微超过航班座位的数量,以防有些旅客临时取消登机导致座位空置)等。
  • 完整性约束。例如账户的账号不得为空,借记卡余额不得为负数等等。本质上,完整性约束是业务规则的一部分。
  • 业务流程。例如,“在线订购”是一个业务流程,它包括“用户登录-选择商品-结算-下订单-付款-确认收货”这一系列流程。

对领域层的进一步说明如下:

  • 领域层映射到领域模型,是问题域的领域模型在软件中的反映。
  • 包含实体、值对象和领域服务等领域对象,通常这些领域对象和问题域中的概念实体一一对应,具有相同或相似的属性和行为。
  • 在实体、值对象和领域服务等领域对象的方法中封装实现业务规则和保证完整性约束(这一点是与CRUD模式相比最明显的差别,CRUD中的领域对象没有行为)。
  • 领域对象在实现业务逻辑上具备坚不可摧的完整性,意味着不管外界代码如何操作,都不可能创建不合法的领域对象(例如没有账户号码或余额为负数的借记卡对象),亦不可能打破任何业务规则(例如在多次转账之后,钱凭空丢失或凭空产生)。
  • 领域对象的功能是高度内聚的,具有单一的职责,任何不涉及业务逻辑的复杂的组合操作都不在领域层而在应用层中实现。
  • 领域层中的全部领域对象的总和在功能上是完备的,意味着系统的所有行为都可以由领域层中的领域对象组合实现。

基础设施层

基础设施层为其余各层提供技术支持。
基础设施层是系统中的技术密集部分。它为领域层、应用层的业务服务(例如持久化、消息通信等等)提供具体的技术支持,用户接口层通常使用特定的表示层框架(例如SpringMVC、Struts或Tapestry)实现,但有需要时也可以申请技术设施层提供专门的技术支持。

一些例子:

  • 领域层需要持久化服务,在DDD中,领域层通过仓储(Repository)接口定义持久化需求,基础设施层通过采用JDBC、JPA、Hibernate、NoSQL等技术之一实现领域层的仓储接口,为领域层提供持久化服务。
  • 领域层需要消息通知服务,在领域层中定义了一个NotificationService领域服务接口,基础设施层通过采用手机短信、电子邮件、Jabber等技术实现NotificationService领域服务接口,为领域层提供消息通知服务。
  • 用户接口层需要一个对象序列化服务,将任何JavaBean序列化为JSON字符串,可以在用户接口层定义一个ObjectSerializer服务接口,基础设施层通过采用Gson实现这一接口,为用户接口层提供对象序列化服务。

以上例子都是满足依赖倒置原则,通过控制反转的方式为高层模块提供低层服务,在实践中,可以通过Spring等IoC容器将基础设施层的实现类实例进行依赖注入。

基础设施层的典型实现形式是提供一个一个的类,这些类使用某些专有的技术实现其余各层(主要是领域层)定义的接口,例如提供一个领域层的仓储接口的实现类,使用Hibernate实现持久化,以及提供领域层的通知接口的实现类,使用ActiveMQ广播领域层中发生的事件,等等。

基础设施层也被称为数据源层或数据访问层。这些名称的一个缺点是给读者一个强烈的暗示:基础设施层只负责数据库访问。虽然数据库访问是基础设施层的职责之一,但基础设施层的负责范围比单纯数据库访问宽广的多,它实现了系统的全部技术性需求,例如上面例子中的通知服务和对象序列化服务,等等。

综合说明

  • 在四层架构中,领域层和应用层纯粹表达业务意图和机制,不包含任何技术逻辑;而基础设施层和用户接口层纯粹提供技术实现,不包含任何业务逻辑。在业务和技术之间存在清晰的关注点分离。
  • 应用层定义系统的全部业务功能,领域层具体实现这些功能。领域层“动于内”,应用层“形诸外”。
  • 应用层和领域层合在一起代表了整个业务系统,具备概念上的完整性(包含了全部领域概念,实现了全部的业务行为),但不具备实现上的完整性(没有基础设施层的技术支持,系统不具备可运行性;没有用户接口层支持,系统不具备可访问性)。
  • 所有业务逻辑都在领域层实现,业务逻辑泄漏到应用层是一个错误,泄露到基础设施层或用户接口层是严重错误(在用户接口层中实现业务逻辑是采用CRUD模式的常犯的典型错误)。
  • 领域层在履行职责的过程中如果需要技术支持,则在领域层中定义一个表达业务意图的领域服务接口,交由基础设施层采用各种具体技术去实现这一接口。保证领域层(和应用层)不被各种具体技术污染是逻辑分层的第一要务。
  • 判断业务层(领域层和应用层)是否被具体技术污染一个方便的方式是检查它们是否有对具体技术框架(例如Spring和Hibernate)的编译时依赖。业务层代码应该只依赖于JDK(java.)、Java规范(javax.),以及一些被广泛使用的类库如commons-lang、Guava、SLF4J、JodaTime等,这些类库本质上可视为对JDK的补充,不是一种具体技术框架。
  • 应用层和门面层的区别:应用层属于后端,门面层属于前端。应用层方法的参数和返回值可以包含领域对象,门面层方法的参数通常是字符串和数字等简单值,返回值是简单值或DTO。以转账操作为例子,应用层中的方法签名是这样的:void transferFund(Account from, Account to, Money amount, Date transferTime),门面层中的方法签名是这样的:void transferFund(String fromAccountNumber, String toAccountNumber, BigDecimal amount, String currency, Date transferTime)。在门面层的实现类中,负责根据账户号码从仓储中获取Account对象,将amount和currency拼装成Money对象,然后以这些对象和transferTime为参数访问应用层中的相应方法。
  • 领域层中的领域对象具有领域通用性或行业通用性,意味着可以在基本相同的领域层上建立不同的应用层(就像三极管、二极管、电容、电阻等在电子工业领域具有通用性,可以用来组装收音机、录音机、电视机等不同应用),应用层是应用特定或客户特定的,只为特定的应用或客户定制。相比应用层,领域层对象具备高度的可重用性。例如一套完备的用户管理领域层模块可以被OA、ERP、CRM、HRM、MES等多个应用重用。因为领域对象中封装了业务逻辑,这种重用是非常有价值的。
  • 可以基于相同的应用层建立不同的用户接口层,例如Web页面,手机App、BI报表、RESTful Web Service等等。

参考:https://www.cnblogs.com/legend886/articles/6889811.html

你可能感兴趣的:(架构,领域驱动设计,分层架构,三层架构,四层架构)