简介: 这一期的 演化架构和紧急设计 将会解决演化架构相关的各种主题,包括设计和架构之间的重要区别(以及如何区分两者),您在创建企业级架构时遇到的某些问题,以及面向服务的架构中静态类型和动态类型的区别。
[size=1.5em]分辨架构和设计 Martin Fowler 对架构的定义(来自和他的对话中)是我认为最好的: 架构就是完成之后很难更改的东西。所以这种东西应该尽可能越少越好。 您可以想象一下架构和设计之间的交互,如图 1 中所示的关系:
图 1. 架构和设计的关系
一个软件系统的架构形成是所有其他部分存在的基础,如 图 1 中的灰盒所示。设计部分存在于架构之上,如红盒所示。处于更基础的地位,架构部分难以移动和替换是因为您不得不移动所有以架构为基础的部分来适应改变。这一定义使识别设计和架构更为简单。例如,您所使用的 Web 框架就是一个架构,因为它难以替换。尽管,在那个 Web 框架中您使用不同的设计模式来表述特定的目标,这就表示大部分的正式设计模式是设计,而不是架构的一部分。 Fowler 所定义的架构的推论是:您应该灵活地构造架构部分,以便能够更轻松地替换它们(如果真的需要的话)。但是如何才能确保这点呢?这里有个例子。 许多框架都会试图诱导您使用其自身的类,而不是 JDK 或者一个开放标准机构(例如 OASIS)提供的更普遍的类。这就是耦合的 “毒贩模式”:如果您服从这些诱导,您就只能永远受制于框架。这些框架采取的普遍方法就是,如果您使用了它们的类,某方面就会变得异常简单。这方面的完美例子就来自于 Apache Struts Web 框架(见 参考资料)。 在您的应用程序中包含业务规则和其他非基础设施代码的类是域类:它们包含着您的问题领域相关的有趣信息。Sturts 中的一个好助手类就是 ActionForm 类。如果您从 ActionForm 继承了您的域对象, 您的应用程序就会变得更方便。您可以从参数完成自动表格填充、自动验证(Web 和服务器层),以及其他便利。您所要做的就只是把 Struts ActionForm 类作为子集,如图 2 所示:
图 2. 使用 Struts ActionForm 类
在 图 2 中,标签为 Model 的盒子包含了您的域对象。它扩展了 Struts 的 ActionForm,使得这一结构此后难以改变。如果以后您决定ScheduleItem 也需要在一个 Swing 应用程序中运行,那就很难办了。您只剩下两个难以接受的解决方案:将所有的 Struts 拖拽到 Swing 应用程序中(且不使用它)或者摆脱对 Struts 的依赖。 较好的替代方案就是采用组合而不是继承,如图 3 所示:
图 3. 通过组合来对您的域类解耦合
在此版本中,域类(黄色部分)包含了一个定义日程项目语义的界面。原始的 ScheduleItem 将实现这个界面,它还可以由ScheduleItemForm 来实现,使得这两个类的语义总是保持一致。反过来,ScheduleItemForm 拥有 ScheduleItem 域对象的一个实例,ScheduleItemForm 的所有读值器和写值器传递到封装的 ScheduleItem 的底层读值器和写值器。这就允许您利用 Struts 的良好特性,同时摆脱该框架的束缚。 经验法则是:可以使框架对您有所了解,而您不可以对框架有所了解。只要您可以维持那种关系,您就能避免把自己的代码耦合到基础设施中去,这使您能够更轻易地改变架构和设计。有时可能要多花点功夫来完成这个任务,但是您可以拥有更好的灵活性。Struts 并不是唯一向您提供这种诱惑的框架。几乎所有的框架都包含将您限制在框架中的帮助工具。如果您在域类中导入来自某个框架或者厂商的数据包,那您以后就有得头疼了。
[size=1.5em]关于架构的考虑 除了架构的定义,典型的企业设置中还出现了各种广泛的问题。我将在这里介绍针对其中一些问题的敏捷架构解决方法。 [size=1.2em]架构的政治 当您被提升到架构师职位时,公司政治将是您所要遇到的众多难题之一。因为架构师 基本上是公司中最高的技术职位,您会成为 IT 部门内发生的所有决策的发言人(和辩护人),无论好坏。事实上,您还常常要因为失败受到责备,却不会因为成功而赢得信任。一些新上任的架构师试图对这些置之不理(当您在技术职位时这也许非常有效),但是在您的新职位这明显行不通。 请您记住在许多软件项目中,沟通比技术更为重要。如果您曾经在某个软件项目上失败过,那么请您思考一下失败的原因:是出于某个 技术 原因,还是某些 沟通 问题?大部分时间,失败是因为沟通而不是技术。技术问题有其解决方案。(有时它们很难解决,但总归有解决方案。)但社会问题就更加复杂和棘手了。 Peopleware(见 参考资料)这本书中有这样一句名言: 总是存在人的问题。 即使是您认为应该按部就班,直截了当的技术决策,也会有政治参杂其中,特别是您处于决定是否批准购买某企业工具的职位。(从乐观的角度看,您可能有机会由某个工具厂家掏腰包打次异国情调的高尔夫。)请记得,作为一名架构师,您不仅需要做出重要的决策,您还必须为这些决策辩护。有时和您交谈的人有他们自己的议事日程,这些内容或许在逻辑上行不通,但是在企业政治的考验面前却行得通。不要气馁,您要记清楚最初之所以作出这个决策的原因。 [size=1.2em]构建与购买 大公司中常出现的普遍问题之一就是决定是构建还是购买:针对现在的需求,我们是应该购买 COTS(Commercial Off-the-Shelf Software)还是自己构建?要做出此决策的动机是可以理解的 — 如果公司可以找到一些完全符合自身需要的现成软件,这样就节约了时间和金钱。不幸的是,许多软件厂商理解这一需求,所以编写可以定制的打包软件,如果软件不能完全符合客户的需要的话。他们意在尽力构建最通用的软件,因为这样能适用更多的生态系统。但是越是通用,就越需要定制。所以有时即使很多顾问在,也需要花费很多年才能完成所有的定制代码。 是否应该购买 COTS 的问题实际上归结为另一个问题:业务流程是由软件在战略上 还是经费上 支持?如果业务流程仅仅是经费问题,购买 COTS 就合情合理。这类软件例子包括人力资源、财务、以及其他普通的业务流程。战略 软件在您的业务领域给您竞争优势,这个竞争优势不能轻易放弃。 避免陷阱[size=0.76em]请记住:并不是所有的业务流程都是可定制的,它们根据业务不同而千差万别。不要轻信那些声称已经编写了您要的业务流程的厂商。如果他们真的拥有这样的流程,他们肯定也在把这些流程卖给您的竞争对手。
图 4 所示的流程图用于帮助您决定是构建还是购买:
图 4. 决策是构建还是购买的流程图
在这个流程图中,您要做出的第一个决策就是战略和经费的重要区别。如果需求是战略性的,您往往需要自己构建解决方案。如果不这么做,您就会将自己置于一个和对手公平竞争的环境中,而不是构建完全符合您现在和将来需求的软件。打包软件吹嘘其可定制性,但还是有对定制程度的限制。如果您自己编写,会花费较长的时间,但是您有了一个平台,在这个平台上您可以构建将您和对手区分开的软件。 流程图中的第二个决策就是询问数据包软件是否能立刻起作用。在购买数据包软件时常见的一个陷阱就是错误估计其适应您的业务流程所需的准确时间;大部分公司都把这个时间错估了一个数量级。您所需的定制越多,所耗费的时间就越长。更糟糕的是,一些公司还允许改变他们的业务流程来适应软件。这是一个错误,因为无论好坏,您的业务流程都应和对手的有所区别。 这个决策树中的第三步就是询问数据包是否可扩展,这和定制性 刚好相反。可扩展的系统由经过良好定义的方法来扩展功能,而无需一切事先就绪。这些扩展点包括经过良好定义的 APIs、SOAP 调用等等。定制意味着您要通过 “欺骗” 来让数据包完成您的工作。例如,如果您试图打开一个 WAR 文件,那么您可以用一个不同的图像(必须用 index.gif 来命名)来替换用 index.gif 命名的文件,您是进行定制而不是扩展。最终检验标准是您的更改是否能够通过升级。如果是,您就扩展了数据包;如果不是,您就定制了数据包。定制不鼓励您不断升级数据包,因为您会意识到对新版本做出相同的改变需要付出多少努力。那么,趋势就是不进行更新,落后于最新版四、五个版本,这将使您面临失去对现在正在使用的老版本的支持的危险。 是经费问题还是战略问题因公司而异。例如,我曾为一家财务服务公司做过顾问,它的招聘过程被认为是其关键战略优势之一。他们雇佣最好、最聪明的人,花费大量的时间和精力来寻找适合的人。他们曾就购买 COTS 人力资源系统咨询过我的意见,我建议他们不要那样做:为什么要让自己置身于一个和对手公平竞争的环境呢?最后,他们采纳了我的建议,编写自己的 HR 系统。编写花费了较长的时间,但一旦完成,他们就有了一个平台,能够完成对其对手来说更劳动密集型的任务。招聘对许多组织来说是简单的经费问题,但对这家公司来说却是战略问题。
[size=1.5em]架构中的类型控制 SOA 计划中经常出现的一个更技术化(更不面向流程)的主题往往和分布式系统中的类型控制和版本控制有关。这就是这类项目中常见的陷阱之一。它之所以常见,不仅因为人们很容易遵循工具厂商铺好的路,还因为问题需要一段时间才能凸显出来 — 最严重的问题产生于您不了解在项目早期应该知道的东西。 关于能否用动态类型语言构建 “企业” 系统的争论已经有了定论,这个结论现在也不能给予什么启示。然而,这一争论意味着就端点的类型控制而言,对分布式系统有了重要的考虑。所谓端点,指的是两个完全不同的系统之间的通信门户。两个相互竞争的类型控制样式是 SOAP 和 Representational State Transfer (REST),前者通常采用诸如 Web Services Description Language (WSDL)这样的标准来创建一个强类型,而后者适用于类型更宽松的、以文档为中心的方法(见 参考资料)。SOAP 与 REST 的详细优缺点不在本文的讨论范围之内;在此我主要想说的是端点层面上宽松类型的好处,这些好处可以使用任一样式实现。 更动态的类型控制在端点处是很重要的,因为这些端点会在以不同速度演变的系统之间形成一个已发布的集成 API。您想在那些系统之间避免严格耦合的特定签名(类型和参数名),因为那样会使通信的双方都很脆弱、容易崩溃,削弱了您分别对两个应用程序进行版本升级的能力。 这里有个例子。在传统的 SOAP 式集成中,使用的协议类型是 Remote Procedure Call (RPC),并用 WSDL 来定义两个应用程序间通话的详细信息,如图 5 所示:
图 5. 在应用程序间使用 RPC 式调用
RPC 式集成使用 WSDL 来进行一个 “常规” 方法调用,并将其抽象出来发送到 SOAP。这样,每个类都映射到 WSDL 中的一个类型,包括其所有参数的类型。这种方法将通信双方强烈耦合到一起,因为它们都依赖 WSDL 来定义发送的内容和预期接收的内容。问题源于这种严格的定义。如果您需要修改其中一个应用程序来采用不同的参数或者改变现有的类型,且不能同时更改这两个应用程序,那又该怎么办呢?该如何对端点进行版本控制?有几个方法是可行的,但所有这些方法都有严重的妥协之处。例如,您可以用新的 WSDL 定义创建另外一个端点。如果原始端点命名为 addOrder,那么您可以创建另一个端点,命名为 addOrder2。您会看到这种方法前景不妙。不久,您就会有数十个稍有不同、到处包含重复代码的端点来处理一次性情况,因为一旦发布,就很难预测人们会怎么应用这些集成点。您也可以使用 Universal Description, Discovery, and Integration (UDDI)(或者仅仅是哈希图)这样的工具来欺骗端点,但那并不会有很好的效果。根本问题就是端点间的严格耦合,那会阻止其按自然、独立的速度发展演变。 一种替代方法就是把集成端点当做宽松类型对待,如图 6 所示:
图 6. 在集成端点采用宽松类型控制
通过将有趣的端点信息传送到一个文档内,您可以在通信双方任意一方主要升级和次要升级过程中保持端点定义不变。您可以灵活选择,而不是依赖 WSDL 来严格定义预期的内容。现在,端点总是接收一个封装该端点所需类型的文档。 要解决端点的版本控制问题,端点要做的第一步就是把文档解开,确定已经传输的内容,并用预期的内容与之协调。我通常联合使用 Factory 和 Strategy 设计模式(见 参考资料)来确定是否正在获得预期的内容,如图 7 所示:
图 7. 在端点内解开内容来确定类型
端点的首要工作就是查看文档的清单,确定它包含的内容。然后,它用一个库来实例化适当的策略,将那些信息从文档中抽出。一旦所有部分都通过验证(必要时可用 WSDL),反序列化的对象就被传递,用于业务处理。 这种方法有几个好处。首先,拥有一个带有两个正交作业的机制是个坏主意,然而那正是传统 RPC 所假设的:端点既要负责提供已发布的集成 API,又要负责验证类型。因为其有两个行为,所以您可能会弄混代码,使其更难理解和维护。其次,现在这个端点可以有多个用户,每个用户使用稍有差异的版本。只要您有一个策略,您就能够用相同的端点支持任何版本(包括那些更新缓慢的应用程序的老版本)。这允许您根据需要进行改变,不用担心这会迫使企业内应用程序的其他部分和您的改变保持一致 —— 它们可以根据自己的进度改变并使用新的文档版本。 目前没有任何工具或者框架允许您轻松地实现这种方法,但是一些额外的前期工作提供了之前提到过的好处。您可以使用 SOAP 或 REST 来实现这个样式(不过在 REST 中会更容易,因为它本身就是以文档为中心的)。通过创建一个宽松类型的生态系统,您可以使不同的开发小组按自己的节奏开展工作,从而使整个企业的应用程序使用以最小的摩擦前进。这就是演化架构的精髓:奠定一个基础来支持尽快实施无摩擦的、不损害功能的变革。
[size=1.5em]结束语 架构是个庞大且复杂的软件主题;在这部分中,我试图涉及许多不同的方面,从政治到 SOA 中的端点版本控制的实现细节。在以后的部分中,我将不断充实这些关于一般架构和新架构方法的想法,帮助您构建一个可发展的 SOA,以免向软件商支付数百万美元的高额费用。
|