面向服务

在面向服务的应用程序中,开发者只需要关注业务逻辑的编写,以及通过可交换的、可互操作的服务终结点暴露业务逻辑。客户端调用这些终结点,而不是服务代码或者它的实现包。客户端与服务终结点的交互基于标准的消息交换,服务发布各种标准元数据,描述服务的功能,以及客户端调用服务操作的方式。元数据就是服务,相当于C++的头文件,COM的类型库,或者.NET程序集的元数据。服务的终结点是可重用的,在交互的约束(例如同步、事务以及安全通信)下,服务是与客户端兼容的,而与客户端的实现技术无关。

在开发面向服务应用程序时,我们能够实现服务代码与客户端使用的技术与平台的解耦,也与并发管理、事务传播和管理以及通信可靠性、协议和模式无关。总的来讲,实现从客户端到服务的消息传递的安全,就是对调用者的认证,它属于服务范围之外。服务根据需求仍然要实现服务自身的本地授权。在大多数情况下,客户端并不知道服务的版本:只要终结点支持客户端期望访问的契约,客户端就不用考虑服务的版本。为了处理客户端与服务之间传递数据的版本兼容,面向服务同时还构件了版本兼容的标准。

面向服务的价值

由于客户端与服务之间的交互是基于行业标准的,这个行业标准包括了保障调用安全的方式、传播事务流的方式以及管理可靠性的方式等等。我们也可以使用现有的这些公共基础功能的实现。这就保证了应用程序的可维护性,因为应用程序在正确性方面是完全脱耦的。即使公共基础功能发生演化,应用程序也不会受到影响。面向服务的应用程序是健壮的,因为开发者能够使用可用的、已验证的、通过测试的公共基础功能。同时也提高了开发者的效率,因为他们可以将更多的时间投入到功能特性的实现,而不是这些公共基础功能。面向服务的真正价值就是:允许开发者从代码中抽取出公共基础功能的实现,更多地关注业务逻辑和需要的功能特性。

面向服务还包括许多广受欢迎的价值,例如跨技术的互操作性。编写服务时,通常不用考虑客户端执行在什么平台上,因为面向服务完全实现了无缝的互操作性。面向服务应用程序所能提供的不仅仅是互操作性,它还允许系统跨越边界。其中一种边界就是技术与平台的边界,跨越这种边界则完全体现了互操作性。

面向服务应用程序

一个面向服务应用程序只是简单地将服务组合到一个单一逻辑的、整体的应用程序中,这类似于聚合了对象的面向对象应用程序,如图1所示:

 

 

图1:面向服务应用程序

应用程序自身可以将组合服务公开为新的服务,就好像一个对象可以由多个小的对象组成一样。

在服务内部,开发者仍然使用传统编程的概念,例如特定的编程语言,版本,技术与框架,操作系统,API等。但是,服务之间则必须使用标准的消息与协议、契约以及元数据交换。

应用程序中的不同服务全部可以放到相同的位置上,或者分布放到局域网或互联网上。它们也可以来自于多个开发商,使用各种不同的技术与平台进行开发,版本独立,设置执行在不同的时区。所有的这些公共基础功能特性对于在应用程序中与服务交互的客户端而言,都是隐藏的。客户端发送标准消息到服务,两端的公共基础功能通过消息以及与平台无关的传输型表示形式进行转换,并对客户端与服务之间存在的区别实现封送(Marshal)。

 既然面向服务框架为了将服务连接在一起,提供了现有的公共基础功能,那么服务的粒度越小,就越能够有助于应用程序对这些基础设施的使用,开发者所要编写的公共基础功能就越少。在极端的情况下,每一个基本的类都应该是服务,以便于最大程度地利用现有的互联性,避免编码实现公共基础功能。理论上讲,它能够轻松地实现事务型整数、安全字符串以及可靠的类。然而实际上,粒度过于细化会影响到使用的框架(例如WCF)的性能。(我相信,面向服务技术会随着时日的发展而逐步演进,服务边界的内聚性会越来越强,服务的粒度会越来越小,甚至于每个基本的构建模块都可以成为服务。显然,这是历史的发展趋势,那就是通过方法学的改进以及抽象,以性能换取效率的提高)。

 

要素与原则

面向服务方法学负责管理服务之间所发生的内容(参见图1),它有一套设计原则与最佳实践,用于构建面向服务应用程序,称为面向服务架构原则:

1、服务边界是明确的

任何服务总是被限制在边界(例如实现技术和分布位置)之后。服务公开的契约与数据类型不会将它的实现技术与分布位置透露给客户端,从而隐藏了这些边界的本质。坚持这一原则可以使得服务与位置和技术无关。不管是以何种方式思考这一原则,它所表达的思想就是客户端知道服务的实现越多,则客户端与服务的耦合度就越高。要减小潜在的耦合度,服务就必须明确地公开它的功能,而且只有操作(或数据契约)才会明确地被公开给客户端共享。服务的其余内容会被封装起来。

【面向服务技术采用了默认为“否决(Opt-Out)”的编程模型,公开的内容则被明确标记为“参与(Opt-In)”

注:Opt-Out与Opt-In本身属于发送广告中的两种不同行为与授权方式。在这里,Opt-Out指的是如果服务的成员没有明确地进行设置,则默认是不暴露的,即否决机制。Opt-In则指的是只有明确标记了需要暴露的成员,则该成员才会参与到服务中,能够被跨越服务边界调用。】

2、服务是自治的

服务无需获取它的客户端或其他服务的内容。服务的运行与版本应该与客户端无关。这一原则允许服务脱离客户端单独演化。服务的安全也是独立的,它能够保护服务自身以及传递的消息,而不用考虑客户端使用的安全级别。这样做同时也能够解除客户端与服务安全之间的耦合。

3、服务共享操作契约与数据样式,而不是类型与特定技术的元数据

服务要做的就是决定公开在服务边界之外的内容应该与技术无关。服务能够将本地的数据类型转换为某种与技术无关的表示形式,而不是共享本地的、特定技术的内容,例如程序集版本号或者它的类型。此外,服务应该禁止客户端知道本地的实现细节,例如实例管理模式或并发管理模式。服务只公开逻辑操作。服务对于操作的实现方式以及执行方式,对于客户端而言是不透明的。

4、服务与策略保持一致

服务应该发布一种策略,指示它所能完成的内容以及客户端与服务交互的方式。策略所体现的访问约束(例如可靠通信)不应依赖于服务的实现细节。并非所有的客户端都能与所有的服务交互。这种不兼容性是完全有效的,它能防止特殊的客户端访问服务。发布的策略是客户端决定它们能否与服务交互的唯一方法,同时不应有任何的外带机制让客户端做出这样的决策。不同的是,服务必须能够在策略的标准表达形式中,表示服务能够执行的内容以及客户端能够与之通信的方式。如果无法表示,就意味着服务的设计是拙劣的。注意,如果服务是私有的(即不是共有服务),那么实际上它可能不会发布任何策略。这一原则暗示如果服务需要,就应该能够发布策略。

实用原则

前面列举的原则是非常抽象的,对它们的支持主要体现在开发、调用以及设计服务的技术方面。下面是一些更加实用的原则:

1、服务是安全的

服务与它的客户端必须使用安全通信。至少,从客户端传递到服务的消息必须是安全的,客户端必须具备验证服务的方法。同时,客户端可能会在消息中提供它们的安全证书,这样服务才能够对它们进行授权与认证。

2、服务在系统中应保持一致的状态

执行客户端请求时,禁止进行部分替换的条件。服务访问的所有资源在客户端调用之后必须是一致的。服务不能有任何剩余内容作为错误的结果,例如部分地影响系统状态。服务不应寻求它的客户端的帮助,在发生错误后,服务会将系统恢复为一致的状态。

3、服务是线程安全的

服务必须设计为线程安全,才能够维持多线程的并发访问。服务同样能够处理因果关系或逻辑线程的重入。

4、服务是可靠的

如果客户端调用服务,客户端总是能够以确定的方式获知消息是否被服务接收。消息应该按照发送的顺序处理,而不是接收的顺序。

5、服务是健壮的

服务与它的错误分离能够防止错误影响服务本身或其他服务。服务不能要求客户端根据服务遇到的错误类型改变它们的行为。这能够有助于客户端与错误处理层面上的服务解耦。

可选原则

我们可以将实用原则看作是强制原则,同时还有一套可选原则:

1、服务是互操作的

设计的服务应该能够被任意的客户端调用,而不用考虑客户端的技术。

2、服务的规模是不变的

不管客户端有多少,也不管服务的承载是多少,服务代码都应该相同。随着系统的发展,这样的设计才能够极大地降低维护服务的成本,服务也能够支持不同的部署场景。

3、服务是可用的

服务总是能够接收客户端的请求,而不会因此停止。如果服务不可用,则意味着客户端需要解决服务的问题,反过来就会引入耦合。

4、服务是及时响应的

服务开始处理客户端的请求时,不能让客户端等待太久。如果服务不能及时响应,则意味着客户端需要解决服务的问题,反过来就会引入耦合。

5、服务是受限的

服务执行的任意操作应尽可能短,不能消耗太多时间去处理客户端的请求。长时间的处理过程意味着客户端需要解决服务的问题,反过来就会引入耦合。