一、One-way MEP V.S. Responsible Service
我们知道MSMQ天生就具有异步的特性,它只能以One-way的MEP(Message Exchange Pattern)进行通信。Client和Service之间采用One-way MEP的话就意味着Client调用Service之后立即返回,它无法获得Service的执行结果,也无法捕捉Service运行的Exception。下图简单表述了基于MSMQ的WCF Service中Client和Service的交互。
但是在有些场景 中,这是无法容忍的。再拿我在上一篇文章的Order Delivery的例子来说。Client向Service提交了Order,却无法确认该Order是否被Service正确处理,这显然是不能接受的。我们今天就来讨论一下,如何创建一个Responsive Service来解决这个问题:Client不再是对Service的执行情况一无所知,它可以获知Order是否被Service正确处理了。
二、Solution
虽然我们的目的很简单:当Client向Service递交了Order之后,能以某种方式获知Order的执行结果;对于Service端来说,在正确把Order从Message Queue中获取出来、并正确处理之后,能够向Order的递交者发送一个Acknowledge Message。为了简单起见,这个Acknowledge Message包含两组信息:
要在WCF中实现这样的目的,对于Request/Reply MEP来说是简单而直接的:Client向Service递交Order,并等待Service的Response,Service在处理接收到Order之后直接将处理结果 返回给Client就可以了。但是我们说过MSMQ天生就是异步的,我们只有采取一种间接的方式实现“曲线救国”。
我们的解决方案是:在每个Client Domain也创建一个基于MSMQ的本地的WCF Service,用于接收来自Order处理端发送的Acknowledge Message。对于处理Order 的Service来说,在正确处理Order之后,想对应的Client发送Acknowledge Message。下图简单演示整个过程:
三、Implementation
了解了上面的Solution之后,我们来看看该Solution在真正实现过程中有什么样的困难。对于处理Order的Service来说,在向Client端发送Acknowledge Message的时候,它必须要知道该Order对应的Client的Response Service的MSMQ的Address以及其他和Operation相关的Context信息(在这里我们不需要,不过考虑到扩展性,我们把包括了address的Context的信息 封装到一个了Class中,在这里叫做:OrderResponseContext)。而这些Context却不能在Configuration中进行配置,因为他可以同时面临着很多个Client:比如每个Client用于接收Response 的Message Queue的address都不一样。所以这个OrderResponseContext必须通过对应的Client来提供。基于此,我们具有两面两种解决方式:
方式一、修改Service Contract,把OrderResponseContext当成是Operation的一个参数
这是我们最容易想到的,比如我们原来的Operation这样定义:
namespaceArtech.ResponsiveQueuedService.Contract
{
[ServiceContract]
[ServiceKnownType(typeof(Order))]
publicinterfaceIOrderProcessor
{
[OperationContract(IsOneWay=true)]
voidSubmit(Orderorder);
}
}
现在变成:
namespaceArtech.ResponsiveQueuedService.Contract
{
[ServiceContract]
[ServiceKnownType(typeof(Order))]
publicinterfaceIOrderProcessor
{
[OperationContract(IsOneWay=true)]
voidSubmit(Orderorder,OrderResponseContextresponseContext);
}
}
虽然这种方式看起来不错,但是却不值得推荐。在一般情况下,我们的Contract需要是很稳定的,一经确定就不能轻易更改,因为Contract是被交互的多方共同支持的,牵一发动全身;此外,从Service Contract代表的是Service的一个Interface,他是对业务逻辑的抽象、和具体实现无关,而对于我们的例子来说,我们仅仅是定义一个递交Order的Operation,从业务逻辑来看,OrderResponseContext和抽象的业务逻辑毫无关系。基于此,我们需要寻求一种和Service Contract无关的解决方式:
方式二、将OrderResponseContext放到Soap Message 的Header中
其实我们要解决的问题很简单,就是要把OrderResponseContext的信息置于Soap Message中发送到Service。而我们知道,Soap的Header具有极强的可伸缩性,原则上,我们可以把任何控制信息置于Header中。基于WCF的编程模式很容易地帮助我们实现对Soap Header的插入和获取:
我们可以通过下面的方式获得当前Operation Context的Incoming Message Headers和Outgoing Message Headers
OperationContext.Current.IncomingMessageHeaders
OperationContext.Current.OutgoingMessageHeaders
如果我们要把一个OrderResponseContext 对象插入到当前Operation Context的Outgoing Message Headers中,我们可以通过下面的代码来实现:
OrderResponseContextcontext=newOrderResponseContext();
MessageHeader<OrderResponseContext>header=newMessageHeader<OrderResponseContext>(context);
OperationContext.Current.OutgoingMessageHeaders.Add(header.GetUntypedHeader("name","namespace"));
相应的,我们可以通过下面的代码从Outgoing Message Headers OrderResponseContext的数据获取的内容:
OrderResponseContextcontext=OperationContext.Current.IncomingMessageHeaders.GetHeader<OrderResponseContext>("name","namespace"));
四、Sample
我们照例给出一个完整的Sample,下面是整个Solution的结构:
除了一贯使用的4层结构(Contract-Service-Hosting-Client),还为ResponseService增加了下面两层:
1.Contract: Artech.ResponsiveQueuedService.Contract
Service Contract: Artech.ResponsiveQueuedService.Contract. IOrderProcessor
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.ServiceModel;
namespaceArtech.ResponsiveQueuedService.Contract
{
[ServiceContract]
[ServiceKnownType(typeof(Order))]
publicinterfaceIOrderProcessor
{
[OperationContract(IsOneWay=true)]
voidSubmit(Orderorder);
}
}
Service Contract: Artech.ResponsiveQueuedService.Contract.IOrderRessponse
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.ServiceModel;
namespaceArtech.ResponsiveQueuedService.Contract
{
[ServiceContract]
publicinterfaceIOrderRessponse
{
[OperationContract(IsOneWay=true)]
voidSubmitOrderResponse(GuidorderNo,FaultExceptionexception);
}
}
接收来自Order processing端的Response:Order No.和Exception。
Data Contract: Artech.ResponsiveQueuedService.Contract.Order
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.Runtime.Serialization;
namespaceArtech.ResponsiveQueuedService.Contract
{
[DataContract]
publicclassOrder
{
PrivateFields#regionPrivateFields
privateGuid_orderNo;
privateDateTime_orderDate;
privateGuid_supplierID;
privatestring_supplierName;
#endregion
Constructors#regionConstructors
publicOrder(GuidorderNo,DateTimeorderDate,GuidsupplierID,stringsupplierName)
{
this._orderNo=orderNo;
this._orderDate=orderDate;
this._supplierID=supplierID;
this._supplierName=supplierName;
}
#endregion
PublicProperties#regionPublicProperties
[DataMember]
publicGuidOrderNo
{
get{return_orderNo;}
set{_orderNo=value;}
}
[DataMember]
publicDateTimeOrderDate
{
get{return_orderDate;}
set{_orderDate=value;}
}
[DataMember]
publicGuidSupplierID
{
get{return_supplierID;}
set{_supplierID=value;}
}
[DataMember]
publicstringSupplierName
{
get{return_supplierName;}
set{_supplierName=value;}
}
#endregion
PublicMethods#regionPublicMethods
publicoverridestringToString()
{
stringdescription=string.Format("OrderNo.\t:{0}\n\tOrderDate\t:{1}\n\tSupplierNo.\t:{2}\n\tSupplierName\t:{3}",
this._orderNo,this._orderDate.ToString("yyyy/MM/dd"),this._supplierID,this._supplierName);
returndescription;
}
#endregion
}
}
对Order的封装。
Data Contract:Artech.ResponsiveQueuedService.Contract. OrderResponseContext
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.Runtime.Serialization;
usingSystem.ServiceModel;
namespaceArtech.ResponsiveQueuedService.Contract
{
[DataContract]
publicclassOrderResponseContext
{
privateUri_responseAddress;
[DataMember]
publicUriResponseAddress
{
get{return_responseAddress;}
set{_responseAddress=value;}
}
publicstaticOrderResponseContextCurrent
{
get
{
if(OperationContext.Current==null)
{
returnnull;
}
returnOperationContext.Current.IncomingMessageHeaders.GetHeader<OrderResponseContext>("OrderResponseContext","Artech.ResponsiveQueuedService.Contract");
}
set
{
MessageHeader<OrderResponseContext>header=newMessageHeader<OrderResponseContext>(value);
OperationContext.Current.OutgoingMessageHeaders.Add(header.GetUntypedHeader("OrderResponseContext","Artech.ResponsiveQueuedService.Contract"));
}
}
}
}
ResponseAddress代表Host在Client Domain的Response Service的Address。同过Current把OrderResponseContext插入到Outgoing Message Headers中、以及从Ingoing Message Headers取出OrderResponseContext对象。
2.Order Processing Service:Artech.ResponsiveQueuedService.Service
相关推荐
我的WCF之旅- 创建一个简单的WCF程序 - Artech WCF入门之选绝佳的例子 代源源于:《WCF全面解析 上》 编程工具:VS2010 语言:C# blog 《IIS站点中部署 WCF项目》
我的WCF之旅源代码_创建一个简单的WCF程序
WCF之旅:一个简单的WCF程序(vs2010源码) 文章 + 源码 入门首选文章,折腾了好久才折腾出第一个wcf程序。 对准备学习wcf的人员绝对有意义
我的WCF之旅(1)配套代码,IIS寄宿 泛型 序列化
在学习WCF之旅的时候自己写得一些代码,不同的版本展示了逐渐深入的过程,有文字说明,很经典。
Apress Pro WCF 4 Practical Microsoft SOA Implementation, 2nd Part I: Introducing Windows Communication Foundation ■Chapter 1: WCF and SOA Basics ...■Chapter 13: Implementing SOA Interoperability
Artech的博客文章,我把它转成chm的格式给大家分享
WCF分布式开发步步为赢(13)WCF服务离线操作与消息队列MSMQ[参照].pdf
我把WCF之旅制作了个电子书,为WCF做点贡献吧
我的WCF之旅 , 主要以讲解例子为重点
WCF 学习笔记 WCF 杂项 WCF Security WCF Transaction WCF MSMQ __
WCF之旅 1. 创建一个简单的WCF程序 3.在WCF中实现双向通信(Bi-directional Communication) 5. 通过WCF Extension实现Localization ......
摘自网友博客 非常不错的学习WCF的文章
新手开发 图文详细介绍WCFService的开发过程及客户端调用
我的WCF之旅后续篇,呀,要大于20字符啊,废话一下
博客园的一个实例的源代码(我的WCF之旅(1):创建一个简单的WCF程序 ) http://www.cnblogs.com/artech/archive/2007/02/26/656901.html 由于上面没有代码,我本人按上面的说明自己创建了一份源码用于测试学习,...
1:创建第一个WCF服务 2:使用IIS发布WCF服务 3:自运行WCF服务 4:使用Windows服务发布WCF服务 5:创建WCF客户端程序
前面已经叙述,WCF是对于ASMX,.Net Remoting,Enterprise Service,WSE,MSMQ等技术的整合。由于WCF完全是由托管代码编写,因此开发WCF的应用程序与开发其它的.Net应用程序没有太大的区别,我们仍然可以像创建面向...
wcfservice示例wcfservice示例wcfservice示例wcfservice示例wcfservice示例