在几乎每一个软件设计的基础上都有一种感知、抽象和分解的方法论。这种理念采用特定的抽象和分解技术将导致更好的设计。在处理变更的场景中,主要有软件开发的组件方法和服务方法,本文分析了它们在处理变更方面的差异。
1.核心的问题: 需求的改变
对企业而言,应对变化是日常生活中必须加以利用和实现的一个事实。合并、收购和新技术的引入是业务环境变化的驱动因素。业务敏捷性是指企业在不断变化和不可预测的环境中蓬勃发展的能力。
了解哪些方面更有可能发生变化,哪些方面不会发生变化,对于处理变化至关重要。尽管业务中有许多事情在变化,但有些要素往往保持不变。从中期来看,企业的核心能力相对稳定; 由于业务程序的改变或新技术的采用,企业的运作方式可能受到变化的影响。从长远来看,业务的几乎每个方面都可能发生变化。
为了满足不断变化的业务需求,软件系统必须不断地进化。
业务系统需求变化是软件设计的一个事实,但并非所有的软件开发方法都能对其解释,不同的方法在如何分解系统应对变化方面有着不同的哲学。
在20世纪70年代,结构化分析的发展是为了应对由许多程序员合作开发的复杂系统。结构化分析主要以功能分解为基础。自顶向下的功能分解从系统的顶层描述开始,然后一步一步地细化这个视图。通过每次细化,系统被分解成更低级别和更小的模块。自顶向下分解需要确定主要的系统需求和功能,然后在连续的步骤中分解它们,直到可以设计特定于功能的模块。
虽然功能分解在较稳定的系统类型方面取得了成功,但在处理业务变化以及随后的系统维护效率较低。要更改数据结构,通常需要更改与该结构相关的所有函数。因此,系统很容易变得不稳定,因为轻微的修改就会产生严重的连锁反应。
面向对象的范式通过在类中封装数据及其相应的操作来解决重用和维护问题。问题域中的对象概念比数据结构和函数具有更高的稳定性; 因此系统的整体架构通常是稳定的。此外,面向对象范式的内部细节更改不会扩展到系统体系结构中。
软件开发方法可以大致分为两大类: 需求预测方法和需求适应方法。第一种假设在编码之前可以识别和解决几乎所有的问题。后者采用了更实用的方法,认为业务系统开发是一个渐进的过程,变更是软件设计中不可避免的一个方面,预计将在每个阶段发生。
为了满足不断变化的业务需求,软件系统必须不断地进化。因此,软件开发过程和维护过程之间的分离变得越来越不重要。在这里,支持持续软件演进有两种设计方法: 基于组件的开发和基于服务的开发。
2.适应需求的变化: 组件化与服务化
软件生产的灵活性是技术和非技术因素综合作用的结果。在处理变更时,组件和服务之间的差异受到这里讨论的因素的影响。
2.1 组件:预制组装
基于组件的开发思想是通过组装预制软件组件来生产软件应用程序,从而实现软件开发过程的工业化。为了响应变化和不断变化的需求,基于组件的开发有两个基本思想。首先,如果可以从预制软件组件快速组装应用程序,那么软件开发可以得到显著改善。其次,将向开发人员提供越来越多的可互操作的软件组件,包括一般组件和专业化组件。
2.2 服务:需求与需求实现机制的逻辑分离
当客户预订从 A 地到 B 地的火车票时,他既不控制火车的运行,也不选择乘务员。在这种情况下,客户只对结果感兴趣,而不能控制实现结果的机制。服务被定义为: “任何一方可以提供给另一方的本质上是无形的,并且不会导致任何所有权的行为或表现。它的生产可能与实物产品有关,也可能与实物产品无关。”在软件中,这被称为“松耦合”。软件服务是一个粗粒度的、可发现的实体,它作为单个实例存在,并与应用程序和其他服务交互。服务的概念不同于组件的概念,因为服务不定义任何结构约束,而是定义接口。
2.3 约束
尽管面向服务的软件开发模式和基于组件的开发模式有着共同的特点,但也存在着较大的差异。它们共同的特点是软件系统的各个部分可以单独开发,然后再添加到系统中(进行绑定)。然而,它们绑定的方法大不相同。
基于组件的软件假设了组件的早期绑定, 也就是说,调用单元确切地知道在运行时之前要联系哪个组件。基于服务的开发采用了更灵活的方法,将绑定延迟到运行时,从而每次都能更改供应源。服务方法不仅允许在提供者中灵活变更,而且还适应需求质量随时间的变化。
在基于组件的开发中,软件组件是“从盒子里拿出来的”,然后插入到系统中,可能还添加了一些“粘合”代码。在这种情况下,所需功能的确切来源是在运行时之前确定的。基于服务的应用程序是动态的。应用程序可以由许多服务组成。对于每个服务,可能存在许多提供者,它们提供相同的服务,但具有不同的质量特征组合。每次调用服务时,可以选择不同的提供者来协商条款和条件,然后最终绑定服务。服务的提供者和使用者之间是松散耦合的。
在这里,服务由许多不同的服务组合而成,以提供某种结果。但是,这种组合对于服务使用者是透明的。
2.4 抽象与粒度
影响软件变更机制的一个因素是变更的粒度。粒度是指要更改的工件规模,范围从粗到中等到非常细的粒度。粒度是一个相对概念,只能在特定的场景中精确定义。例如,如果一个服务实现了银行系统的所有功能,那么它可以被认为是粗粒度的。如果它只支持信贷余额检查,那么它就被认为是细粒度的。
在20世纪90年代早期的面向对象革命之后,很明显,面向对象技术不足以满足现实世界软件系统快速变化的需求。虽然面向对象的方法提供了丰富的模型来描述问题领域,但这还不足以适应不断变化的需求。具体来说,对象过于细粒度,没有明确区分计算和组合方面,提出的组件来封装一组对象的计算细节。
服务应该发布在与现实世界活动或可识别的业务功能相对应的抽象级别上。服务及其方法的适当粒度级别相对较粗。服务通常支持单个独特的业务概念或流程。它包含实现业务概念的软件,因此可以在类似的上下文中重用它。
2.5 传输与通信
组件和服务之间交付机制的差异可能是个革命性概念。软件工程主要集中于为软件生产提供技术和管理支持,作为一种面向产品的概念。组件是面向产品的,其中软件通过 CD 或其他媒体交付。然而,基于互联网的计算扩散带来了新的概念、机遇和挑战,不仅在广泛的一般服务规定方面,而且也在重新思考软件交付的方法和模式方面提供了机会。将软件作为服务交付的主要好处包括通过松散耦合提高业务敏捷性的潜力,以及随着业务需求变化而发展的能力。
在面向服务的模型中,软件功能作为服务交付,其中每次都需要确定功能的服务元素,协商、执行条款和条件,然后“丢弃”这使得即使在最小的功能单元级别也可以灵活地进行更改。除了技术模型的不同之外,将软件作为服务交付还会带来新的业务模型,这些业务模型建立在这种远景提供的机会之上。示例包括用于计费软件服务的业务模型、服务协商规则以及信任评估和提供。
2.6 架构
组件体系结构是控制组件之间通信的一组接口和交互规则的规范。大多数组件体系结构代表了紧耦合的情况。例如,在 CORBA (一种基于组件的体系结构)中,客户端和服务器之间存在紧密耦合,因为两者必须与客户端的框架和服务器端的相应框架共享相同的接口。此外,大多数基于组件的体系结构的实现都是封闭的系统,因为它们只能处理专有技术。
面向服务的体系结构(SOA或者微服务)是一种设计软件系统的方法,通过发布和自动发现的接口向终端用户应用程序或其他服务提供服务。服务使用者通过代理与服务提供者解耦。面向服务的体系结构在现有 IT 环境之上添加了一个抽象层。通常,可以在组件基础结构上添加服务层。
3.挑战
通过组件或服务实现软件灵活性涉及到技术和非技术挑战。在解决方案成为商业现实之前,必须解决这些挑战。
3.1 信任
在软件的上下文中,正如与之相关的描述中所承诺的那样,信任是对组件或服务将提供其功能性和非功能性义务的信心。通过检查源代码来测试组件并不是一种实用的解决方案。然而,信任来自未知来源的组件可以通过在使用前多次测试来部分解决。此外,对源代码的任何更改都可能使组件契约规范失效。
在基于服务的开发中,信任问题要复杂得多,因为很难预测提供者是否符合商定的服务水平。当软件以服务形式交付时,必须监控服务级别协议是否符合规定。对于由其他服务组成的服务,这个问题变得更加复杂。在这种情况下,服务的最终质量将取决于组成服务的服务质量。
3.2 组合管理
与动态服务组合相比,由许多组件组合的系统是相对受控的。随着越来越多的服务提供者在大型分布式系统中公开他们的服务,人工管理和组合服务变得不可行; 这个过程必须完全自动化。与这种开放环境相关的是管理回滚、计费、许可和事务语义的问题。
3.3 适应与高级发现
组件选择是一个设计期间的活动,随后可能需要某种适应性。这种适应性有时被称为胶水代码。在基于服务的开发中,服务发现和选择在运行时进行; 也就是说,在确定了提供的来源之后。这使得在使用前测试服务几乎不切实际,因为服务的源以及使用条件可能在两个连续调用之间有所不同。
在基于服务的开发中自动发现是相对于其前身基于组件的开发的最重要的进步。
使用组件构建软件的一个主要限制是组件的指定方式。专有标准和依赖于实现的组件规范阻碍了基于组件的开发实现其促进复用的主要目标。
基于服务开发中的连接点是服务规范,而不是实现。这提供了实现透明度,并最小化了变更对软件系统的影响。
3.4 执行效率
运行时绑定的关键概念是基于服务所固有的。虽然实现这样的概念有利于灵活性,但也会导致执行开销,特别是当每次调用功能时都要进行服务发现和匹配的时候。
4.小结
需求变更在许多软件系统的生命周期中至关重要,特别是那些服务于高度不稳定业务领域的软件系统。组件和服务虽然相似,但并不相同; 它们有不同的方法论和抽象,都支持一定程度的演进。方法论和抽象级别的差异使得服务成为更好的变更解决方案。
所有未来的软件可能都是基于服务的?与其说是为了实用性,不如说是为了炒作。事实上,服务的概念适用于需求经常变化的系统,这些系统可以容忍某种低效。虽然组件是实现服务的好方法,但理想的基于组件系统并不一定产生理想的面向服务系统。因此,服务不会完全替换组件,而是补充它们。