不为"事务"而"事务"

类别:.NET开发 点击:0 评论:0 推荐:

本问首发于:不为"事务"而"事务"
背景:
    最近在做一个项目,需要用到两个第三方组件:北京莲塘语音组件和CMailSever
前者作为语音聊天室的二次开发组件,后者用于网站的小型邮件系统二次开发组件

需求:
    用户在主程序登陆后,无须再次登陆聊天室和邮件系统,即实现一次登陆,全站通行

下面将给出对应方案以及其对事务处理的依赖程度,并给出解决方案

方案:
    用户在注册时,同时向聊天室和邮件系统注册一个用户,在用户修改密码时对应修改
聊天室和邮件系统,这样当用户进入聊天室和邮件系统,主系统只需要向其传递用户和密码
即可满足需求

该方案存在的问题及其解决方案:
    在用户注册或者修改密码信息时,如果有一个聊天室和邮件系统出现错误,那么用户
将有可能不能登陆其中一个系统,这就提出一个原子事务的问题,为注册和修改密码加上
原子事务处理,C#伪代码如下:

using System.EnterpriseServices ;
[Transaction(TransactionOption.Required)]
public class Users : System.EnterpriseServices.ServicedComponent
{
    [AutoComplete]
    public static void ModifyPassword(string userName , string olePassword , string newPassword)
    {
         ModifyMainSystemPassword(userName,newPassword) ; //修改主程序数据库密码
         ModifyVoiceChatPassword(userName,newPassword) ;  //修改聊天室密码
         ModifyMailSystemPassword(userName,newPassword) ; //修改邮件系统密码
    }
}

这样就可以实现当有一个系统修改密码出错时都会自动回滚到原来的状态了,OK,问题解决了!

进一步思考:
但事情却没有想象的简单,使用以上代码必须存在一个前提条件:聊天室和邮件系统组件必须支持事务性,
这样才能在应用程序之间共享事务,达到事务提交/回滚的目的

思路暂时中断.......

在CSDN论坛得到思归的帮助:如何对非事务性组件实现事务处理
即使用所谓的资源补偿器(CRMs),下面简要说说实现的步骤
对于第1,2,4步可以在SDK中搜索System.EnterpriseServices.CompensatingResourceManager命名空间得到更
详细的信息,或则参见Compensating Resource Managers (CRMs)

这里贴出代码是为了实现的完整性,第3步是我本人扩充的,SDK无此描述

1,实现一个CRM类

using namespace#region using namespace
using System;
using System.IO;
using System.Reflection;
using System.EnterpriseServices;
using System.EnterpriseServices.CompensatingResourceManager;

[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationCrmEnabled]
[assembly: AssemblyKeyFile("crm.key")]
#endregion

namespace CrmServer 
{
实现代码#region 实现代码
      [Transaction]
      // Create a Worker class.
      public class CRMWorker:ServicedComponent
      {
            public void CRMMethod(string fileName, bool bCommit)
            {
                  // Create clerk object.
                  Clerk clerk = new Clerk(typeof(CRMCompensator), "CRMCompensator", CompensatorOptions.AllPhases);
                  clerk.WriteLogRecord(fileName);
                  clerk.ForceLog();
                  if (bCommit)
                        ContextUtil.SetComplete();
                  else
                        ContextUtil.SetAbort();
            }
      
      }
      // Create class derived from Compensator class.
      public class CRMCompensator:Compensator
      {
            bool bBeginPrepareCalled = false;
            bool bPrepareRecordCalled = false;
            bool bBeginCommitCalled = false;
            bool bCommitRecordCalled = false;
            bool bBeginAbortCalled = false;
            bool bAbortRecordCalled = false;
      
            String _fileName;
      
            public override void BeginPrepare()
            {
                  bBeginPrepareCalled = true;
            }
      
            public override bool PrepareRecord(LogRecord rec)
            {
                  Object o = rec.Record;
                  _fileName = o.ToString();
                  bPrepareRecordCalled = true;
                  return false;
            }
      
            public override bool EndPrepare()
            {
                  if (!bBeginPrepareCalled)
                  {return false;}   
                  if (!bPrepareRecordCalled)
                  {return false;}   
                  if (_fileName==null)
                  {return false;}
                  // This is a Prepare Phase success.
                  return true;
            }
      
            public override void BeginCommit(bool fRecovery)
            {
                  bBeginCommitCalled = true;
            }
      
            public override bool CommitRecord(LogRecord rec)
            {
                  bCommitRecordCalled = true;
                  return true;
            }
      
            public override void EndCommit()
            {
                  if (!bBeginCommitCalled)
                  {return;}   
                  if (!bCommitRecordCalled)
                  {return;}
                  if (_fileName==null)
                  {return;}
                  // This is a Commit Phase success.
            }
      
            public override void BeginAbort(bool fRecovery)
            {
                  bBeginAbortCalled = true;
            }
      
            public override bool AbortRecord(LogRecord rec)
            {
                  bAbortRecordCalled = true;
                  Object o = rec.Record;
                  _fileName = o.ToString();
                  return true;
            }
      
            public override void EndAbort()
            {
                  if (!bBeginAbortCalled)
                  {return;}   
                  if (!bAbortRecordCalled)
                  {return;}               
                  if (_fileName==null)
                  {return;}
                  // This is an Abort Phase success.
            }
      
      }
#endregion
}


2,生成强名,编译成托管dll
[C#]
sn –k crm.key
csc /t:library /r:System.EnterpriseServices.dll crm.cs

3,使用命令行把托管dll注册为com+服务
RegSvcs /:appname : CrmServer CrmServer.dll

4,在应用程序中调用com+服务
[示例代码]

using namespace#region using namespace
using System;
using System.IO;
using System.EnterpriseServices;
using CrmServer;
using System.Runtime.InteropServices;
#endregion
class CRM
{
      public static int Main()
      {           
            string logfilename = "crm.log";
            Console.WriteLine("Creating a managed CRM worker object");
            CRMWorker crmworker = new CRMWorker();
            Console.WriteLine("Demonstrating a worker commit");
            crmworker.CRMMethod(logfilename, true);
            Console.WriteLine("Demonstrating a worker abort");
            crmworker.CRMMethod(logfilename, false);
            Console.WriteLine("DONE!");
            return 0;   
      }

哇哈!终于可以交一份满意的差了!

准备在项目上实现上面的方案了,但是快乐之后仍需理智的思考,这样真的可以实现我们的要求吗 ?
这个方案至少有以下问题:
1,需要在服务器注册com+服务,部署不方便 ;
2,代码结构变复杂了
3,如果我们的web应用程序需要通过mono部署在linux系统呢 ?
......
晕到!别挑刺儿了......

不满是前进的唯一动力,会有更好的解决方案吗 ?我不断问我自己
终于工夫不负有心人,终于想到一个“完美的方案”:
1,分解模块,把聊天室和邮件系统和主系统独立出来
2,在用户注册时,所有用户的聊天室和邮件系统密码都是一样的,这个密码信息存储在配置文件或数据库中
这样当用户进入聊天室和邮件系统传递这个密码过去就OK了
3,用户修改密码时,不需要修改聊天室和邮件系统密码,因为它们永远是一样的
这样做的话,就剩下一个问题,如果注册的时候出错怎么办 ?下面给出两种解决方案:
1,在用户每次登陆前,都重新注册一次[不怎么好]
2,当用户注册时忽略错误,在后台提供一个给管理员手工添加用户信息到聊天室和邮件系统的功能,当用户
出现问题时联系管理员处理[比较好]

众里寻他千百度,蓦然回首,那人却在灯火阑珊处

自得其乐,板砖不断......

本文地址:http://com.8s8s.com/it/it42543.htm