WCF技术内幕之面向服务

快速定义面向服务

    简单地说,面向服务是一种分布式应用组件通过消息和契约实现松耦合的架构风格。面向服务的应用是通过契约描述它们交互中使用的消息。这些契约必须使用一种语言描述。并且它的格式能够被其他应用简单地理解,因此可以减少组件实现带来的依赖性。

理解消息

    在面向服务的应用中,消息是通信的基本单位。因此,面向服务的应用通常被称为消息应用系统。在某一时刻,每个面向服务的应用系统都会发送或接收消息。同样,一个面向服务的消息可以被计算机发送,发送给另一个计算机,并且可能由另外的计算机来投递。这些与面向服务消息交互的实体称为消息参与者。

消息参与者

    当描述消息参与者的时候,把它们对应到消息发送过程的角色是非常有用的。通常来说,有三种类型的消息参与者:初始发送者、最终接收者和中介者。

    让我们考虑一个真实的商业场景——Contoso回飞棒公司的订单系统。基本上,客户在网上订购回飞棒,为了处理和完成订单流程,网站产生一个消息发送到其他的系统,如图1-1所示。


图1-1 Contoso回飞棒公司的消息流


        Contoso订单处理系统至少有两个消息参与者:网站是消息发送者,内部系统是消息接收者。可能还有一个负载均衡路由负责转发消息到合适的系统。如图1-2所示,可以认为路由就是中介者。


图1-2 带消息路由的Contoso回飞棒公司的消息流

        初始发送者

            发送者是一个发起通信的实体。

            订单处理系统乍一看,网站可能就是消息的最初发送者;可是从内部系统的角度看,也许不是这样。事实上消息的最初发送者是相对的。相对,是指最初的消息发送者可以随赋予消息的上下文环境改变而改变。在例子里,可以画出任意一个包围两个或多个消息参与者的边界,并且改变消息的最初发送者。

        中介者

            中介者对发送者是不可见的,并且处于发送者和接收者中间。

        最终接收者

            最终接收者是消息期望到达的目标

消息剖析

    由于SOAP的灵活性,目前SO消息的消息都是SOAP消息。SOAP它的核心是一个基于XML的消息结构。SOAP定义了3个主要的可以定义任意XML消息的XML元素:信封、消息主体和消息头。以下是个简单的SOAP消息例子:

<?xml version='1.0' ?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header>...</env:Header>
<env:Body>...</env:Body>
</env:Envelope>

    消息信封

        消息信封包含消息体和消息头。所有的SOAP消息都有一个消息信封作为根元素。

    消息头

        消息头是可以选择的,如果出现,就必须是消息信封encelope标签下的第一个元素。SOAP消息头由一个或多个消息头块组成。SOAP消息头块包含可以被最终接收者和中介者使用的信息。最典型的就是这些消息头块包含描述消息体数据的信息。换句话说,安全、关联或消息上下文信息都可以放到消息头部分里。如果需要特定的消息行为,消息头块是必须的。

    消息体

        消息体是必须的,它包含消息的有效数据。不能保证中介者不打开和改变SOAP消息体。使用数字签名和加密可能能保证消息从初始发送者到最终接收 者的完整性。

消息传输

    SOAP消息是独立于传输的。换句话说,没必要在消息中添加任何传输规范。这个简单的特性是许多关键特性之一,它使得这个消息结构无比强大。

消息编码

    某些情况下,基于文本的XML数据大小限制了它的使用,特别是当要通过网络发送一个XML消息的时候。

    XML Infoset

        XML规范要求定义格式良好,XML文档必须包含一个开始和结束元素、一个根节点等。奇怪的是,XML规范发布以后,激起了抽象定义XML文档的需求。XML Infoset(定义在http://www.w3.org/TR/xml-infoset/)提供了这个抽象定义。

        实际上,XML Infoset定义是条目之间的关系,而不是定义任何具体的语法,这种设计为以后使用新的、更加高效的编码机制提供了扩展性。WCF提供了三种编码器:文本编码器、二进制编码器和MTOM(消息传输优化机制)编码器。

    选择恰当的编码

        消息编码器强迫人们去考虑当前和将来的消息使用问题。绝大多数情况下,应用互操作性和消息中的数据类型就决定了我们的选择。表1-1列举了消息编码、可以发送的消息类型与可以接收这种消息的系统之间的对应关系。

表1-1 消息编码器的适用场景
消息类型BinaryTextMTOM
Text内容,只与WCF交互123
Text内容,与现代非WCF系统交互N/A12
Text内容,与旧的非WCFsystems交互N/A1N/A
大量的二进制内容,只与WCF交互132
大量的二进制内容,与现代非WCF系统交互N/A21
大量的二进制内容,与旧的非WCFsystems交互N/A1N/A
少量的二进制内容,只与WCF交互123
少量的二进制内容,与现代非WCF系统交互N/A12
少量的二进制内容,与旧的非WCFsystems交互N/A1N/A

标记消息地址

<?xml version='1.0' ?>
<Envelope>
<Header>
	<!--识别消息-->
	<MessageIdentifier>1</MessageIdentifier>
	<!--指定最终接收者-->
	<SendTo>http://wintelloct.com/OrderService</SendTo>
	<!--指定操作-->
	<Op>http://wintelloct.com/OrderSerrvice/ArchiveMessage</Op>
	<!--谁在监听消息-->
	<Reply>http://someotherurl.com/OrderReplyService</Reply>
	<!--指定初始发送者-->
	<SendFrom>http://wintelloct.com/SendService</SendFrom>
	<!--指定错误发送者-->
	<OnError>http://wintelloct.com/ErrorService</OnError>
</Header>
<Body>...</Body>
</Envelope>

WS-Addressing

    WS-Addressing规范定义了传输层中常见的两种结构。目的就是实现传输中立。

    终结点引用

        到目前为止,已经使用初始发送者、消息中介者和最终接收者去描述消息交换过程中不同的实体。简单来说,一个服务终结点就是一个消息发送的目标。正如WS-Addressing规范中定义的一样,服务终结点引用就是一种描述服务终结点的方式。

        这些逻辑上的属性都已经在XML Infoset元素中定义了。一些属性,比如Reference Properties、Reference Parameters和Policy可以包含XML元素的信息。以下例子演示的就是在XML中如何表示这些属性。

<wsa:EndpointReference xmlns:wsa="http://schemas.xmlSOAP...">
<wsa:Address>...<wsa:Address>
<wsa:ReferenceProperties>...</wsa:ReferenceProperties>
<wsa:ReferenceParameters>...</wsa:ReferenceParameters>
<wsa:PortType>...</wsa:PortType>
<wsa:ServiceName>...</wsa:ServiceName>
<wsp:Policy>...</wsp:Policy>
</wsa:EndpointReference>

    消息头块

        WS-Addressing同样定义了一些标准的SOAP消息头块,可以用来标注消息地址。以下代码块包含一些消息信息头块和WS-Addressing规范定义的数据类型。

<wsa:MessageID>xs:anyURI</wsa:MessageID> 

<wsa:RelatesTo RelationshipType="..."?>xs:anyURI<wsa:RelatesTo>
<wsa:To>xs:anyURI</wsa:To>
<wsa:Action>xs:anyURI</wsa:Action>
<wsa:From>endpoint-referrence</wsa:From>
<wsa:ReplyTo>endpoint-referrence</wsa:ReplyTo>
<wsa:FaultTo>endpoint-referrence</wsa:FaultTo>

    消息头块依赖

        特定消息头块的信息依赖于其他消息中消息头块的内容。例如,ReplyTo表示回发消息的MessageID,这就可以解释为什么必须有MessageID了,表1-2描述了这种标准想头块的依赖性。

表1-2 消息头块的依赖性
Header#Header NameMin OccursMax OccursDepends On
1wsa:MessageID01N/A
2wsa:RelatesTo0UnboundedN/A
3wsa:ReolyTo011
4wsa:From01N/A
5wsa:FaultTo011
6wsa:To11N/A
7wsa:Action11N/A

面向服务的4个原则

    边界清晰

        在面向服务里,服务可以通过消息与每个其他的服务交互。服务可以穿越边界发送消息给其他服务。服务可以发送和接收消息,也能被发送和接收的消息形状定义服务边界。这些边界被良好的定义,清晰地表示,并且是唯一的服务功能访问点。

    服务自治

        服务是可以控制生命周期的,能控制可用性和另外服务的边界。

    契约共享

        因为面向服务关注参与者之间传递的消息,所以必须有一个方式可以描述这些消息以及成功的消息交换需要什么条件。从广义上讲,这些描述称为契约。面向服务的系统用XSD和WSDL来表述契约。更确切点说,Schema用来描述消息结构,WSDL用来描述消息终结点。这些基于XML的契约表示发送和接收的消息结构、终结点地址、网络协议、安全需求等。本质上,一个消息发送者需要依赖于契约而不是服务本身。    

    基于策略的兼容性

        服务必须能够描述其他服务与之交互的底层环境。

概念汇总

    接下来看看这些概念是如何在WCF系统里工作的。在我们的例子里,将构建一个简单的接收客户订单的订单处理服务。

    很显然,在面向服务系统开发首先应该创建契约。为了让例子简单,一个订单包含三个字段:产品ID、数量和状态消息。订单处理服务里,消息发送者和接收者统一使用WS-Addressing规范的SOAP消息来限制消息的结构。

    

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
<s:Header>
	<wsa:Action s:mustUnderstand="1">urn:SubmitOrder</wsa:Action>
	<wsa:MessageID>4</MessageID>
	<wsa:ReplyTo>
		<wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
	</wsa:ReplyTo>
	<wsa:To s:mustUnderstand="1">http://localhost:8000/Order</wsa:To>
</s:Header>
<s:Body>
	<Order>
		<ProdID>6</ProdID>
		<Qty>6</Qty>
		<Status>order placed</Status>
	</Order>
</s:Body>
</s:Envelope>

其实,我们无须亲自来做这些工作。基本上,C#描述的契约都可以根据需要转化为基于XSD和WSDL的契约。当使用C#表示契约时,可以选择定义一个类或接口。

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;

[ServiceContract(Namespace ="http://wintellec.com/ProcessOrder")]
public interface IProcessOrder
{
    [OperationContract(Action ="urn:SubmitOrder")]
    void SubmitOrder(Message order);
}

现在有了自己的契约,就开始建立一个接收者程序。第一个商业订单一个构建一个实现定义好的契约接口。

/// <summary>
/// 实现程序集定义的接口
/// </summary>
public sealed class MyService : IProcessOrder
{
    public void SubmitOrder(Message order)
    {
        //根据MessageID创建文件名
        string fileName = "Order" + order.Headers.MessageId.ToString() + ".xml";
        //提示消息到达
        Console.WriteLine("Message ID {0} received", order.Headers.MessageId.ToString());
        //创建一个XmlDictionaryWriter区写文件
        XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(new FileStream(fileName, FileMode.Create));
        //写消息到文件里
        order.WriteMessage(writer);


        writer.Close();
    }
}
下一个任务就是让MyService类型去接收内部消息。

    

    static void ServiceOpen()
    {
        //定义服务绑定
        WSHttpBinding binding = new WSHttpBinding(SecurityMode.None);
        //使用文本编码
        binding.MessageEncoding = WSMessageEncoding.Text;
        //定义服务地址
        Uri addressURI = new Uri("http://localhost:4000/Order");
        //使用MyService实例化服务宿舍
        ServiceHost svc = new ServiceHost(typeof(MyService));
        //给服务增加一个终结点
        svc.AddServiceEndpoint(typeof(IProcessOrder), binding, addressURI);
        //打开服务宿主开始侦听
        svc.Open();

        Console.WriteLine("The receiver is ready");
        Console.ReadLine();
    }
下面就是消息发送应用程序
 static void Main(string[] args)
    {
        //开启监听服务
        ServiceOpen();


        //接收程序的地址
        EndpointAddress address = new EndpointAddress("http://localhost:4000/Order");
        //定义一个通信的服务
        //在这个例子里,可以使用WS的HTTP绑定
        WSHttpBinding binding = new WSHttpBinding(SecurityMode.None);
        binding.MessageEncoding = WSMessageEncoding.Text;
        //创建通道
        ChannelFactory<IProcessOrder> channel = new ChannelFactory<IProcessOrder>(binding, address);
        //使用通道工厂创建代理
        IProcessOrder proxy = channel.CreateChannel();
        //创建一些消息
        Message msg = null;
        for(int i=0;i<10;i++)
        {
            //调用方法创建消息
            //注意使用在IProcessOrder契约里定义的Action
            msg= GenerateMessage(i, i);

            //SOAP消息头里添加MessageID
            UniqueId uniqueId = new UniqueId(i.ToString());
            msg.Headers.MessageId = uniqueId;

            Console.WriteLine("Sending Message # {0}",uniqueId.ToString());

            //SOAP消息头里添加Action
            msg.Headers.Action = "urn:SubmitOrder";
            //发送消息
            proxy.SubmitOrder(msg);
        }
    }
    /// <summary>
    /// 创建消息的方法
    /// </summary>
    /// <param name="productID"></param>
    /// <param name="qty"></param>
    /// <returns></returns>
    private static Message GenerateMessage(int productID,int qty)
    {
        MemoryStream stream = new MemoryStream();
        XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(stream, Encoding.UTF8, false);
        writer.WriteStartElement("Order");
        writer.WriteElementString("ProdID", productID.ToString());
        writer.WriteElementString("Qty", qty.ToString());
        writer.WriteEndElement();
        writer.Flush();
        stream.Position = 0;
        XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(stream, XmlDictionaryReaderQuotas.Max);
        return Message.CreateMessage(MessageVersion.Soap12WSAddressing10, string.Empty, reader);
    }

运行程序,检查服务写过的文件,会看到如下代码:

<?xml version="1.0" encoding="utf-8"?>

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">urn:SubmitOrder</a:Action>
    <a:MessageID>0</a:MessageID>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
    <a:To s:mustUnderstand="1">http://localhost:4000/Order</a:To>
  </s:Header>
  <s:Body>
    <Order>
      <ProdID>0</ProdID>
      <Qty>0</Qty>
    </Order>
  </s:Body>
</s:Envelope>

消息头应该和我们在WS-Addressing规范里看到的一样,有些奇怪,并且它们的值应该与在消息发送应用里设置的属性一样。事实上,System.ServiceModel.Message类型展示出了一个叫做Headers属性,这个属性可以表示WS-Addressing的消息头。


为什么要面向服务?

    回答很简单:可伸缩性、维护性、互操作性和灵活性。过去,分布式组件技术如COM紧紧地把所有的组件绑定在一起。最低限度上,这些分布式技术必须分享公共类型系统,并且通常是一个运行时。有这些依赖,软件升级变得十分复杂、费时费力。面向服务的应用系统恰恰相反,不需要依赖相同类型,就会展示出更适合企业计算需求的行为特征。    

    版本升级

        应用系统需求会随着时间的变化而变化。在面向服务的应用系统里,消息发送者和接收者之间唯一协定就是契约。只要保持契约不变,两者都可以根据自己的期望自由改变自己的实现。

    负载均衡

        每个应用都有一些瓶颈,而且这些瓶颈有时候阻止一个应用系统为了增加吞吐量而进行的扩展工作。面向服务的应用程序可以更容易扩展应用系统需要,降低了总成本,而且简化了配置管理工作。

          

 扩展一个面向组件的应用系统 

  

扩展一个面向服务的应用系统

    平台一直在变

        随着时间的推移,平台变化非常快。面向服务使平台使用了独立的XML语法来表示消息契约,这个契约把发送者和接收者解耦。发送者接收遵守契约产生和发送消息,而接收者接收接收和处理消息。不需要序列化平台规范到消息里,所以终结点可以不受关注的平台版本更新影响。

   基于内容的路由

        路由的信息可以放置到消息头里,终结点可以根据信息头来判断消息路径。

    端到端的安全

        使用标准的XML安全机制通过面向服务的消息来提供端到端的安全。即使消息被持久到日志文件或破解攻击,如果消息使用标准的XML安全机制加密,消息里的数据仍然是可以保证安全的。

    互操作性

        面向服务的应用系统是与平台无关的。与平台无关是XML语法表述消息契约的普遍本性。发送消息给一个终结点而不知道它所运行的平台,这完全是可能的。

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值