使用模式创建一个面向服务的组件中间件(二)

日期: 2008-08-11 作者:Jim Siddle 来源:TechTarget中国 英文

  提供抽象执行


  由于服务可以彼此进行通信和交互,很明显,服务会自行处理执行状况,并运行自己的消息循环以处理通过服务总线接收到的进程消息。体系结构远景说明,服务应当关注业务逻辑,而不是执行的细节,因此下一个关键的步骤是对服务执行进行抽象。为了实现这一目的,将应用执行程序(&#101xecutor)模式。


  如果服务间的所有交互都是通过服务总线进行的,可以创建一个执行程序,用它把服务总线中的队列消息取出来,并根据接收到的消息确定执行上下文,然后将消息传递给服务进行处理。


  起初创建的是一个单线程执行程序,但它很快经过修改,引入一个线程类,并使用池模式创建一个进程池,这使执行程序有了支持服务并发执行的能力。进一步的修改提供了可配置性,使每个具有配置文件的服务都可以选择单线程或多线程的执行模型。


  实际上,线程配置机制并没有得到广泛使用,因为大多数服务必须同时处理多个请求以保证拥有可接受的系统响应能力。


  提供经过良好定义的接口


  服务通信、交互和执行抽象都已完成,因此下一个步骤就是在服务间建立经过良好定义的接口。系统要求服务处理它们自己收到的消息。体系结构的关键在于,要让服务把重点放在业务逻辑而不是通信的细节(如消息格式)上。显式接口(explicit interface)模式被用来为服务间的那些经过良好定义的接口提供支持,而代理(proxy)模式则被用来完成业务逻辑与通信细节分离的工作。


  需要某个特定接口能力的客户将会利用“服务上下文”发现某个支持该接口的服务,然后将该服务作为参数,进行方法调用。服务接口都被赋以唯一的名称,供标识之用。对于那些可进行本地解析的服务,可以使用该接口调用实际的服务对象。对于远程服务,则应调用代理对象。本地调用机制不久就被禁用了,因为该机制会绕过抽象执行。不过在某些条件下,出于性能的原因,它能提供一条用来优化服务调用的有效方法,特别是在可以通过配置启用它的时候。


  在纯粹的抽象基类中定义显式接口,以启用代理和服务类并轻松地实现它们,而且可以在本地和远程调用之间进行无缝的转换。支持接口的每项服务或代理必须继承定义该接口的抽象基类。支持多接口的服务会采用一个Java风格的多重继承方法,如采用多个纯粹的抽象基类,每个抽象基类都定义了一个由服务提供支持的接口。


  在C++中使用多重继承,对于某个使用了多重继承的特定类而言,可能会使基类出现多个副本,从而导致问题发生。不过,多重继承只能用在被认为可以减轻这一问题的接口实现中。


  服务接口的典型操作包括:


  ·订阅语音相关事件。
  ·调用或改变存在状态等操作。
  ·请求访问或修改配置数值或数据变量。


  随后,这一方法会与项目的平台独立性目标发生冲突。抽象C++基类中定义了每个显式接口,对于没有共享同一基础平台的服务客户机(如远程系统中基于Java的客户机)而言,这种做法并不合适。我们必须为其他平台上的客户机提供一种备选的接口定义,该定义是以基础消息格式文档的形式出现的。根据事后反思,更适当的做法是以一种具有平台独立性的形式提供接口定义,如使用接口定义语言,包括CORBA IDL或Web服务描述语言(Web Services Description Language , WSDL)。


  对称调用程序与代理对象对应,它借鉴了命令模式(出自《Design Patterns》一书),但在《Remoting Patterns》中它被正式描述为一种模式,引入该模式,可以在远程服务中调用显式接口。代理和调用程序对象之间建立了一对一的关系,而显式接口则充当了常用的关联方式。


  执行程序类经过修改,以便使某个特定服务接收到的每条消息都能使用框架上下文发现适当的调用程序类。执行程序把调用服务的责任指派给调用程序对象,同时将消息以及被调用服务(该服务为所需的接口提供支持)的引用传递给它。


  代理和调用程序组件都需要框架上下文对象的支持以建立某些通信细节,如用于返回消息的服务总线位置。


  使用显式接口、代理和调用程序模式的结果是,提高了服务交互的复杂性,使不熟悉模式和面向对象开发的开发人员难于理解。如果要建立相对简单的服务交互,则需要大量的开发工作。根据事后反思,如果代理和调用程序类是自动生成的,则会更好。“服务生成器”的功能是根据显式接口为代理、调用程序和服务类生成框架代码,它已经被部分编写出来。不幸的是,迫于项目的时间压力,不可能对它进行深入开发。


  观察器此后将得到应用,它使用经过良好定义的接口,支持向多个订阅者异步发出事件通知。该模式的应用是在客户机-服务器引入的异步行为以及显式接口引入的各个接口的基础上构建的。通知事件通过回调型显式接口以及与之对应的代理和调用程序对象,与观察器进行通信。


  有时候,观察器会在软件系统中得到广泛使用。它与代理模式一起被用来解决几种不同的问题,并成为设计会议中常常讨论的一项核心项目课题。应用这一模式,可以解决诸如如何将物理设备事件、托管数据更新事件和其他语音相关事件通知给多个订阅者等问题。观察器是用来处理体系结构内的事件通知的关键模式。


  图2. 服务调用




 
  图2 显示了服务调用的流程。


  确保服务的透明性


  虽然服务之间拥有经过良好定义的接口,但是如果要确保特定接口的客户机和实现这些接口的服务间不存在依存关系,仍然有问题存在。为了解决这一问题,服务透明性是必不可少的。


  服务上下文提供了本地和远程两种方式,用来发现为所需的显式接口提供支持的服务。查询被用来实现这个目标。在发现期间查询了两个来源:由组件配置器实现进行管理的本地服务“存储库”,以及位于一个已知位置的“服务注册中心”。


  如果在本地存储库中找到了一个支持所需的显式接口的服务,将返回对该服务对象的直接引用。如果没有在本地找到支持该接口的服务,或是已禁用了本地发现功能(如提供经过良好定义的接口中所述),则会查询服务注册中心,并根据发现的信息创建一个代理。服务总线地址表示支持所需接口的服务的位置,该地址与服务总线对象一起被传递给代理对象,以使它能发送消息。类似的查询机制也被建立起来,使执行程序实现能根据调用的接口查询相应的调用程序对象。


  该方法能保证服务客户机对实现其调用的显式接口的具体服务不具有依赖性。它还能确保代理和调用程序对象与显式接口的特定远程实现没有关联。相反,它们只在接口处提供一种调用转换方法,将调用转换为基于消息的形式,或将基于消息的形式转换为其他形式。


  这个方法的重要后果是显而易见的。显式接口、代理、调用程序和组件配置器实现间的交互严重依赖于C++实时类型信息(Run-Time Type Information, RTTI)。当在不同的共享库或执行文件中创建强制转换对象时(如客户机服务将代理对象强制转换为特定的接口,以发出服务请求),动态强制转换是必不可少的。如果项目中的某些目标资源是有限的,对于RTTI的需求会成为这种环境中的限制性因素。


  最初的服务发现机制纯粹以所需的接口为基础。但在将来,该机制有可能利用服务上下文扩展可用的发现选项。例如,可能会支持按某些条件(如“支持的数据库条目数量”或“安全性级别”等)发现服务。


  图3. 由组件配置器实现支持的上下文




 
  图3 显示了由组件配置器实现支持的上下文。


  提供扩展性


  现在,系统的核心框架已经搭建好了。托管的各项服务:


  ·可以通过一条支持位置独立性的服务总线,以同步和异步的方式彼此进行交互
  ·不了解通信和执行的细节
  ·是利用经过良好定义的接口从服务提供者的细节中抽象出来的,这些接口由显式接口以及代理和调用程序对象提供支持。


  拦截器模式在此后会得到应用,以实现在扩展性方面的核心项目目标。它被表示为一个应用于总体体系结构远景的模式。它的目的是,在服务总线这样一个负责在服务和服务客户机之间传送信息的核心框架中引入一个拦截点。


  为了在不同的拦截点间保持一定程度的一致性而做出的决定是,创建一组基类,可以根据这些基类为每个具体的拦截点创建子类。模板方法模式的应用,旨在建立一个将事件分派给拦截器的机制,在其中,具体的实现决策会被指派给与某个特定的拦截点相关的子类。


  团队认为,稍后在项目中,当设计出拦截器的动态管理时(可能是使用组件配置器设计的),基类的作用将被证实。回想起来,在没有显式需求的情况下引入这些类,这种做法并没有必要。使初期的实现尽量简单,只在必要时引入某个抽象,这可能是更适当的做法。


  服务总线构成了整个软件的基础,因此拦截点是通用的,这样可以最大限度地保证其适应性和适用性。人们感到这种做法可能会带来对性能的影响,特别是插入了许多具体的拦截器的情况下。在第一个使用场景中对拦截点的要求是最低的,迄今还没有出现性能问题。


  为了确保将性能保持在可接受的水平,必须仔细选择拦截器,在最坏的场景中,可能有必要进行性能分析和代码效率增强处理。在开始时创建的概念日志记录拦截器是用来拦截和记录由服务总线发出的消息的,稍后还会在执行程序实现的服务调用点引入一个更深入的拦截点。所编写的性能记录拦截器是用来记录每个服务调用的性能统计数据的,它会使用这个拦截点。在将来会创建与安全相关的拦截器。


  中介模式的“桥接”角色是使用模板方法模式实现的,它通过几种不同的基础通信技术,为远程服务调用提供支持。在项目的早期阶段会发现远程调用服务方面的问题。用多种不同的远程传输机制和技术(如UDP/IP和USB)调用服务,这是可能的,也是必要的。必须使用某种通用方法以避免将复杂性和不一致性引入系统,这也可以对平台独立性的目标提供支持。


  引入一个模板类,这个模板类能理解如何与服务总线进行本地交互,并利用模板方法,将与外部交互的实现细节指派给子类。


  在项目的后续阶段中,这提供了一种简单的方法,为不同类型的外部通信添加新的子类。它还能实现内外服务通信细节的分离。后来该项目中的一项后果是,实现的模板类并不能理想地适用于所有类型的通信技术。初期实现是以第一项必需的技术为基础建立起来的,但是引入的其他技术无法与模板很好地结合。在开始之前,更好地理解可能使用的通信技术,并根据这些理解,以一种通用性更强的方式定义模板,这可能是更合理的做法。


  确保可接受的性能


  系统核心与某些扩展机制已经就位。下一个任务是确保系统在一个可接受的水平上运行。为了实现这一目标,有几种模式得到了应用,不过它们不是作为补救措施而应用的。在开发期间,会按照需要将它们引入。为了清晰起见,我们将一起讨论它们,揭示它们与先前应用的其他模式的关系。


  执行程序中应用了池,这可以有效地使用线程资源。异步完成令牌也能对线程的有效使用提供支持。例如,正在等待异步响应的服务可能会释放线程,将其放回线程池中。


  缓存模式被用来存储代理和调用程序对象,以供将来使用。可以使用框架和服务上下文,对代理和调用程序的中央缓存进行查询。执行程序内部也可以使用缓存;例如,可以对与某个特定服务相关的最新调用程序对象进行缓存。


  对显式接口和代理模式支持的服务进行本地解析(如提供经过良好定义的接口中所述),可以绕过通信和执行基础设施,这对实现可接受的性能有所帮助。


  最后,利用框架上下文接口公开一个启动方法,从而将延迟获取模式应用在服务的初始化中。组件配置器经过修改,可以根据某个配置参数,有选择性地初始化服务,对于那些在启动时不会自动初始化的服务,可以使用上下文启动它们。执行程序实现经过修改,以便在服务第一次接收到消息时对它们进行初始化。


  应用横切关注点


  在建立了系统核心、可扩展机制和可接受的性能之后,最后一个步骤就是将横切关注点应用到系统。


  独立模式在设计会议中往往受到贬斥,因为它是一种“反模式”,而且“对于某个全局变量而言是个‘政治上正确’的术语”(Wikipedia中对独立模式的定义)。不过本项目中有两个紧密相关的场景可以证明独立模式是有用的:可以用于日志记录和异常处理。不幸的是,面向方面的编程并没有被列入考虑范围。它能提供更好的解决方案,但是当时并没有启用面向方面编程的合适C++工具,因此没有考虑使用它。


  项目的目标之一是重用现有的软件组件。团队认为,在软件体系结构中,只有某些形式的全局实例才能处理特定的横切关注点。在开发期间,有几个来自先前项目的大型软件模块被引入系统中,对于现有代码和新代码而言,一种用来横切关注点的通用方法是必不可少的。


  改变现有代码以引入用来横切关注点的新机制,这需要花费太多的精力,独立日志记录处理程序和异常发布程序类就是因此创建的。为了重用现有代码,对日志独立对象的调用被封装在宏中,这些宏与遗留代码库中的宏十分类似。这可以对现有代码进行一次轻松的转换,改用新的日志记录机制。


  独立异常发布程序负责确保将异常报告给系统中某个单一的位置。这种方法保证将系统中的所有异常都报告给体系结构中的某个要素,并因此提供更高的系统可用性级别。这遵循的是《Software Architecture In Practice》第6章中所述的“容错层次结构”方法。


  虽然独立模式可对项目的重用目标提供支持,但该模式会导致所有代码对横切关注点类的特殊实例产生依赖性。如果允许日志记录和异常处理类有多个实例则会更好。使用服务和框架上下文,可以将这些类的实例提供给服务和框架要素,而全局实例则会支持现有代码中的横切关注点。这将:


  ·提供一种更为灵活的方法以便在系统中应用横切关注点,并使现有代码和新代码之间拥有一定的共同性。
  ·避免将所有代码与横切关注点类的特殊实例联系起来。


  “纯粹”的独立模式实现在后来会掺入其他因素,以使核心基础设施管理代码能动态管理相应的对象实例。


  因为执行程序模式将所有服务的执行封装了起来,所以可以将一个通用的异常处理策略应用于所有服务。这一目标是通过在Thread类中添加一个通用的“catch-all”代码块来实现的。如果发生了异常,这个catch块只是简单地调用异常发布程序,然后将正在执行的线程返回到线程池中。


  解决决定因素


  这一部分描述了所选的模式是如何帮助解决问题的(请参阅表1中的简要介绍),并对每种决定因素的解决方法进行了总结。


  某个产品线策略中对软件组件的未来重用


  分层模式提供了支持某一产品线策略所必需的整个体系结构的结构,它将可重用和特定于平台的软件要素分离成不同的层。


  使用包装外观模式封装操作系统代码、遗留代码和第三方代码,这提供了一条途径,可以利用适当的抽象将现有代码和新代码分离开来,抽象支持两组代码的重用。


  使用组件配置器模式管理服务生命周期,利用中介支持位置透明性,这提供了一个在多种不同场景中部署服务的方法,并可支持重用。


  客户机-服务器模式可在系统内建立多种清晰的角色,并支持创建各项服务,这些服务的角色和职责均经过清晰定义。它对服务的清晰性和内聚性,乃至总体的可重用性都有所贡献。


  利用组件配置器模式可对服务生命周期进行抽象;利用中介、代理和调用程序,可对通信进行抽象;而使用独立模式,则可对横切关注点进行抽象。所有启用的服务均将重点放在业务逻辑上,这有助于创建可重用的资产。


  经过良好定义的可发现接口(由显式接口模式提供支持)、封装上下文对象和查询功能使服务能专注于业务逻辑,因为关注的重点被放在接口公开的功能而不是实现的细节上。


  封装上下文对象提供了一个将服务执行上下文和框架要素封装起来的方法。分离的上下文接口将上下文实现和上下文用户分离开来。这些模式的组合可以提供一致、分离的方法,用来访问执行上下文细节。


  使用观察器以创建能为多个订阅者生成事件的可重用服务,而无需(或很少)依赖于其他服务,从而带来更多的重用机会。


  在服务总线内准备的通用拦截器提供了一个有用的扩展点。使用模板方法实现中介模式的桥接角色,这种做法提供了一种可以在将来添加外部通信备用类型的机制。


  在运行时对服务进行无缝(重新)部署


  组件配置器提供了一套动态机制,用来管理服务生命周期和部署。它提供的方法可以用来随意分配服务,使它们在不同的进程中运行,并可快速移除导致崩溃或死锁的服务。在某些情况下,只需重新排列服务配置文件就能修复缺陷。它还允许创建特殊的配置文件以运行测试。


  独立于部署模型的服务调用透明性


  中介能提供位置透明性。由显式接口和查询提供的服务透明性,可以在服务和服务间的接口之间进行分离。


  使用纯粹的抽象C++显式接口类与真正的服务调用透明性是矛盾的,因为非C++系统中的服务客户机需要备用的接口定义。


  一个用来管理、测试和处理服务的通用方法


  经过良好定义的分离接口是由显式接口引入的,它能创建用来管理和测试的通用接口,服务可以按照要求提供支持。


  独立模式支持横切关注点,使之可以利用新代码和遗留代码,以不同的抽象级别跨越各种软件组件。


  重用和保护现有软件组件


  使用包装外观模式,将遗留代码模块间复杂的交互关系包装起来,提供更多的重用机会。它还在新代码和遗留代码之间提供一个保护层,以确保这两种类型的代码不致于对彼此造成不利影响。


  显式接口还可以允许服务包含现有的软件组件,从而使软件组件的重用和保护成为可能。


  通过提供独立对象以支持遗留组件所需的接口,可以更有效地使用遗留组件。


  在资源有限的环境中的可接受性能


  对代理和调用程序对象进行缓存,配合执行程序中的线程池,可以提高资源的使用效率。异步完成令牌支持线程池,它允许服务在等待异步响应时释放线程,并将其放回到池中。


  利用显式接口引入经过良好定义的接口,虽然会付出打破执行封装的代价,但它提供的本地解析能确保实现快速有效的访问。


  不幸的是,显式接口、代理、调用程序和组件配置器模式之间的交互,由于要依赖于C++ RTTI,它们可能会对内存消耗和运行时性能造成不利影响。


  延迟获取模式提供了一套快速有效的启动机制,它使服务得以延迟,直到必要时才启动服务。


  独立于特定的计算平台


  分层模式提供了支持平台独立性必需的基础体系结构的结构,它将可重用的代码和特定于平台的代码分离到不同的层。


  使用包装外观模式可以定义纯粹的操作系统抽象,后者可针对特殊的操作系统加以实现,从而使服务和与服务相关的代码具有平台独立性。


  使用纯粹的抽象C++显式接口类与平台独立性是矛盾的,因为非C++服务客户机需要备用的接口定义。


  总结


  在本文中,您已经了解了怎样应用各种模式才能针对资源有限的语音设备实现面向服务的组件中间件。本项目中包括可重用的自包含软件模块的一些关键性的SOA特性、经过良好定义的接口,以及平台独立性。


  与重用性、适应性和性能相关的各种决定因素,使众多可供选择的模块得以应用,包括组件配置器、拦截器、封装上下文对象和代理模式。在创建一个框架以实现项目的体系结构目标的过程中,模式居于中心地位。在多个模式的实现中要求使用的 C++ RTTI,被认为可能会在资源有限的环境中造成问题。对于非自动生成的调用程序和代理类来说,开发它们时的开销也是令人担心的。


  整个服务框架(例如生命周期管理、通信、服务接口和横向关注点要素等等)是在内部开发的。开发内部解决方案有一些优势,但另一个可供采用的方法是使用第三方框架,如嵌入式CORBA或IBM Rational Rose RT。在本项目的早期,有几种第三方框架被列入考虑范围内,但由于它们都不太合适,所以没有采用它们。


  项目中应用的模式并不是来自某个特殊的模式语言;它们是从几本最受欢迎的出版物中挑选出来的,得到了权威人士的推荐,或已经为团队的成员所熟知。过去的做法是精心挑选模式语言并应用其中的模式,而不是逐个选择更合适的模式,后者可以避免某些缺陷。模式语言的一个例子是Remoting Patterns,它提供的模式用来解决在企业、Internet和实时分布对象中间件领域中的问题。


  在撰写本文时已经声明了若干个主要的项目交付里程碑,而且现在项目正在逼近它的最后一个交付里程碑。


  关于作者


  Jim Siddle是一位软件开发人员兼架构师,参加过从统一消息、远程系统管理、嵌入组件框架,到供应链管理和企业消息中介的多个创新型软件项目。他积极参加模式社区的活动,有大量文章发表或正在创作中。Jim在IBM的工作地点是英国的Hursley Park。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐