代码改变世界

WCF 填写消息地址

2011-07-26 11:31  田志良  阅读(...)  评论(... 编辑 收藏

填写消息地址

  现在我们已经看过了与消息交互的实体,详细剖析了消息结构,然后看了一下WCF提供了几个消息编码器,现在我们来看一下如何在详细发送的时候表示我们要发送的目的地。毕竟,除非能发送给接受者,否则消息等于是毫无用处。和邮政服务需要一个良好格式的地址结构一样,面向服务的消息同样也需要一个定义良好的地址结构。这节里,我们将会建立自己的地址结构(Scheme),看它可以不可以广泛适用于所有的消息应用系统,然后把它关联到那个和WCF消息一起使用的地址结构上。

在传输里标记地址 VS 在消息里标记地址 

  面向服务的消息直接在消息里指定最终接受者。这是一个微不足道但是非常重要的问题。如果消息目标在消息里指定,一个完整的息模消式集合成为可能。当我们直接把地址插入到消息时,我们在为更高效的消息处理做准备。效率可以指代许多东西,在这个意义上,与创建消息的速度相反,我正在讲的是实现一个更先进的消息发送行为的容易性。和写个信封地址需要花费时间一样,序列化地址到消息里也需要耗费时间。无论如何,写地址到信封上会改善邮政服务的效率,序列化地到消址息里改善处理效率,特别当更高级的消息发送行为要实现(像消息路由和中介者)。

指定最终接受者

  那什么类型条目应该放到地址里呢?对于发起者,地址应该可以标识消息的最终接受者。因为最终接受者可能托管多个服务,所以我们应该有一个方式可以唯一地区别最终接受者上的服务。一个地址元素也能够描述托管服务的最终接受者和服务本身。看一下下面的例子:

http://wintellect.com/OrderService

  Internet时期,我们知道,地址由最终接受者的地址和我们可以使用的访问协议组成。我们知道SOAP消息包含3种类型的元素:信封、包含多个消息块的消息头和消息体。消息信封不是一个好的选择,因为信封元素只能出现一次。选择只有消息头和消息体可以作为候选了。所以消息体怎么样?从我们早期的讨论,我们知道消息体只是给最终的消息接受者使用的。经过排除过程以后,消息头里可以给我们提供包含地址的逻辑空间。这样消息头块看起来会是什么样子?这样如何:

<Envelope>    
<Header>        
    <To>http://wintellect.com/OrderService</To>    
</Header>    
<Body> …</Body> 
</Envelope>

  更高层次上,这个简单的XML结构达到了我们标识最终接受者和我们要发送的服务的目的。

指定初始发送者

  在消息里加入发送者信息也许有用,就像信件上的退信地址。增加发送者信息有2个目的:为最终接受者指出发送者,为中介者指出发送者。我们已经看到URL可以用来鉴别消息。所以事实上我们可以使用相同的结构来标识发送者。例子如下:

<Envelope> 
<Header> 
    <To>http://wintellect.com/ReceiveService</To> 
    <From>http://wintellect.com/SendService</From> 
</Header> 
<Body> …</Body> 
</Envelope>

  给SOAP增加简单的元素来支持消息的来源,这个可以既可以被中介者也可以被最终接受者使用。

指定错误发送地址

  如有出了问题会如何处理消息?每个现代的计算平台都有一些方法去指示错误和异常信息。这些消息处理机制使得我们的系统更加强壮、可预测和容易调试。很自然就想到我们的消息应用系统也应该有这样的机制。考虑到我们的消息已经包含了<To> 和 <From>,我们可以发送所有的错误通知给<From>元素定义的地址。为了错误处理,如果我们想让错误通知发送到一个特定的地址特别需要保存什么呢?这个例子,我们必须创建另外一个元素:

<Envelope> 
<Header>        
    <To>http://wintellect.com/OrderService</To>        
    <From>http://wintellect.com/SendService</From>        
    <Error>http://wintellect.com/ErrorService</Error> 
</Header> 
<Body> …</Body>
</Envelope>

  增加<Error>元素到消息头里可以清楚地指出消息发送希望错误发送的地址。因为URL在消息头里,它可以被消息接受者和中介者使用。

鉴别消息

  我们简单的地址结构需要发送者在消息里增加我们的To、From和Error信息作为消息头块,然后发送消息给最终接受者。当中介者或者最终接受者处理消息的时候,可能会发生错误。这个错误与初始发送的消息完全不同。从初始发送者的角度看,接受错误消息意味着自己的问题来了,但是特别是我们不知道的发送消息导致的错误的时候。如果我们能把初始消息和错误消息关联,对于调试、故障分析和审计来说就太棒了。为了实现这个,在消息里我们需要两个元素:消息标识元素和消息关联元素。让先我们看一下消息标识:

<Envelope> 
<Header> 
    <MessageID></MessageID> 
    <To>http://wintellect.com/OrderService</To> 
    <From>http://wintellect.com/SendService</From> 
    <Error>http://wintellect.com/ErrorService</Error> 
</Header> 
<Body></Body> 
</Envelope>

  这个例子里,我们把消息标识元素称作<MessageID>。现在,我们可以认为MessageID的值是一个全球唯一的数(例如GUID)。在产生上,这个数字对于其他参与者没有任何意义。如果初始发送者产生了一个如前面描述的消息,所有的中介者和最终接受者都知道错误消息发送到哪里,但是它们可以使用MessageID去找到导致这个错误的特定消息。如果错误消息的接受者和初始发送者不同,这些处理必须在它们之间交换信息区完全了解导致错误的消息。

关联消息

  如果我们假设一个中介者或者最终接受者处理消息的时候产生了错误,那么接下来就应该有一个新的消息被发送给错误元素指定的地址。如果一个中介者或者最终接受者发送了一个完全的新的消息,中介者或者最终接受者就会成为新消息的发送者。同样地,错误消息头块里的地址下载成为了新消息的最终接受者。我们刚确定了导致错误的初始消息会包含一个MessageID元素。不知道为什么,错误消息需要包含一个引用MessageID的元素。初始消息和错误消息的关联可以通过RelatesTo元素描述:

<Envelope> 
<Header> 
    <MessageID></MessageID> 
    <RelatesTo></RelatesTo> 
    <To>http://www.wintelelct.com/ErrorService</To> 
    <From>http://wintellect.com/OrderService</From> 
    <Error>http://wintellect.com/ErrorService</Error> 
</Header> 
<Body> …</Body> 
</Envelope>

  这个在http://wintellect.com/ErrorService的错误服务时最重的消息接受者。当这个错误服务读取消息,导致错误的消息的信息包含在RelatesTo元素里。虽然错误服务也许不对RelatesTo做任何事情,它可以被用作调试、故障分析和审计。注意懂啊这个例子里的To、 From和Error元素已经改变去反映消息的新的上下文。

谁在监听响应?

  让我们先不讨论错误消息,回到初始消息。正如你看到的,我们有一个方式去指定消息的最终接受者、初始发送者的地址、一个唯一标识和错误通知发送的地址。同样当处理初始消息的时候我们也想增加一个返回消息的地址也是可行的。现实世界里这种例子很多。比如,发货单都有一个“回信到此”地址来区分初始的发送地址。我们的SO消息需要一个类似的结构。我们能多使用一次地址的概念与一个新的元素绑定来描述这个信息。在下面的例子里我们称这个新的元素为ReplyTo:

<Envelope> 
<Header> 
    <MessageID></MessageID> 
    <To>http://wintellect.com/OrderService</To> 
    <ReplyTo>http://wintellect.com/OrderReplyService</ReplyTo> 
    <From>http://wintellect.com/SendService</From> 
    <Error>http://wintellect.com/ErrorService</Error> 
</Header> 
<Body> …</Body> 
</Envelope> 

  或许在相同的消息里有From 和ReplyTo两个元素,看起来有点重复。重要的是记住,From 和ReplyTo两个元素或许表示相同的服务,但是它们也可以描述不同的服务。增加ReplyTo元素能够简单地为我们创建的消息头块增加更多的灵活性和功能。

指定一个操作

  如果你希望发送邮件给一个大公司,你也许不需要特地指定一个部门。你可以发送邮件给Contoso Boomerang Corporation并且希望一些人打开这个邮件,猜一下谁会打开这个邮件,然后转发给推断的接受者。明显这个过程和我们指定准确的部门或者小组地址相比将花费更多的时间。Contoso Boomerang Corporation可能包括几个可以收到这个信件的组。每个组或许有自己的一套处理流程。比如,Contoso或许有一个组负责签订新客户,另外一个组负责客户支持,再有另外一个组服务新产品开发。在抽象层上,地址可以为目标指定不懂级别的粒度,每个目标或许有自己的一套处理流程。

  目前为止,我们已经创建了一个元素来定义最终接受者,一个reply-to接受者,一个错误提醒的接受者,一个消息标识,一个关联机制,初始发送者。我们还没有定义一个消息的操作。我们假设,现在我们可以使用另外一个URL来标识一个动作(action)或者操作(operation)。下面的例子阐述了这个假设:

<Envelope> 
<Header> 
    <MessageID></MessageID> 
    <To>http://wintellect.com/OrderService</To> 
    <Action>urn:ProcessOrder/Action> 
    <ReplyTo>http://wintellect.com/OrderReplyService</ReplyTo> 
    <From>http://wintellect.com/SendService</From> 
    <Error>http://wintellect.com/ErrorService</Error> 
</Header> 
<Body> …</Body> 
</Envelope>

  在这个例子里,Action元素表示ProcessMsg操作应该在此消息上执行。OrderService也可能定义了另外的操作。例如,通过下面的Action元素我们可以发送另外一个消息去存档消息操作:

<Envelope> 
<Header> 
    <MessageID></MessageID> 
    <To>http://wintellect.com/OrderService</To> 
    <Action>urn:ArchiveMessage</Action> 
    <ReplyTo>http://someotherurl.com/OrderReplyService</ReplyTo> 
    <From>http://wintellect.com/SendService</From> 
    <Error>http://wintellect.com/ErrorService</Error> 
</Header> 
<Body>    </Body> 
</Envelope>

标准消息头块的需求

  我们已经粗略地定义了7个可以帮助我们标记地址的元素。我们可以假设我们的元素名称可以被普遍接受。我们能建立理解这些元素的基础架构并且在我们每个消息参与者里使用这个基础架构。换句换说,我们不能发送这些消息到一个不知道我们7个元素意义的应用系统里。同样地,我们的应用系统不能够接受包含不同地址头的消息。例如,另外一个厂商或许已经定义了下面这样的消息头:

<Envelope> 
<Header>        
    <MessageIdentifier>1</MessageIdentifier>        
    <SendTo>http://wintellect.com/OrderService</SendTo>        
    <Op>http://wintellect.com/OrderService/ArchiveMessage</Op>        
    <Reply>http://someotherurl.com/OrderReplyService</Reply>        
    <SentFrom>http://wintellect.com/SendService</SentFrom>        
    <OnError>http://wintellect.com/ErrorService</OnError> 
</Header> 
<Body></Body>
</Envelope>

  包含我们的基础架构的应用系统不能够处理这个消息。

  如果我们打算调查一下大部分企业应用,我们会看到软件厂商已经遵守这个模型定义了他们自己消息。过了几年,SOAP已经成为大家认可的消息格式,但是还没有就消息头块出现在消息里达成共识,结果,应用系统部能轻易地互操作。真SOAP消息的互操作性需要一个可以通行于各个厂商的消息头块。如第一章里提到的,WS-*规范通过定义一个公共的消息头经过漫长的努力解决这个问题。