服务、终结点与契约
《Programming WCF Services》翻译笔记之一
《Programming WCF Services》的第1章介绍了WCF的基本知识。
在WCF中,最重要的概念自然是服务(Service)。书中对服务的定义为“服务是暴露在外的一组功能的集合”。虽然服务大部分是以契约接口的形 式存在,而对于服务的定义通常也与“面向接口编程”的思想近似,但更重要的是我们必须理解服务的含义。传统的编程思想,在理解业务逻辑的时候,我们可以将 业务逻辑封装为对象,该对象提供了与业务相关的功能;而WCF编程却更多的是考虑如何提供业务服务,以及服务的消费者——客户端。
因此,面向服务编程思想中,调用业务的角色发生了根本的变化。以酒店预订业务为例,如果是传统的面向对象编程,在编写业务用例时,调用酒店预订对象的角色为Customer,如图所示:
如果是WCF服务编程,首先就要考虑服务的提供者与消费者。同样对于酒店预订业务而言,服务的提供者会定义和发布酒店预订服务,而客户端则完成服务的调用,如图所示:
WCF 最关键的是“允许客户端跨越执行边界与服务进行通信。在同一台机器中,客户端可以调用同一个应用程序域中的服务,也可以在同一进程中跨应用程序域调用,甚 至跨进程调用。” 客户端还可以跨机器访问服务,甚至“跨越Intranet或Internet的边界与服务交互。”
与传统的分布式技术如 DCOM和.NET Remoting不同,不管服务对象是本地的还是远程的,WCF都提供了完全一致的编程模型,也就是通过代理的方式完成。这样做的好处在于:“它使得开发 者不会因为服务位置的改变影响客户端,同时还大大地简化了应用程序的编程模型”。
在WCF中,要访问WCF服务,必须知道服务的地址(Address)。书中简单介绍了几种服务地址:TCP地址、HTTP地址、IPC地址、MSMQ地址、对等网地址。在定义服务时,都需要指定基地址。基地址的格式为:
[传输协议]://[机器名或域名][:可选端口]
书中列出了几个常见的地址:
http://localhost:8001
http://localhost:8001/MyService
net.tcp://localhost:8002/MyService
net.pipe://localhost/MyPipe
net.msmq://localhost/private/MyService
net.msmq://localhost/MyService
其中,前两个地址为HTTP地址,第三个为TCP地址,第四个为IPC地址,使用了命名管道,最后两个为MSMQ地址。
在定义了基地址后,还可以为每个服务指定URI。此时,服务的地址为基地址加上指定的URI。关于地址的描述,会在第二章详细介绍。
第 1章简单介绍了契约(Contract)。“WCF的所有服务均暴露为契约(Contract)。契约是与平台无关的,是描述服务功能的标准方式。” WCF定义了四种类型的契约:服务契约(Service Contract)、数据契约(Data Contract)、错误契约(Fault Contract)以及消息契约(Message Contract)。
本书会在第2章和第3章分别介绍服务契约和数据契约,第6章介绍错误契约。至于消息契约,则不在本书范围之内。
定义服务契约的特性为ServiceContract,它既可以应用到接口上,也可以应用到服务类上。最佳实践则是将特性应用到接口上,如:
interface IMyContract
{
[OperationContract]
string MyMethod(string text);
//Will not be part of the contract
string MyOtherMethod(string text);
}
要将服务类型的成员定义在服务契约中,必须将ServiceContract和OperationContract特性结合起来。定义服务契约时,应着眼于 服务的提供,而不是业务的实现。特别是在服务契约的概念里,是否属于服务契约,与对象的访问限定没有必然的联系。例如在机票预订服务中包含了两个方 法:Subscribe()与Settle()。对于业务对象而言,这两个方法无疑都需要暴露为公有方法。但对于服务定义而言,服务的消费者仅需要关心 Subscribe()方法而已。此时,机票预订服务接口的定义就应该如下:
public interface ISubscribeTicket
{
[OperationContract]
bool Subscribe(Customer customer);
void Settle(double money);
}
此时暴露在服务契约中的方法只有Subscribe()方法。
一个单独的类通过继承或实现多个应用了ServiceContract特性的接口,实现对多个契约的支持。
书中特别申明:“实现(服务)时必须避免使用有参构造函数,因为WCF只能使用默认的构造函数。”
服务的名字与命名空间可以通过ServiceContract特性的Name和Namespace属性来指定。如果未指定,则默认的命名空间为http://tempuri.org,默认的服务名为接口名(或服务类名,如果ServiceContract特性直接应用到类上)。
在第1章中,最有价值的内容应该是本章的图1-4,如下所示:
图中给WCF用户展示了如何根据不同的场景选择合适的绑定。书中描述如下:
“首先需要叩问自己服务是否需要与非WCF的客户端交互。如果是,同时客户端又是旧的MSMQ客户端,选择 MsmqIntegrationBinding绑定就能够使得服务通过MSMQ与该客户端实现互操作。如果服务需要与非WCF客户端交互,并且该客户端期 望调用基本的Web服务协议(ASMX Web服务),那么选择BasicHttpBinding绑定就能够模拟ASMX Web服务(即WSI-Basic Profile)公开WCF服务。缺点是我们无法使用大多数最新的WS-*协议的优势。但是,如果非WCF客户端能够识别这些标准,就应该选择其中一种 WS绑定,例如WSHttpBinding、WSFederationBinding或者WSDualHttpBinding。如果假定客户端为WCF客 户端,同时需要支持脱机或断开状态下的交互,则可以选择NetMsmqBinding使用MSMQ传输消息。如果客户端需要联机通信,但是需要跨机器边界 调用,则应该选择NetTcpBinding通过TCP协议进行通信。如果相同机器上的客户端同时又是服务,选择NetNamePipeBinding使 用命名管道可以使性能达到最优化。如果基于额外的标准,例如回调(选择WSDualHttpBinding)或者联邦安全(选择 WSFederationBinding),则应对选择的绑定进行微调。”
“即使超出了使用的目标场景,大多数绑定工作仍然良好。例如,我们可以使用TCP绑定实现相同机器甚至进程内的通信;我们也可以使用基本绑定实现Intranet中WCF对WCF的通信。然而,我们还是应尽量按照图1-4选择绑定。”
第1章作为WCF基础的整体介绍,不可避免要介绍WCF的体系架构,书中的描述如下:
“WCF提供了对可靠性、事务性、并发管理、安全性以 及实例**等技术的有力支持,它们均依赖于基于拦截机制的WCF体系架构。通过代理与客户端的交互意味着WCF总是处于服务与客户端之间,拦截所有的调 用,执行调用前和调用后的处理。当代理将调用栈帧(Stack Frame)序列化到消息中,并将消息通过通道链向下传递时,WCF就开始执行拦截。通道相当于一个***,目的在于执行一个特定的任务。每个客户端通道 都会执行消息的调用前处理。链的组成与结构主要依赖于绑定。例如,一个通道对消息编码(二进制格式、文本格式或者MTOM),另一个通道传递安全的调用上 下文;还有一个通道传播客户端的事务,一个通道管理可靠会话,另一个通道对消息正文(Message Body)加密(如果进行了配置),诸如此类。客户端的最后一个通道是传输通道,根据配置的传输方式发送消息给宿主。”
“在宿主端,消息同样通过通道链进行传输,它会对消息执行宿主端的调用前处理。宿主端的第一个通道是传输通道,接收传输过来的消息。随后的通道执行 不同的任务,例如消息正文的解密、消息的解码、参与传播事务、设置安全准则、管理会话、**服务实例。宿主端的最后一个通道负责将消息传递给分发器 (Dispatcher)。分发器将消息转换到一个栈帧,并调用服务实例。执行顺序如图1-11所示。”
书中的图1-11如下所示:
“服务并不知道它是否被本地客户端调用。事实上,服务会被本地客户端——分发器调用。客户端与服务端的***确保了它们能够获得运行时环境,以便于 它们执行正确的操作。服务实例会执行调用,然后将控制权(Control)返回给分发器。分发器负责将返回值以及错误信息(如果存在)转换为一条返回消 息。分发器获得控制权,执行的过程则刚好相反:分发器通过宿主端通道传递消息,执行调用后的处理,例如管理事务、停用实例、回复消息的编码与加密等。为了 执行客户端调用后的处理,包括解密、解码、提交或取消事务等任务,传输通道会将返回消息发送到客户端通道。最后一个通道将消息传递给代理。代理将返回消息 转化到栈帧,然后将控制权返回给客户端。”
“特别值得注意的是,体系架构中的所有要点均与可扩展**息相关。我们可以为专有交互定制通道,为实例管理定制行为,以及定制安全行为等。事实上,WCF提供的标准功能都能够通过相同的可扩展模式实现。”
通过WCF实现SOA的一个关键问题是,如何将与技术无关的服务转换为CLR。一旦提供了这种转换,WCF的服务设计者与开发者就可以根据自己拥有 的CLR的知识,进行WCF面向服务开发。为两者搭建桥梁的是宿主。一个宿主可以包含多个上下文,而在上下文中则可以包含服务实例。特殊的,宿主的上下文 也可以为空。如下图所示:
客户端若要调用服务,可以采用代理或通道的方式。这两种方式在很多资料上已经有了详尽的介绍。在这里,我不准备重复。如果需要详细了解甚至掌握着两 种方式,当然可以购买Programming WCF Services一书一探究竟。这里我想介绍的是本书中频繁使用的一种简化WCF类库的一个示例,主要是引入泛型来简化以及各种步骤地自动化处理。例如 ServiceHost<T>类,就是通过引入泛型简化了WCF提供的ServiceHost类:
{
public ServiceHost( ) : base(typeof(T))
{}
public ServiceHost(params string[] baseAddresses) :
base(typeof(T),Convert(baseAddresses))
{}
public ServiceHost(params Uri[] baseAddresses) :
base(typeof(T),baseAddresses)
{}
static Uri[] Convert(string[] baseAddresses)
{
Converter<string,Uri> convert = delegate(string address)
{
return new Uri(address);
};
return Array.ConvertAll(baseAddresses,convert);
}
}
如果是使用ServiceHost类,则托管服务的代码如下所示:
{
Uri baseAddress = new Uri("http://localhost:8000/");
ServiceHost host = new ServiceHost(typeof(MyService),baseAddress);
host.Open( );
//Can do blocking calls:
Application.Run(new MyForm( ));
host.Close( );
}
使用ServiceHos<T>t类,则可修改为:
{
Uri baseAddress = new Uri("http://localhost:8000/");
ServiceHost<MyService> host = new ServiceHost<MyService>(baseAddress);
host.Open( );
//Can do blocking calls:
Application.Run(new MyForm( ));
host.Close( );
}
在ServiceHost<T>类的实现中,使用了.NET 2.0中才引入的一个方法Array.ConvertAll(),它的方法签名为:
TInput[] array,
Converter<TInput,TOutput> converter
)
它能够根据Converter对象将一种类型的数组转换为另一种类型的数组。
本书中,还使用了很多.NET的高级特性,特别是一些在.NET 2.0才引入的技术。所以作者在本书的前言中写到:“本书大量使用了.NET 2.0技术,从某种角度来说,本书也可以算是一本高级的C#技术书籍。”
转载于:https://blog.51cto.com/wayfarer/280119