队列框架方案之消息通知解决方案
项目实施规范
编号:DEMO-NOTIFY-PROJ
版本:1.0
1概述
在进行系统设计时,除了对安全、事务等问题给与足够的重视外,性能也是一个不可避免的问题所在,尤其是一个B/S结构的软件系统,必须充分地考虑访问量、数据流量、服务器负荷的问题。解决性能的瓶颈,除了对硬件系统进行升级外,软件设计的合理性尤为重要。对于一些实时性不是很高的模块我们可以使用了Microsoft Messaging Queue(MSMQ)技术来完成异步处理,利用消息队列临时存放要操作的数据,使得数据访问因为不需要访问数据库从而提高了访问性能,至于队列中的数据,则等待系统空闲的时候再进行处理。【摘自张逸】
消息通知系统就是利用MSMQ技术、window服务实现一系列的通知服务。
1.1 目的
n 为消息通知提供解决方案
n 本文档的编写也为下阶段的设计、开发提供依据,为项目组成员对需求的详尽理解,以及在开发开发过程中的协同工作提供强有力的保证。同时本文档也作为项目评审验收的依据之一。
1.2 范围
消息通知解决方案中包括 消息的发送、接受模块;消息的处理模块;消息通知模块等三大模块。
1.3 参考资料
l C#高级编程 Wrox[消息队列,window服务]
l PetShop数据访问层之消息处理
l 敏捷软件开发
l 重构-改善既有代码的设计
l HeadFirst设计模式
1.4 术语定义
用户:是指技术部人员而非客户或软件的最终购买者。
消息:是由通信的双方所需要传递的信息,它可以是各式各样的媒体,如文本、声音、图象等等。我们这里仅仅指MSMQ的Message
消息体:指专门自己定义的一个超类MessageBase。自定义消息体这样做的好处是,一是相当于对数据进行了简单的加密,二则采用自己定义的格式可以节省通信的传递量
namespace DEMO.Notify.Server
{
[Serializable]
public class MessageBase
{
/// <summary>
/// 消息类型
/// </summary>
private string messagetype;
public string MessageType
{
get { return messagetype; }
set { messagetype = value; }
}
/// <summary>
/// 消息内容
/// </summary>
private string messagebody;
public string MessageBody
{
get { return messagebody; }
set { messagebody = value; }
}
}
}
1.5 概述
用户利用本系统提供的接口向消息队列发送消息。Window服务通过轮询监视消息队列的数据,并取出消息,根据消息类型通过工厂创建具体的消息处理器Processor处理消息,然后把消息通过消息投递器Poster发送指定的买家、卖家、客服等。
2 UML图
2.1 Sequence Diagram
2.2 class Diagram
3 接口设计
3.1 消息体MessageBase重构
3.1.1 重构过程
自定义消息体的好处很多,比如采用自己定义的格式相当于对数据进行了简单的加密,采用自己定义的格式可以节省通信的传递量等等。
因为我们的系统是为处理订单通知服务的,所以消息体的大概内容应该是订单的类型[订单生成、订单支付成功等],消息内容更确切的讲应为构造发送内容必须的参数[目前一个OrderId就够了]。
其消息体的代码如下:
Serializable]
public class
{
/// <summary>
///消息类型
/// </summary>
private string
public string
{
get { return
set { messagetype = value; }
}
/// <summary>
///消息内容
/// </summary>
private string
public string
{
get { return
set { messagebody = value; }
}
}
说明:1:消息体必须可序列化也就是有Serializable特性
2:消息体尽量为轻量级内容
3.1.2 UML图
3.2 客户端组件重构.
3.2.1 重构过程
这个和PetShop的消息发送、接收是一样滴,就不多说了。
3.2.2 UML图
3.3 Section节点重构
3.3.1 重构过程
先看看app.config里的一段Section代码
<?xml versinotallow="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<!--消息队列路径-->
<section name="MSMQClient" type="System.Configuration.SingleTagSectionHandler"/>
<!--log4net 错误日志-->
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
<!--消息通知系统配置节点-->
<section name="Notify" type="Demo.Notify.Configuration.NotifySection,Demo.Notify"/>
</configSections>
<connectionStrings />
<MSMQClient MessageQueuePath=".\Private$\demoECMessageQueue"></MSMQClient>
<appSettings>
<add key="BatchSize" value="10" />
<add key="assembly" value="Demo.Notify"/>
<add key="OrderDuration" value="2"/>
</appSettings>
<log4net>
<root>
<level value="DEBUG" />
<appender-ref ref="RollingLogFileAppender" />
</root>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="File" value="D:\log\" />
<param name="AppendToFile" value="true" />
<param name="MaxSizeRollBackups" value="10" />
<param name="StaticLogFileName" value="false" />
<param name="DatePattern" value="yyyyMMdd".log"" />
<param name="RollingStyle" value="Date" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
</log4net>
<Notify>
<ConnectionStrings>
<add name="" constr=""></add>
</ConnectionStrings>
<ClientMails>
<add name="client1" email="yinguang@staff.demo.com.cn"></add>
</ClientMails>
<MessagePoster>
<MailPoster type="MailSenderType" domain="smtp.163.com" port="25" userid="" password="" from="" fromname="demo.notify" replayto="yimart@163.com"></MailPoster>
</MessagePoster>
<MessageProcessors>
<add name="OrderCreateMsgProcessor" type="Demo.Notify.Processor.OrderCreateMsgProcessor,Demo.Notify" ></add>
</MessageProcessors>
<MessageTypes>
<add name="OrderCreatedSuccess" processor="OrderCreateMsgProcessor" Poster="EmailPoster,IMPoster,syslogPoster">
<!--这里可以添加个性属性-->
<Parameter>
</Parameter>
</add>
</MessageTypes>
</Notify>
</configuration
上面的Section节点是该系统的纽带!没有它,系统将无法运行,发送器、处理器也成了独立的羔羊。既然很重要,那我们也就重点说说这个流程吧。
l Notify继承ConfigurationSection它里面有ConnectionStrings、ClientMails、MessageTypes、MessageProcessors、MessageSenders等ConfigurationProperty。
l ConnectionStrings继承ConfigurationElementCollection它保存了不同数据源的 connectionstring.
l ClientMails继承 ConfigurationElementCollection, 它保存了常用的客服email。
l MessageSenders 是个ConfigurationElement , 里面配置Poster需要的信息。比如MailSender保存了Email的domain,userid,pwd,from等相关信息。
l MessageProcessor 是个ConfigurationElementCollection, 里面的每个ConfigurationElement保存了每个处理器和对应的Asssembly。这样就可以通过下面的代码创建对应的处理器。
TypeType.GetType(processName);
AssemblyAssembly.GetAssembly(type);
Object obj = a.CreateInstance(processName);
returnas IProcessor;
l MessageTypes 保存了每个消息类型[OrderCreate,OrderPaySucceed等] 的个性特征。我们的系统就是通过MessageTypes里的每个MessageType的Processor去MessageProcessor找对应的处理器的。
1:name:它就是消息体MessageBase的MessageType属性,当系统从msmq里取到消息后,就会根据name去MessageTypes找和MessageType匹配对应的条目
: 当从MessageTypes里找到对应的条目后,就会通过processor去MessageProcessor去找对应的处理器。
:Poster:它是对应了每个Messagetype[一个MessageType对应一个MessageProcessor]所需要的发送器Poster。多个Poster用逗号隔开。这样我们就可以配置发送器了。
:Parameter:是增加的特性列表。其中Consumer,merchant,Client对应了买家、卖家、客服的内容模板,
guestEmail对应客户的email。
3.3.2 UML图
....
.
3.4 发送器Poster的重构
3.4.1 重构过程
商城的订单状态行为会产生通知,通知的方式目前有Email,sys,IM,他们三个有个共同的方法那就是Post投递方法,我们可以把Email,sys,IM当成三个Poster类,并且都有Post的方法。 代码如下:
publicclass
{
public voidparam1)
{
//TODO:Process order and Post
}
}
publicclass
{
public voidparam2)
{
//TODO:Process order and post
}
}
publicclass
{
public voidparam3)
{
//TODO:Process order and post
}
}
我们细看上面的代码,按照一般的重构常理应该提出一个基类或接口了。这里用接口更合适些,抽取出来的接口如下:
publicinterface ImsgPoster
{
void post(SenderInfo
}
备注:SenderInfo是一个超类,用来保存要格式化内容时需要的参数
最后完整的代码如下:
publicclass
{
public void
{
//TODO:Process order
}
}
publicclass
{
public void
{
//TODO:Process order
}
}
publicclass
{
public void
{
//TODO:Process order
}
}
public interface
{
void
}
publicclass
{}
3.4.2 UML图
3.5 处理器重构
3.5.1 重构过程
假设我们把处理每个订单状态[订单生成、订单支付]的程序叫处理器Processor,每个订单状态都有个处理器,比如订单生成、订单支付对应的处理器为OrderCreateProcessor, OrderPaySucceedProcessor,每个处理器都有Process方法,该方法用来生成买家、卖家、客服等发送对象的内容,并调用Process把通知投递出去。通过上述讨论,代码大概如下:
publicclass
{
public void
{
//TODO:Poster
}
private SenderInfo ConsumerEditor()
{
//TODO:构造买家SenderInfo
}
private SenderInfo MerchantEditor()
{
//TODO:构造卖家SenderInfo
}
private SenderInfo ClientEditor()
{
//TODO:构造客服SenderInfo
}
}
OrderPaySucceedProcessor.cs的代码内容和上述代码没有什么区别。我们也应该抽象出一个基类或者接口。因为这里是’is a’的关系,而并非’behave like’的关系,并且抽象出来的目的是为了有共有的功能,而非约定、合同。所以我们选择了抽象类。该抽象类UML图如下:
IProcessor 抽象类有4个属性 ,ClientTemplate,MerchantTemplate,ConsumerTemplate代表了客服、商家、买家的内容模板,Msg是Message类型,它保存了从消息队列取的消息条目信息;AddPoster方法向发送器列表里添加了一个发送器,AddSender向发送对象列表里添加了一个SenderInfo[这个SenderInfo是派生类中ConsumerEditor, MerchantEditor, ClientEditor方法构造出来的.],IntialProcessor用来初始化处理器Processor。IProcessor完整的代码如下:
publicabstract class
{
private
#region templates variables
//Processor通过模板生成不同发送对象的内容
private string _consumertemplate;//买家模板
private string _merchanttemplate;//卖家模板
private string _clienttemplate;//客服模板
#endregion
private List<SenderInfo> senderlist = new List<SenderInfo>();//发送对象列表
private List<ImsgPoster> posterlist = new List<ImsgPoster>();//发送器列表
#region template properties
public string
{
get { return
set { _clienttemplate = value; }
}
public string
{
get { return
set { _merchanttemplate = value; }
}
public string
{
get { return
set { _consumertemplate = value; }
}
#endregion
/// <summary>
///初始化Poster[发送器],Template[模板文件]
/// </summary>
protected void
{
"Notify");
//获取消息类型
string
msgType = ((MessageBase)msg.Body).MessageType.ToString();
//获取发送器列表,以逗号隔开!
string
string[] posters = poster.Split(new char[] { ','
foreach (string pst in
{
AddPoster(DataAccess.CreateMsgPoster(pst));
}
"Merchant"].ParamValue.ToString();
"Consumer"].ParamValue.ToString();
"Client"].ParamValue.ToString();
}
/// <summary>
///消息
/// </summary>
public
{
get { return
set { msg = value; }
}
/// <summary>
///添加SenderInfo(发送对象)
/// </summary>
/// <param name="sender">发送对象</param>
protected void
{
senderlist.Add(sender);
}
/// <summary>
///添加发送器
/// </summary>
/// <param name="poster">发送器:Email,Msn,Qq,Yahoo,Sms</param>
public void
{
posterlist.Add(poster);
}
/// <summary>
///这个是最关键的地方,用virutal实现多态,从而减少发送器和发送对象的耦合性!~..~
/// </summary>
public virtual void
{
if (senderlist != null)
{
for (int
{
//senderlist[i];
foreach (ImsgPoster poster in
{
poster.post(senderlist[i]);
}
}
}
}
}
现在OrderCreateMsgProcessor类代码如下:
publicclass
{
private
{ }
private
{ }
private
{}
/// <summary>
///实现父类的Process 方法。
/// </summary>
public override void
{
//initial processor
base.InitialProcessor();
//添加买家到链表中~。。~
SenderInfo sender;
sender = ECConsumerEditor();
if (sender != null)
{
base.AddSender(sender);
}
//添加商家到链表中·。。
sender = MerchantEditor();
if (sender != null)
{
base.AddSender(sender);
}
//添加客服到链表中~。。~
sender = ClientEditor();
if (sender != null)
{
base.AddSender(sender);
}
//邮递员发送到各个地方!
base.Process();
}
}
在IProcessor的InitialProcessor里有个NotifySection对象,这个对象将在3.3的配置节点重构章节里说明。
3.5.2 UML图
4 实例演示下载
demo.notify.rar