译注

这是我们技术分享的内容。

译文在结构上并没有完全忠于原文。主要有两点改变:把本来在全文末尾的优缺点对比放到了每一章节的末尾;在章节中会穿插一些翻译官的碎碎念。

专业的翻译讲究“信达雅”,科技文章的翻译以“信”为先。我算不上专业的翻译官,只能尽量保证译文可“信”。但我算是专业的程序员,因而总会想要结合自己的经验来表“达”。至于“雅”么……总不能用“子所雅言”来翻译吧!保证语句通顺就好了吧!哈哈。

以下正文。

10种常用的软件架构模式概述

Vijini Mallawaarachchi
原文:10 Common Software Architectural Patterns in a nutshell

你有没有想过,我们要怎样设计一个大型的企业级系统?在主要的软件开发工作开始之前,我们必须选择一个合适的架构:一个能满足我们所需的功能和质量要求的架构。因此,在把这些架构应用在我们的设计中之前,我们应该先理解不同的架构。

软件架构模式

什么是架构模式

由维基百科我们可以知道:“架构模式是一套通用的、可重用的解决方案,用于解决给定上下文中软件架构常见的一些问题。架构模式与软件设计模式有相似之处,但是其内涵和外延更加广泛。”

在这篇文章里,我会简要地介绍以下十种常用架构模式的使用场景、优点和缺点。

译注:这里的这些架构模式……其实讲得不够深入,很多东西也没讲到点子上。尤其是使用场景方面——简直是敷衍。】

  • 分层模式
  • 客户端-服务端模式
  • 主从模式
  • 管道-过滤器模式
  • 代理模式(Broker模式)
  • 点对点模式
  • 事件总线模式
  • 模型-视图-控制器模式(MVC模式)
  • 黑板模式
  • 解释器模式

十种常见架构模式

分层模式

我们可以用分层模式来构造可分解为子任务组的程序:这些子任务组中的每一个子任务都是在一个特定层级上的抽象。每一层都向它的上一层提供服务。

分层模式

在很多信息系统中,我们都可以看到这样四个层次:

  • 表现层(也叫UI层)
  • 应用层(也叫服务层)
  • 业务逻辑层(也叫领域层)
  • 数据访问层(也叫持久化层)

译注:领域层和服务层的关系是怎样的?我一直认为领域层在服务层之上——我们可能需要多个资源服务才能搭建起一个领域模型。

使用场景

  • 一般的桌面应用
  • 电子商务网络应用

译注:其实,所有问题都可以分层。分层是我们解决问题的天性——父母和子女,老师和课代表,上级和下级,这就是我们人类社会中的分层。分层既可以减少本层的复杂度,也允许每一层保持灵活。虽然分层会带来额外的工作量——基本上使用任何一种模式都是这样——但我真心建议为每一个问题都划分好层级,把不同的事情交给不同层级的人(模块)去做。

优点

  • 底层可以被不同的高层调用。
  • “层”可以简化标准化工作,因为我们可以很清楚地定义每一层。
  • 每一层的变化都不会波及其它层。

译注:我认为“每一层的变化都不会波及其它层”是分层模式的核心。在TCP/IP模型中,顶层使用的是HTTP协议还是SMTP协议,与TCP层无关。在Controller-Service-Dao的层次模型中,Controller层返回的是JSON串还是HTML页面,与Service层无关;Service层使用自研服务、还是调用第三方服务,与上下两层也无关;Dao层是接入MySQL还是Oracle,与Service层同样无关。只有保证了这一点,分层模式才能最大程度的发挥其结构化、解耦合的功能。

缺点

  • 不是普遍适用的。
  • 某些场景下,我们可能必须跳过/忽略某些层。

译注:比如TCP/IP分层模型中的socket,比如把一个处理单条数据的服务改造成批量处理的服务。大多数时候,使用模式/模型会给我们后续工作带来很大的便利。但确实有少数时候,我们需要打破原有的模式/模型。

客户端-服务端模式

客户端-服务端模式包含两个部分:一个服务端和多个客户端。一个服务端可以向多个客户端提供服务。客户端向服务端发起请求,而服务端则向客户端发回对应的相应。此外,服务端会持续性的监听客户端发送的请求。

客户端-服务端模式

使用场景

  • 在线应用,例如电子邮件、文件共享、在线银行等。

译注:当然,还有我们的移动端APP。与客户端-服务端模式(CS模式)类似的,还有浏览器-服务端模式(BS模式)。BS模式下,浏览器使用系统资源会受到很大限制;并且通常浏览器请求都是无状态的;而且一般完全不支持离线应用。CS模式没有这些问题。但是,BS模式非常易于扩展和升级,CS模式在这一点上则要麻烦得多。

优点

  • 适用于构建一组可接受客户端请求的服务。

译注:这有点像是废话。我认为,CS模式的优点在于客户端是分散于“本地”的,因而可以提供相当强大的功能。

缺点

  • 一般情况下,服务端会用彼此独立的线程来处理客户端发送来的请求。

  • 由于不同的客户端有不同的展示逻辑,客户端-服务端之间的进程间通信会带来额外的开销。

译注:客户端有不同的展示逻辑,和进程通信的额外开销之间并没有必然联系吧。客户端的不同展示逻辑,可能带来的问题是服务端需要兼容多版本/多客户端。至于进程间通信,对分布/分体系统来说,这是必然的代价。

主从模式

主从模式包含两个部分:主节点和从节点。主节点把任务分发给几个完全一样的从节点;并且根据从节点返回的结果计算出一个最终结果。

译注:这与我们常说的“主从模式”很不一样。我们常说的主从模式一般是在主/从节点之间同步数据,以保证服务可用性的一种模式。这篇文章里所说的“主从模式”其实更像MapReduce或者ElasticSearch。

主从模式

使用场景

  • 在数据库复制场景中,主数据库作为可信源使用,从数据库从主数据库中同步数据。

译注:然而,主数据库并不需要根据从数据库返回的数据来计算出一个最终结果,除非这里说的是分库分表场景下的分页查询。

  • 在计算机系统中,外围设备(从驱动)连接到总线(主驱动)上。

译注:我们通常理解的主从,主要用来保证服务高可用,而且是为有状态服务保证高可用。如果是无状态服务,简单地增加服务节点就可以了——这也是为什么我们一定要保证服务无状态性的一个原因。对有状态服务,例如数据库、缓存等。

优点

  • 准确性——一个服务可以被委托给不同的从节点上;而不同的从节点可以有不同的实现。

译注:然而在前面的说明中却说:主节点把任务分发给几个“完全一样”的从节点。不知道这说的到底是什么意思。

缺点

  • 从节点之间是相互独立的,彼此之间没有共享状态。
  • 在实时系统中,主-从节点之间的通信延迟也是一个问题。
  • 这个模式只适用于可以进一步拆分的问题中。

管道-过滤器模式

管道-过滤器模式可用于构建需要提供或者处理一个数据流的系统。每一个处理步骤都被封装在一个过滤器组件中。需要处理的数据通过管道传递。我们也可以为了缓存或同步的目的使用管道。

译注:管道的思想应该是来自于Unix/Linux操作系统,就是我们常用的“|”。软件系统中常见的责任链/装饰者模式也是异曲同工。这是我非常喜欢的一种模式,它可以把一个复杂的业务流程简化为一系列具有相同外观的简单服务。服务越简单就越容易复用。

管道过滤器模式

使用场景

  • 编译器。使用连续的过滤器来处理语法分析、解析、语义分析和代码生成等功能。
  • 生物信息学的工作流。

译注:其实不一定是生物信息学,大部分工作流都可以用这种模式来实现。就像责任链——当流程中的大部分操作都基于最初的输入,并且彼此之间非常独立时,就可以用这种方式来处理。

优点

  • 表现为并发处理。当输入和输出由流组成时,过滤器在接收到数据时就可以开始计算。

译注:这里的并发,应该是指多个管道之间可以并发处理。例如CPU的流水线处理逻辑。但是在同一个管道内,一般来说,各个过滤器应该是同步处理的:上一个过滤器处理完毕,下一个才能处理。

  • 易于增加过滤器,整个系统易于扩展。

  • 过滤器可复用。可以通过重组同一套过滤器,来构建不同的管道。【碎碎念:构建复杂逻辑/系统有两种思路。一种是组件复杂、结构简单;另一种是组件简单、结构复杂。管道-过滤器属于第二种。】

缺点

  • 整个管道的处理性能由其中最慢的一个过滤器决定。

  • 过滤器之间传递数据时,数据转换会造成额外的开销。

代理模式

代理模式可用于构建由解耦组件组成的分布式系统。这些组件可以通过远程服务调用来相互交互。一个代理组件负责协调这些组件之间的通信。

服务器把它具备的能力(服务和角色)发布给代理。客户端向代理发起请求,代理从它的注册表中找到一个合适的服务,并将客户端的请求重定向到这个服务上。

代理模式

使用场景

  • 各种消息代理软件,例如Apache ActiveMQ, Apache Kafka, RabbitMQ 和JBoss Messaging。

译注:这里的例子和描述有点令人费解。消息队列并不是把客户端发送的请求“重定向”到对应的服务上。这个操作更像是Proxy,而非Broker。无论是Proxy还是Broker,都可以用来解耦客户端和服务端。如果有这个需要,或者说,如果客户端和服务端没有必要强耦合,那就可以用这种模式来构建系统。

优点

  • 可以动态的改变、增加、删除或者重定向一些对象。并且,Broker使得开发者不用关注分布式了。

译注:原文是分布式对开发者变透明了。个人理解是开发者不必关心分布式服务的相关信息,只需要关注Broker既可以了。

缺点

  • 要求标准化的服务描述。

译注:应该是与Broker的通信必须标准化;但是各服务不必标准化。

点到点模式

在点到点模式中,各个独立的组件被称作“点”。每个点都可以作为客户端,以向其它点发送请求;也可以作为服务店,以向其它点提供服务。一个点既可以是服务端、也可以是客户端、还可以同时是服务端和客户端,甚至可以实时地、动态的切换自己的角色。

译注:这就是所谓的P2P模式……

点到点模式

使用场景

  • 文件共享网络,例如Gnutella和G2。
  • 多媒体协议,例如P2PTV和PDTP。

优点

  • 支持去中心化计算。

译注:点到点的最大优势就在于去中心化。

  • 健壮性高,任何节点出现故障都不影响大局。

  • 资源和算力上具有很高的扩展性。

缺点

  • 不保证服务质量,因为节点是自愿加入合作的。

  • 安全性很难保证。

  • 性能依赖于节点数量。

事件总线模式

事件总线模式主要用来处理“事件”。它包含四个主要组件:事件源,事件监听器,信道,和事件总线。事件源把消息发布到事件总线上的某个特定信道上;监听器订阅特定信道,并且在有消息被发布到这个信道上时会接收到消息通知。

译注:其实消息队列更符合这种模式。而前面所说的代理模式(Broker),其实用Proxy来表述会更合适些。

事件总线模式

使用场景

  • 安卓开发
  • 通知服务

译注:这里的事件总线与前面的代理很相似。

优点

  • 可以很简便的添加发布者、订阅者和连接。
  • 对高度分布式应用来说,效率很高。

缺点

  • 扩展性是个潜在问题,因为所有的消息都通过同一个总线来传输。

译注:这一点很像曾经的企业服务总线。

模型-视图-控制器模式

这个模式就是著名的MVC模式。它把一个交互式应用拆分为以下三部分:

  • 模型——包含核心功能和数据
  • 视图——向用户展示信息(可以定义多个视图)
  • 控制——处理用户输入

这样,信息的内部展现方式、向用户展现的方式、从用户获取信息的方式就被拆开来了。各个组件也就借此完成了解耦,代码也就可以高效地复用了。

译注:MVC和Controller-Service-Dao不是一回事。但是解耦合的思路是一致的——其实各种模式的思路都是解耦合。

MVC模式

使用场景

  • 使用主流编程语言来搭建www应用。
  • web框架,例如Django和Rails。

译注:虽然现在MVC都用在WEB上,但是别忘了,它肇始于C/S系统。所以,MVC并不局限于WEB应用。文章后面有评论说,MVC模式常用于展示信息,这说到点子上了。之所以需要从Model中拆分出一个View来,就是为了展示信息。所以简单说,需要对外——不一定是人,可能是其它系统——展示信息时,我们就可以运用MVC模式来解耦展示信息和模型数据。

优点

可以很轻松地为一个模型创建多个视图,这些视图可以在运行时连接和断开。

译注:大概是说,可以在运行时确定使用哪个视图吧。

缺点

增加了复杂度。用户动作可能导致很多不必要的更新。

译注:应该是指用户行为改变时,系统要做出的修改会比较多。最常见的情况就是,为了在页面上多现实一个数据,我们需要从前端页面到后台服务、底层数据库做一连串的变更。

黑板模式

当不知道确定的解决方案时,黑板模式就很有用了。黑板模式包含三个主要组件:

  • 黑板——一个结构化的全局存储,其中包含有来自解决方案空间的对象。

译注:解决方案空间可能比较费解。考虑下问题域/值域,或者问题空间/解决方案空间,可能就比较好理解了。

  • 知识源——特定的模块,以及它们自己的展示方式。
  • 控制组件——选择、配置和执行模块。

所有的组件都接入到黑板中。组件可以产生新的数据,并把它们加入到黑板中。组件也会通过与现有的知识源进行模式匹配,来从黑板中查找特定类型的数据。

黑板模式

使用场景

  • 语音识别
  • 车辆识别和追踪
  • 蛋白质结构识别
  • 声呐信号解析

译注:我们可以想想一个这样的查询接口。它要求传入一个复杂对象。如果对象中包含有id,则按id查询数据库;否则,如果包含有用户id,则按用户id查询;否则,如果包含有×××号,则按×××号查询;否则……这种模式,是否也算是一种黑板模式呢?

优点

  • 易于增加新的应用。
  • 易于扩展数据空间的结构。

缺点

  • 很难修改数据空间的结构,因为所有的应用都会受到影响。
  • 可能需要同步和访问控制。

解释器模式

解释器模式用来设计翻译用指定语言写成的程序的组件。它主要指定了如何评估程序行,如用特定语言携程的语句和表达式。其基本思路是为编程语言中的每一个符号都定义一个类。

译注:还有表达式解析等。

解释器模式

使用场景

  • 数据库查询语言,例如SQL。
  • 用来描述通信协议的语言。

优点

  • 可以提供高度动态的行为。
  • 为终端用户的编程提供便利。
  • 灵活性很高,因为可以很轻松的替换被解释的语言。

缺点

  • 性能可能是个问题,因为一般来说,解释语言比编译语言要慢。

一些讨论

“这些模式并不是全都在同一个抽象层次上发挥作用的、也不是全都用来解决同样的问题的。虽然文章开篇说‘选择一个合适的架构’,但我建议不要把这些架构模式和实际使用的架构混为一谈。在一个大型企业级项目所使用的架构常常会包含多种模式。还是很感谢提供这篇综述。”

译注:这是大实话。无论是这里提到的架构模式、还是软件设计模式,基本上都会组合成更为复杂的复合模式来使用。

“MVC模式与分层模式有显著的不同。MVC模式的各个组件之间都可以互相通信。而分层模式只允许相邻的两层之间传递信息。MVC架构最常用于展示信息,而分层架构则专注于整个系统。”

译注:分层模式的限制确实更严格。除了只允许相邻两层通信外,一般还只允许高层依赖底层、而不允许底层依赖高层。