分层是一种常见的根据系统中的角色/职责拆分和组织代码单元的常规实践。
在一个面向对象的程序里,UI、数据库和其它支撑代码会被写到业务对象里。额外的业务逻辑也会被嵌到 UI 控件和数据库脚本里。这是由于在短期内完成够用的代码是更简单的选择。
当领域相关的代码扩散到这样大规模的其它代码中,要发现和理解这些代码会相当困难。表面上对 UI 的修改实际上也会改变业务逻辑。要修改业务规则就得小心翼翼地追踪 UI 代码、数据库代码,或者其它的编程元素。实现内聚的、模型驱动的对象变得不切实际。自动化测试也变得尴尬。如果每个活动都要卷入所有的技术和逻辑,程序必须非常简单,要不然就完全无法理解。
——Eric Evans 2014, Domain-Driven Design Reference
◐ 分层意味着什么
在一个分层系统中,每一层:
- 依赖它之下的层;
- 和它之上的层无关,对使用(依赖)它的层次无感知。
在分层架构中,分层的使用可以严格地限制:分层只知道直接的下层,或者可以宽松一些:分层可以访问它之下的任何分层。Martin Fowler 和我自己的经验都是第二种方式实际中会更好,因为它避免了在中间分层创建代码方法(或者完整的代理类),也避免了退化成千层面的反模式(下文会详细探讨)。
有时分层会这样安排,领域层将数据源完全隐藏不让展现层看到。但是,更多的时候展现层会直接访问数据存储。这不那么纯粹,但实际却工作得更好。——Fowler 2002, Patterns of Enterprise Application Architecture
它的优势有:
- 我们只需要了解我们工作的那层之下的层次;
- 每个层次都可以用等价的实现替换,而不会影响到其它层次;
- 层次是标准化的最佳候选;
- 层次可以被多个不同的上级层次使用。
它的劣势在于:
- 分层并不能封装一切(UI 中添加的字段,很可能也要添加到数据库) ;
- 额外的分层会影响性能,尤其是位于不同物理层的时候。
◐ 20 世纪 60 年代和 70 年代
尽管上世纪 50 年代软件开发就开始了,它真正发展成我们今天所见的这样是在 60 年代和 70 年代,随着构建可以被发行、部署并可以被除了开发者自己之外其它人使用的应用的活动发展起来的。
然而,当时的应用程序和今天的应用程序截然不同。那时还没有 GUI(大概 80 年代末 90 年代初才出现),所有的应用程序要通过命令行使用,显示在一个哑终端里,它只是将用户的输入传输给应用程序,应用程序很可能就在同一台电脑中被使用。
应用程序此时还十分简单,还不需要分层,它们被部署到一台电脑之中被使用。它们实际上是单层的应用程序,尽管有时哑客户端还是远程的。尽管这些应用程序非常简单,但它们却无法伸缩,例如,如果我们需要升级软件的新版本,我们要在每台安装了该软件的电脑上升级。
◐ 20 世纪 80 年代和 90 年代的分层
在 20 世纪 80 年代,企业应用出现了,在公司里有多个用户开始使用桌面电脑通过网络访问应用。
这时它们多半分成三层:
用户界面(展现):用户界面就是网页、命令行或者原生桌面应用;
例如:作为(富)客户端的 Windows 应用,普通用户在桌面电脑上使用,和服务器器通信才能完成工作。客户端负责应用的流程和用户输入的校验;
业务逻辑(领域):应用之所以存在的逻辑;
例如:应用服务器,包含业务逻辑并从原生客户端接收请求,采取行动并将数据保存到数据存储;
数据源:数据的持久化机制(数据库),或者是和其它应用之间的通信。
例如:数据库服务器,应用服务器用它来完成数据持久化。
随着使用上下文的变迁,分层实践开始得到应用,尽管它真正得到大范围的实践是在 C/S 系统崛起的 20 世纪 90 年代。这实际上是一种 两层 应用,客户端是一个用为应用界面的富客户端应用程序,而业务逻辑和数据源放在服务器。
这种架构模式解决了伸缩性问题,因为很多用户可以独立地使用应用,只需要另外一台安装了客户端应用程序的桌面电脑就行。然而,如果我们有数百或者数十个客户端而我们想用更新应用程序的话,操作会特别复杂,因为我们要一个一个地更新客户端。
◐ 20 世纪 90 年代中期之后的分层
大约在 1995 年和 2005 年之间,随着普遍迁移到云的趋势,应用用户的增长,应用复杂性和基础设施复杂性的增加,我们终于看到了分层方案的变化。新的分层的典型实现如下:
- 原生浏览器应用程序,渲染和运行用户界面,向服务器应用发送请求;
- 应用服务器,包括了展现层、应用层、领域层和持久化层;
- 数据库服务器,应用服务器用它来完成数据的持久化。
这就是三层架构模式,也叫 N 层架构。它是可伸缩的解决方案,尽管用户界面是在客户端浏览器中渲染和运行,但由于用户界面存放于服务器上并在服务器上编译,它“解决了客户端的更新问题”。
◐ 新世纪之后的分层
2003 年, Eric Evans 出版了他的标志性著作 Domain-Driven Design: Tackling Complexity in the Heart of Software。在书中提出的许多关键概念之中,也有对软件系统分层的展望:
用户界面
负责绘制用户和应用交互的界面,并将用户输入转换成应用的命令。值得注意的是,“用户”可以是人类也可以是其它应用。它和 Ivar Jacobson 的 EBI 架构(后面其它文章会介绍更多细节)中的边界对象不谋而合;
应用层
指挥领域对象完成用户要求的任务。它不包括业务逻辑。它和 Ivar Jacobson 的 EBI 架构中的交互器对象对应,唯一不同的是 Jacobson 的交互器可以是任意和界面或实体无关的对象;
领域层
这一层包含了所有的业务逻辑、实体、事件或者其它任何包含业务逻辑的对象类型。显然它和 EBI 中的实体对象类型相对应。这是系统的心脏;
基础设施
支撑上面所有层次的技术能力,如持久化机制和消息机制。
◐ 反模式:千层面架构
千层面架构常常说的就是分层架构的反模式。以下这些情况发会出现:
- 我们决定使用严格的分层方法,也就是分层只感知得到它的直接下层。这种情况下,我们最终会创建代理方法甚至代理类,必须通过中间层次访问而不是直接访问需要的层次;
- 热衷于创建完美的系统导致项目过度抽象;
- 小更新也会波及应用的方方面面,例如,整理一个层次也会是风险巨大和收效甚微的大动作。
- 层次太多,增加了整个系统的复杂性;
- 物理层次太多,不但增加了整个系统的复杂性,还降低了系统的性能;
- 我们明确地按照层次(UI、领域、数据库)来组织我们的单体,而不是根据子域/组件(例如,产品、支付、付款)来组织它,并因此破坏了领域概念的模块化和封装。
◐ 总结
分层架构是另一种根据代码在应用中的功能角色对代码单元进行划分的方式,它带来了关注点的分离、封装性和解耦。
然而,和生活中的很多事情一样,过犹不及!所以,最重要的一条经验是:只使用必要的层次和物理层次,够用就行!我们千万不要得意忘形地追逐架构的圣杯,它根本就不存在。存在的只是需求,和最可能恰好符合它的架构。顺便说一句,这也是精益所提倡的。
此外,还有一点值得注意,上/下这种纵向的分层方式已经过时了。现代的软件开发中我们不应该使用这种方式了,应用的层次有更好的新思路。我会在接下来的文章中进行探讨。
◐ 引用来源
- 2002 – Martin Fowler – Patterns of Enterprise Application Architecture
- 2003 – Eric Evans – Domain-Driven Design: Tackling Complexity in the Heart of Software
- 2011 – Chris Ostrowski – Understanding Oracle SOA – Part 1 – Architecture