在与客户的多次接触中,我都需要建立一套基本的SOA原则。以下章节介绍了面向服务的架构(SOA)所需要遵循的基本准则。
这些准则不是绝对正确的,只是一套针对SOA相关讨论的框架性意见。你可能注意到前四条是基于Don Box的四项原则,只是我在经过了这么长时间后加上了点个人的想法(我之所以把它们列出来是因为有人要求我提供一个可供参考的版本;我没有回顾过这几条,可能现在我对其中某些的看法已有改变)
1)清晰的边界
在调用一个服务来完成它提供的功能的时候,需要将服务所需的所有信息传递过去。所有对服务的访问都应通过其对外公开的接口;不应当允许某个服务存在任何隐含的假设。:“服务不可避免地要依赖消息,因为所有进出服务的东西都是消息”。通常情况下,服务的调用应该不依赖共享的上下文,而应该设计成无状态的。服务所暴露的接口通过契约规定了它的功能性和非功能性的作用和特征。服务的调用是一个有业务意义的动作,可能是十分消耗资源的,并引入一系列不同于本地调用或远程过程的异常。服务调用是不同于普通远程过程调用的。
提供和消费服务需要尽可能的容易,所以应当避免隐藏过多服务之间所发生的交互。服务发送和接受的消息、服务的契约以及服务本身是SOA中首先需要确立的。这里面所包含的意思,对于任何建模的语言和工具来说,就是起码应该对编写服务的程序员提供这几个概念方面的API.总而言之,服务通过外部接口隐藏内部细节,暴露服务的功能;于服务的交互是一个显式的行为,它依赖服务的提供者和消费者之间消息传递。
2)共享契约和Schema,而不是Class类
根据服务的描述(某种契约),服务的消费者和提供者应该拥有消费或提供服务所需要的所有东西。根据松耦合原则,服务的提供者不应该依赖消费者在它自己环境下所能提供的代码重用能力;因为服务可能被用在不同的开发时和运行时环境中。该原则在很大程度上限制了可以在SOA中交换的数据类型。理想的数据格式是使用可以被一个或多个Schema所验证的XML文档,因为几乎所有可以想象到的语言环境都支持XML.
3)策略驱动
要跟一个服务交互,必须满足两组正交的需求:
1)服务提供者的功能、语法和语义必须满足服务消费者的需求,
2)技术能力和技术需求必须相符。
打个比方,一个服务提供者可以很好地满足一个服务消费者的要求,但是它是通过JMS来提供服务的,而消费者却只能使用HTTP(例如它是在.NET平台上实现的);提供者可能要求通过XML加密标准在消息层进行加密,而消费者只支持SSL的传输层加密。即便在那些双方都具备所需要的能力的情况下,这些能力也需要被“激活”--例如,服务提供者可能会有必要根据不同的消费者对应答消息采用不同的加密算法。
为了支持各式各样不同设备和不同能力的服务消费者来访问服务,SOA工具集必须引入某种策略机制。服务的功能性特征是由它们的接口来描述,而这些非功能性能力和需求则是用策略文件来指定的。
4)自治
跟“清晰的边界”相关的一条准则是,一个服务应该是自治的,即,它跟外界的唯一关系(至少从SOA的角度来看)是通过它的接口。在某些情况下,一个服务还必须能够切换它的运行期环境,例如,从轻量级的原型实现切换到完善的基于应用服务器的互相协作的多个组件的实现,而不需要用户做任何相应的改动。服务可以相互独立地进行改变、部署和版本管理。服务的提供者应该能够不依赖用户能力而迅速切换服务到更新的版本;而一些用户也无法或不愿意切换到新的服务接口(特别是那些服务提供者无法控制的用户)。
5)用格式连接,而不是用程序API
服务都支持使用特定的连接格式。这条原则和“清晰的边界”原则关系非常大,不过是引入了一个新的角度:尽最大可能让服务可以被访问(从而达到长期的可用性),即,只要遵循服务所定义的交互策略,应该可以从任何平台上通过符合服务接口定义的消息的交互来访问一个服务。比如,要测试某个服务是否符合这个准则,可以看看是否能使用某种非主流的动态编程语言,例如Perl,Python或Ruby来调用或提供这项服务。虽然这些语言在当下的技术蓝图中无足轻重,但可以作为试纸来评估服务是否满足下列约束:
1)所有消息的格式使用公开标准,或人类可读的描述;
2)不需要耗费很多工作就可以创建符合特定Schema的消息,也不需要依赖特定的程序库;
3)正确通讯所需的额外信息(例如为了安全或可靠性所创建的头部信息)的语义和语法应该依照公开规范或标准;
4)与服务交互相关的传输(或传送)协议中至少栀湩???? ?o??有一个是标准的网络协议(或可以通过标准网络协议进行访问的);
6)面向文档
在服务交互的时候,数据是通过文档传递的。文档是显式建模的树状容器。在上面提到过的自描述性是面向文档的一个重要特征。理想状况下的文档应该是按照真实世界的文档来建模的,如采购单、发票或帐户声明。设计良好的文档在特定问题域的上下文环境中很有帮助,即它应该被一个或多个服务所采用。
类似于真实世界的纸质文档,在服务中交换的文档会包含冗余的信息。例如,客户的地址可能与客户的ID信息放在一起(尽管只有客户ID就足够了)。这些冗余的信息会被显式的使用,它的作用在于将服务和服务消费者和服务提供者的底层数据模型进行隔离。当使用面向文档模式的时候,服务调用成为了有意义的业务信息交换而不仅仅是上下文无关的RPC调用。尽管没有要求,一般都假定用XML来作为这种文档的格式/语法。
消息在通过SOA互连的异构系统间的参与者之间流动着。松耦合原则要求对于共有认知的依赖越小越好。当消息在分布式对象或RPC环境中传递时,客户端和服务端依赖的是一组用同一个接口描述文档自动生成的代理类(stubs和skeletons)。如果不采用这样的方式,若契约本身不支持双方交互则将无法通讯。所以说,RPC形式的架构必然要求客户端和服务端代码同步变化。
通过下面的比较我们来看一看。考虑以下消息:
“2006-03-1347113”
将它和
<order />
<date />2006-03-13</date />
<product />4711</product />
<quantity />3</quantity />
</order />
比较一下。
显然后者比前者的可读性更好,并且对于后者来说,除了使用固定的语法,参与者还可以使用例如XPath这样的技术来隔离细微的非颠覆性的变化。反过来说,如果用了像XML这样的自描述格式的消息却仍然使用RPC方式进行通讯,其作用无非只能证明XML是最消耗带宽的一种方式。如果你使用XML,那么一定要利用它的优点而不是缺点。
7)松耦合
大多数SOA倡导者都认同松耦合是一个重要的概念。但不幸的是,对于一个系统“松耦合”特性的认定却有很多不同的选项。认定一个系统是松耦合还是紧耦合的,根据具体的需求和上下文可以有许多维度,从某些维度来看可能是松耦合,但是从另一些维度来看又可能那是紧耦合。这些维度包括:
时间:当(服务)参与者在时间维度上松耦合时,他们不是必须同时在线才能进行交互。这要求他们之间必须有某种缓冲和队列的机制。当一个参与者发送消息到对方的时候,它不必依赖立即的消息回复也能继续流程。
位置:如果参与者需要知道其它参与者的位置信息来进行通讯,那么位置的松耦合意味着当这些位置信息的改变时应该不需要重新编程、重新配置或重新启动通讯中的各个参与者。这就隐含要求了服务的调用需要有一个寻址过程,并需要某种目录或地址簿存储服务终端的位置。
类型:类似编程语言中的静态类型/动态类型或强类型/弱类型,类型的松耦合使得服务的参与者不依赖文档的全部或哪怕部分结构。
版本:服务的参与者可能依赖特定版本的服务接口,也可能可以接口接口的(一定程度上)变化。越是依赖服务的特定版本,参与者的松耦合的程度就越低(在该维度上)。一个不错的参考准则是Postel法则:服务提供者的实现应当尽可能接受各种版本,以拥有更大的自由度(甚至可以容错),而服务的消费者则应该尽可能地遵循特定的语法和文档接口。这样可以增加系统整体上的可靠性和灵活性。
关联:在某些情况下,服务消费者和服务提供者之间是1:1的关系,尤其是在“请求-答复”式交互或使用显式的消息队列的情况下。而在另外一些情况中,服务的消费者(叫“消息发送者”或“事件源”更合适)可能不知道甚至根本不在乎收到的消息数。
查询:请求服务调用的参与者既可以根据服务提供者的(物理的或逻辑的)名字来跟它们通讯,也可以首先根据一组能力描述执行一个服务查找操作。这意味着需要有一个可以将消费者需求和服务提供者能力进行匹配的服务注册表或服务仓库。
接口:既可以要求参与者依赖服务所提供的特定接口,也可以要求它们支持一个通用的接口。如果使用通用接口,所有使用这个通用接口的参与者就可以和所有提供这个通用接口的参与者进行交互。尽管乍一看这样做非常别扭,但这种单一(统一)通用接口却是WWW结构中的核心所在。
任何创建的系统不可能、也不需要满足上述所有维度的松耦合。对于不同类型的服务需要做不同程度的权衡。
8)兼容工业标准
在准备实施SOA的时候一条关键的原则就是依赖标准而不是依赖特定的API。从技术层面上来说,标准包括数据格式、元信息、传输协议、以及业务级别的文档格式(如UBL)。
9)厂商无关
在任何架构准则中,都不应该依赖某个特定厂商的产品。在将抽象概念转化到实际可运行的系统的过程中,不可避免地要选用某个特定的产品,不论是商业的或开源的软件。但是不应该在架构一级上就先入为主地考虑到这些决定。这就是说要尽量考虑到标准的互可操作性和可移植性。这样一来,参与者可以使用支持标准的任何技术来构建,而不用受制于厂商的产品路线。
10)元数据驱动在整个SOA架构中,所有的元信息必须以恰当的方式存储在某个地方,使得它们可以同时在设计时和运行时被发现、获取和解释。这些元信息包括服务接口的描述、参与者、服务终端(endpoint)、绑定信息(bingdings)、组织的结构和职责、文档类型和schema、消费者-提供者关系,等等。这些元信息的使用应该利用代码生成或解释的方式尽可能地自动化,并成为服务和参与者生命周期中的一部分。