【存档】software-architecture-patter
软件架构模式
理解通用架构模式及使用场景
马克 理查德
简介
软件工程师不经过设计正式架构而构建应用程序是十分寻常的。
大部分工程师在没有经过清晰和明确的架构设计的情况下,都会选择传统的标准分层架构模式, 也称作'n-tier'架构,
自然地通过拆分源代码模块成包而创建分层。不幸的是,此类架构实践的结果是一堆没用经过设计的源代码模块集合,缺乏清晰角色定位,互相之间没用联系。
这也是通常指的大泥巴球架构反模式。
缺乏正式架构设计的程序通常高耦合、脆弱、很难扩展,并且没有清晰远景或者发展方向。
结果是,很难定义决定程序架构属性,因为无法完全理解系统中内在模块和组件的交互关系。
甚至基础的部署和开发问题都很难解答:
程序是否可扩展?
程序如何轻易响应变化?
程序性能如何?
程序如何部署?
程序响应度如何?
架构模式帮助我们定义程序基础特点和行为。例如: 一些架构模式本身扩展性很高, 因此采用这些模式的应用十分敏捷。
认识每种架构模式的特点、优点及缺点对于选择符合特定业务需求和目标的模式十分必要。
作为一名架构师,你应当不断验证自己的架构决策, 特别是需要选择特定架构模式或方法的时候。
本书的目标便是提供给你足够知识以制定和验证决策。
分层架构
最常见的架构模式便是分层架构, 也称作(n-tier)架构模式。
这也是大部分架构师、设计者和软件工程师熟知的,大部分Java EE应用的标准模式。
分层架构模式十分符合大部分公司的传统IT通信和组织结构中,也是大部分业务应用开发的自然选择。
模式描述
分层架构模式中的组件以水平方式组织, 每层扮演应用中的不同角色(如:展示逻辑或业务逻辑)。
尽管架构本身没有指定分层数量和类型,大部分分层架构模式包含四种标准分层:
展示层、业务层、持久层、数据库层。
某一场景中,业务层和持久层合并成独立业务层, 特别是当持久逻辑如: SQL,HSQL嵌入在业务层组件中时。
小型应用中可能只有三层,大型和更复杂业务应用有五层或者更多层。
应用中每层有特定角色和职责。如: 展示层会负责用户界面和浏览通信逻辑,业务层负责执行特定业务规则关联的请求。
每层会形成满足特定业务请求的抽象。如: 展示层不需要知道知道或担心如何获取客户数据,只需要在显示器中展示特定格式的信息。
类似地,业务层不需要关心如何格式客户数据或者如何获取用户数据, 只需要从持久层获取数据,对数据执行业务逻辑,如: 计算值或汇总值,然后
传递数据至展示层。
分层架构最强大的特性是做到了组件间关注度分离。特定分层中的组件只处理该分层逻辑。
如: 展示层组件只处理展示逻辑, 业务层组件只处理业务逻辑。此种组件分类很容易在架构中建立有效角色和职责模型,
由于良好组件接口设计和有限组件范围,使得很容易开发、测试、管理应用。
关键概念
如图1-2架构中各分层已经关闭。这是分层架构中非常重要的概念。
关闭分层意味着由于请求在层与层之间传递,请求必须自上而下通过上一层到达下一层。
如: 展示层的请求必须首先经过业务层、持久层才能最终到达数据库层。
为什么不让展示层直接访问持久层或数据库层呢?
总之,直接访问数据库层比经过一堆无关的分层去获取或保存数据要快得多。
这个问题的答案是被称作分层隔离的关键概念。 分层隔离意思是单层变化不会影响其他层的组件。
变化被隔离在该层组件和可能的关联层(如:持久层包含SQL)。如果你让展示层直接访问持久层,那么持久层中的SQL变化将会影响业务层和展示层,
将会产生有着许多关联的组件的高耦合应用。此类架构将会提高变化难度和成本。
分层隔离同样意味着层与层直接的独立,各层无需其他层的内部交互关系。为了理解这个概念的强大和重要性,想下将展示框架从JSP转换至JSF的大型重构需求。
假设展示层和业务层的约定(如: 数据模型)仍保持一致,重构不会影响业务层,业务层对于展示层使用何种用户界面框架完全独立。
关闭分的层帮助分层隔离,有利于隔离架构内部变化。合适的时机可以开放某些层。如: 假设你添加共享服务层到包含有业务层访问的通用服务组件的架构中。
(如: 数据和字符串工具类或者审计和日志类)。此类场景中创建新的服务层总会是一个好主意,因为架构上限制共享服务和业务层的访问,而不是展示层。
如果没有新增独立分层,架构上没有限制展示层直接访问通用服务,使得很难管理访问权限限制。
此例中,新服务层将会放在业务层的下方显示展示层不能直接访问新服务层的组件。但是,这里引入一个问题,业务层必须访问服务层才能到达持久层,这里没有意义。
这是分层架构模式中的老问题,可以通过创建开放分层解决。
如图1-3所绘, 服务层被标记为开放,意味着请求可以绕开服务层直接访问下一层。下面的例子中,只有服务层开放,业务层允许绕开服务层,直接访问持久层,这样就很清楚。
扩充开发和关闭分层概念定义了架构分层与请求流之间的关系,让设计者和开发者理解不同层的访问限制。
如果不记录和分享架构中分层开放情况将会产生高耦合和脆弱的架构,最终难以测试、维护和部署。
模式示例
图1-4中演示业务用户如何获取用户信息。
黑色箭头展示流向数据库获取用户数据的请求,红色箭头回传至屏幕显示数据的响应。
此例中,用户信息包含用户数据和排序数据。
用户屏幕负责接收请求和显示用户信息。它并不知道数据源、获取方式或查询数据的表数量。只要用户屏幕接收单个用户的用户信息请求,将会转交请求至用户代理模块。
此模块负责查找能够处理此请求的业务层模块,解释该模块调用方式以及模块所需数据模型(契约)。 业务层中客户对象负责汇总业务请求所需的所有信息,这里是获取
客户信息。此模块调用持久层中的客户数据访问对象(DAO)模块访问客户数据,还有访问排序DAO模块获取排序信息。持久层对应模块执行SQL语句获取相应数据,传递至
业务层的客户对象。一旦业务层客户对象接收到数据,它会汇总数据同时传递至用户代理,继而传递给用户屏幕显示客户信息。
从JAVA技术角度看,这些模块有很多种实现方式。例如: JAVA平台中,客户屏幕可以是一个JSF关联着用户代理作为受控BEAN组件。业务层的客户对象是一个本地SPRING BEAN
或远程EJB3 BEAN. 图中绘制的数据访问对象可以用简单的POJO, MYBATIS XML映射文件,甚至是封装的纯JDBC调用或HIBERNATE调用实现。
从微软平台看,用户屏幕可以是ASP模块,使用.NET framework访问业务层的C#模块,客户数据和排序数据访问模块用ADO实现。
注意事项
分层架构模式是一种固定的通用模式,可以用于大部分应用良好的开始,特别是你不确定何种模式适用你的应用的情况下。
但是这里有些选择此模式时应该注意事项。
第一点要注意的是著名的架构阴沟反模式。此反模式描述一种场景,请求流向不同的分层,但是各分层只是简单传递处理,很少或没有逻辑处理。
例如: 假如展示层响应用户请求获取客户数据。展示层传递请求至业务层,业务层简单传递给持久层,持久层执行数据库简单SQL调用获取用户数据。
数据只是简单传递,无须额外处理,没有汇总计算逻辑,也没有数据转换。
每个分层架构总会有些场景会掉入架构阴沟反模式。关键是,此类请求的百分比。可以遵循80-20原则来决定你是否掉入阴沟反模式。典型的有20%的简单数据传递请求,80%的业务逻辑关联请求。
但是你发现百分比反了,大部分请求只是简单数据传递,你应当考虑开放某些分层,记住如果没有分层隔离,架构将会变得更难控制。
另一点是应用分层架构应用模式本身会变成大一统应用,即使你拆分展示层和业务层至独立部署单元。但就某些程序这可能无须顾虑。
分层架构在部署、通用自动化、可用性、性能和可扩展性会有点潜在问题。
模式分析
下表包含分层架构的通用架构特性评级和分析。每个特性评价基于作为众所周知的模式典型应用能力的自然趋势。关于和其他架构模式对比的全面对比,可以参考本书末尾的附录A。
总体灵活度:
评级: 低
分析: 总体能够快速响应持续变化的环境,尽管变化可以隔离至独立分层,因为大部分实现大一统的本性以及高耦合的组件依赖,改变该模式依然稍显笨重和耗时。
部署简单度:
评级: 低
分析: 依赖具体模式实现,部署会成为问题,特别是大型应用程序。 一个微小的变化需要整个系统或大部分模块重新部署, 导致需要在下班或周末规划、排期、执行部署。
本身而言,此模式不能轻易用于持续部署过程,导致部署评级下降。
可测试度:
评级: 高
分析:由于组件分属不同分层,其他分层可以模拟或移除,使得此模式相对容易测试。开发者可以模拟展示组件或屏幕组件来隔离业务组件测试,也可以模拟业务层来测试展示组件功能。
性能
评级: 低
分析:虽然一些分层架构性能也可以,由于完成业务请求分层之间不得不低效数据传递,此模式本身不能成为高性能应用。
可扩展
评级: 低
分析:由于此模式大一统实现和高耦合的特性趋势,采用此模式构建的应用一般很难扩展。 你可以分割分层进行独立物理部署或复制整个应用至不同节点来扩展整个分层应用。
但整体粒度太广,扩展成本很高。
开发简单
评级: 高
分析:开发简单得到相对高分,因为此模式很知名,实现并不复杂。因为大部分公司通过分层分割技术团队来开发应用(展示,业务,数据库),此模式成为大部分业务应用开发的自然选择。
公司的沟通和组织架构联系和软件开发方式可总结为康威定律。你可以谷歌康威定律找到更多有趣信息。
事件驱动架构
事件驱动架构模式是用来构建高可扩展应用的一种流行分布式异步架构模式。
该模式同样高可适配,可用于小型、大型复杂应用。事件驱动模式主要由可以异步接收和处理事件的高解耦,单一目的事件处理组件。
事件驱动架构模式由两个拓扑结构组成,中介者和代理人。
中介者通常用于通过中央中介在单一事件中组织多个步骤,代理者用于无须中央中介来串联事件。
由于这两种拓扑结构架构特点和实施策略不同,理解并选择合适场景的架构十分重要。
中介者拓扑
中介者适用于多步事件及多层事件。例如,处理股票交易事件需要首先验证交易,使用多种验证规则进行校验,分配交易给代理人,计算佣金,最后提交给代理人。所有交易步骤都需要基于层级的组织和决策执行顺序,明确串行和并行事件。
中介者拓扑由四个主要架构组件构成:事件队列、事件中介者、事件通道、事件处理器。客户端向事件队列发送事件,事件队列传送至事件中介者,事件中介接收初始事件通过向事件通道发送额外异步事件并执行过程中步骤,事件处理器监听事件通道,接收事件中介的事件,处理事件中的业务逻辑。
图2-1展示基于事件驱动架构模式的总体中介者拓扑
事件驱动架构中通常存在一系列的事件队列。此模式不是指事件队列组件的实施,可以是消息队列、web服务节点或其他任意组合。
中介模式有两种类型事件: 初始事件和处理事件。 初始事件是中介接收的原始事件,而处理事件由中介者生成,事件处理组件接收。
事件中介组件负责组织初始事件内置步骤。针对初始事件的每个步骤,中介者向事件通道发送特殊处理事件,最终事件处理接收并处理这些事件。要注意事件中介并不直接处理业务逻辑,而是组织初始事件中的内置步骤。
事件中介通过事件通道向事件处理器中发送和初始事件中步骤相关的特殊处理事件。事件通道可以是消息队列】消息主题,消息主题在中介拓扑中广泛使用,因此多个事件处理器可以处理同一处理事件,基于接收的处理事件执行不同任务。
事件处理组件包含执行处理事件的业务逻辑,事件处理器单独封装,独立,高度解耦,仅在应用系统中执行特定任务。事件处理可以细化到处理,类似:计算销售税的排序,也可以执行保险申报这样的大型任务,一定要记住事件处理组件只执行单一业务任务,不依赖其他组件。
事件中介可以采用不同方式实现。作为架构师,你应该理解每种实现,确保事件中介满足需求和要求。
最简单通用的事件中介实现就是采用开源集成服务如Spring Integra‐ tion, Apache Camel, or Mule ESB。开源集成服务事件流一般由java代码或者DSL实现。对于更复杂的中介和组织,可以使用BPEL(业务流程执行语言),一般封装一个BPEL引擎如Apache ODE。BPEL是一种类XML语言,描述处理初始事件的数据和步骤。对于大型应用,需要更复杂的组织,包括人机交互步骤,可以BPM(业务处理管理器)如jBPM,来实现事件中介。
理解需求并匹配正确事件中介实现对于成功实现任何事件驱动架构至关重要。采用开源集成服务执行复杂业务逻辑无疑会失败,正如用BPM来实现简单路由逻辑。
为了描绘中介拓扑实现原理,假设你购买了保险公司保险想换保。此例中,初始事件可以叫做重置事件。图2-2中显示处理重置事件的步骤。每个初始事件步骤,事件中介创建处理事件如 改变地址,重算报价,想事件通道发送处理事件,等待对应事件处理器如 客户进程、报价进程处理。这一过程将一直进行直到所有初始事件全部处理完毕。重新计算报价和更新报价步骤长条显示这些步骤可以同时进行。
代理人拓扑
与中介模式不同,代理人模式没有统一的事件中介。消息流通过轻量级消息消息代理如 ACTIVEMQ、HORNETMQ,分布至不同的事件处理组件。这种模式适合相对简单的事件处理流程,不想或不需要事件中介。
代理模式有两种主要类型架构组件,代理组件和事件处理组件。代理组件可以集中或分布管理,包含事件流中的所有用到的事件通道。
代理组件中的事件通道可以是消息队列、消息主题,两者集合。
图2-3显示代理模式。图中可以看出,没有集中事件中介组件控制和组织初始事件,事件处理器负责处理事件并发布事件处理结果通知。如,股票平仓事件处理组件可以接受股票拆分初始组件。该处理组件可以执行股票重算,向事件代理发布重算股票新事件,其他事件处理组件也可以接收这条事件。记住可能有时候发布事件没有事件处理接收。当你升级或扩展应用这种情况很常见。
我们使用同样的例子演示代理人工作原理,由于代理模式没有统一事件中介,用户进程组件直接接收初始事件,改变客户地址,发送一条改变客户地址事件。此例中,有两个事件处理接收这个事件,报价进程和发布进程。 报价处理组件接收地址更新事件,重新计算新的保险费比例,并向系统其它组件发布新事件。发布处理组件,接收相同事件,向系统发布更新通知事件,事件持续向系统传递,但已经新的事件发布。
图2-4展示执行业务功能的事件链。理解代理模式最好的方式是把它想象成接力赛跑。运动员拿着接力棒,跑一段距离,交给下一个人,直到最后一个人跑完全程。只要运动员交接接力棒,她的任务就完成。代理模式也是一样的,事件处理处理完事件,再也不会处理该事件。
注意事项
由于事件驱动模式异步和分布特效,实施起来相对复杂。实现该模式,必须处理很多分布架构问题,像远程进程可用性,缺乏响应,代理重置和中介宕机。
选用事件模式要注意的是单一业务进程缺乏原子事务。事件处理组件高度解耦,分布执行,很难维持组件之间的事务性。设计系统时,必须持续思考耦合事件,规划事件组件的粒度。如果发现拆分单一工作单元,发现会使用独立处理来执行
特定事务,那可能事件模式不适用应用。
也许事件驱动模式最难的是创建、维护、管理事件处理器协议。每个事件包含特定协议,如数据值和数据格式。制定标准数据格式,xml、json,java对象等等和协议版本策略至关重要。
模式分析
下表包含分层架构的通用架构特性评级和分析。每个特性评价基于作为众所周知的模式典型应用能力的自然趋势。关于和其他架构模式对比的全面对比,可以参考本书末尾的附录A。
总体灵活度:
评级: 高
分析: 总体能够快速响应持续变化环境。因为事件处理组件执行单一逻辑,完全解耦。事件处理内部变化可以快速处理而不影响其他组件。
部署简单度:
评级: 高
分析: 由于事件处理组件高度解耦特性,该模式相对容易部署。代理模式比中介模式更容易部署,中介组件和事件处理组件存在耦合。事件处理变化同样影响中介变化,需要二者一起部署。
可测试度:
评级: 低
分析:独立单元测试不难,需要定制工具生成事件,由于异步特性,测试很复杂。
性能
评级: 高
分析:由于消息架构的存在,可能实现事件驱动模式不会很顺利。由于异步特性,事件驱动模式十分高效。解耦和并行异步操作比排队和出队消息成本更高。
可扩展
评级: 高
分析:高度独立和解耦的事件处理组件设计可以实现自然扩展。每个事件处理可以独立扩展,允许深度扩展。
开发简单
评级: 低
分析:由于异步特性和契约建立,开发会比较复杂。事件处理和实效代理内部代码需要做更多高级容错处理。
P26