.NET可逆框架设计
前段时间一直在学习和研究.NET事务处理,慢慢的我发现可以使用事务处理来实现一种可逆的系统框架。这种框架在一些IT社区似乎还没有见过,但是在我们日常开发中确实有这个需求。所以我花了点时间深入的研究了一下事务的原理和使用,实现了以事务为纽带,以资源为操作对象的可逆框架。
这里我假设您对事务有了整体的认识,也对自定义事务管理器有过了解。[王清培版权所有,转载请给出署名]
(可以参考本人的:.NET简谈事务本质论、.NET简谈自定义事务资源管理器)
1. 什么是可逆的程序框架
什么叫可逆的?程序的执行是可以被无限制回滚的。
什么叫可逆的框架?实现了对可逆功能的封装,并能通过简单的接口调用进行使用。框架可能有大有小,我想这么称呼它是为了表达它的整体性和重要性。
那么到底可逆的需求在哪里?其实在我们开发程序的时候经常会使用事务来进行业务的控制。比如删除订单,然后删除订单明细等等,对于这样的要求很多,我们只能将逻辑控制在一个事务范围内,不能在没有事务性的逻辑代码中编写这种要求的业务功能。等出现未知错误的时候在进行事务的回滚。
你也许会问,使用原来的事务处理不是也能进行回滚吗?当然不是这么简单的,我们使用事务回滚时只能将资源回滚到最初未进行事务处理前的状态。(这里不仅仅指的是数据库事务,而是全局的事务处理) 我们用图做个比较。[王清培版权所有,转载请给出署名]
传统的事务处理图:
可逆的事务处理图:
从这两幅图中我们可以很明显的看出,传统的事务处理在事务处理的过程当中无法控制中间数据,也就是说无法对事务处理进行分段,然后在进行统一的提交或回滚。
在可逆框架的事务处理里我们就可以控制事务的执行阶段,在必要的时候我们只需提交或者回滚某一阶段的数据。
1.1环境事务
在可逆框架的事务处理图中,我们看到事务的开始,然后就进行下一步、下一步这样的操作。在每进行一个下一步操作的时候,就是进入到了一个子事务里处理,在.NET中是可以进行事务的嵌套,其实也就是依赖事务Dependent Transaction实现。通过使用环境事务可以让事务性感知代码能自动的识别出您将要使用事务进行操作。所以在每进行下一步操作的时候,只有将当前环境事务切换为您将依赖的子事务才行。如果只是单纯的使用依赖事务对象实例在使用,那么将无法进行诸多其他的事务处理。
2可逆框架的实现原理
由于我们只能控制自定义事务资源管理器的内部实现,所以我们在构建自己的数据处理时问题变的简单多了。
实现可逆框架的核心技术就是使用依赖事务进行事务的克隆操作。将一个大的事务处理逻辑上切割成多了小的事务操作,然后在进行统一的提交或回滚。
在实现上其实就是将Committable Transaction对象进行包装,实现简单的调用接口。这里参照了环境代码的概念,将对象的生命周期控制在代码片段中。
2.1自定义资源管理器的实现
我们需要扩展IEnlistmentNotification接口的实现,加入对“上一步”、“下一步”的数据操作。
请看代码:
1 /*** 2 * author:深度训练 3 * blog:http://wangqingpei557.blog.51cto.com/ 4 * **/ 5 using System; 6 using System.Collections.Generic; 7 using System.Text; 8 using System.Transactions; 9 10 namespace ReversibleLib 11 { 12 /// <summary> 13 /// 可逆范围内的资源管理器。 14 /// 可以使用该类对易失性资源进行事务范围内的管理。在事务操作范围内进行可逆操作。 15 /// </summary> 16 /// <typeparam name="T">需要管理的资源类型</typeparam> 17 /// <typeparam name="Xcopy">资源在使用、恢复过程中的数据复制对象。</typeparam> 18 public class ReResourceManager<T, Xcopy> : IEnlistmentNotification, IReversibleGetResourceData<T> 19 where T : class, new() 20 where Xcopy : class 21 { 22 /// <summary> 23 /// 私有字段。资源的持久引用。 24 /// </summary> 25 T _commitfrontvalue; 26 /// <summary> 27 /// 私有字段。事务性操作数据对象。 28 /// </summary> 29 T _rollbackfrontvalue = new T(); 30 /// <summary> 31 /// 保存数据复制对象。 32 /// </summary> 33 Xcopy _copy; 34 /// <summary> 35 /// 泛型约束需要,内部使用。 36 /// </summary> 37 public ReResourceManager() { } 38 /// <summary> 39 /// 资源管理器内部名称。便于追踪 40 /// </summary> 41 public string Name { get; set; } 42 /// <summary> 43 /// 重载默认构造函数,使用资源类型和数据复制对象初始化资源管理器。 44 /// </summary> 45 public ReResourceManager(T t, Xcopy icopy) 46 { 47 (icopy as IResourceCopy<T>).Copy(_rollbackfrontvalue, t); 48 _commitfrontvalue = t; 49 _copy = icopy; 50 } 51 52 #region IEnlistmentNotification 成员 53 public void Prepare(PreparingEnlistment preparingEnlistment) 54 { 55 preparingEnlistment.Prepared(); 56 } 57 public void Commit(Enlistment enlistment) 58 { 59 enlistment.Done(); 60 } 61 public void InDoubt(Enlistment enlistment) 62 { 63 enlistment.Done(); 64 } 65 public void Rollback(Enlistment enlistment) 66 { 67 (_copy as IResourceCopy<T>).Copy(_commitfrontvalue, _rollbackfrontvalue);//回滚事务 68 enlistment.Done(); 69 } 70 #endregion 71 72 #region IReversibleGetResourceData<T> 成员 73 T IReversibleGetResourceData<T>.GetPreviousData() 74 { 75 T result = new T(); 76 (_copy as IResourceCopy<T>).Copy(result, _rollbackfrontvalue); 77 return result; 78 } 79 T IReversibleGetResourceData<T>.GetNextData() 80 { 81 T result = new T(); 82 (_copy as IResourceCopy<T>).Copy(result, _commitfrontvalue); 83 return result; 84 } 85 #endregion 86 } 87 }
2.2可逆框架的入口实现
我们需要简单的调用就能方便的使用可逆功能,不能以一种新的方式使用。所以这里借鉴了Transaction Scope的设计思想。
请看代码:
1 /*** 2 * author:深度训练 3 * blog:http://wangqingpei557.blog.51cto.com/ 4 * **/ 5 using System; 6 using System.Collections.Generic; 7 using System.Text; 8 using System.Transactions; 9 10 namespace ReversibleLib 11 { 12 /// <summary> 13 /// 使代码成为可逆框架的事务性代码 14 /// </summary> 15 public class ReversibleManagerScope : IDisposable 16 { 17 /// <summary> 18 /// 初始化ReversibleManagerScope新的实例 19 /// </summary> 20 public ReversibleManagerScope() 21 { 22 ReversibleManager._reversibleManager = new ReversibleManager(); 23 } 24 /// <summary> 25 /// 使用ReversibleManager对象构造ReversibleManagerScope使用范围对象 26 /// </summary> 27 /// <param name="manager">ReversibleManager实例</param> 28 public ReversibleManagerScope(ReversibleManager manager) 29 { 30 ReversibleManager._reversibleManager = manager; 31 } 32 /// <summary> 33 /// 使用自定义资源管理器构造ReversibleManagerScope包装的环境ReversibleManager.Current中的对象实例。 34 /// </summary> 35 /// <param name="source">IEnlistmentNotification资源管理器</param> 36 public ReversibleManagerScope(IEnlistmentNotification source) 37 { 38 ReversibleManager._reversibleManager = new ReversibleManager(source); 39 } 40 /// <summary> 41 /// 全局上下文ReversibleManager对象销毁 42 /// </summary> 43 public void Dispose() 44 { 45 ReversibleManager._reversibleManager = null; 46 } 47 /// <summary> 48 /// 完成整个操作的提交。该操作将提交事务栈中的所有依赖事务 49 /// </summary> 50 public void Completed() 51 { 52 ReversibleManager.Current.Commit(); 53 } 54 } 55 /// <summary> 56 /// 可逆模块的入口。 57 /// ReversibleManager对事务对象的封装,实现阶段性的事务提交和回滚。 58 /// </summary> 59 public class ReversibleManager 60 { 61 #region 上下文静态ReversibleManager实例 62 /// <summary> 63 /// 持有对可逆框架的对象引用 64 /// </summary> 65 internal static ReversibleManager _reversibleManager; 66 /// <summary> 67 /// 获取当前上下文中可逆框架 68 /// </summary> 69 public static ReversibleManager Current 70 { 71 get { return _reversibleManager; } 72 } 73 #endregion 74 75 #region 构造对象 76 /// <summary> 77 /// 默认构造函数 78 /// </summary> 79 public ReversibleManager() { } 80 /// <summary> 81 /// 表示可提交的事务(主事务) 82 /// </summary> 83 private CommittableTransaction _commiTransaction; 84 /// <summary> 85 /// 支持两阶段提交协议的资源管理器(主资源管理器) 86 /// </summary> 87 private IEnlistmentNotification _resourceManager; 88 /// <summary> 89 /// 重载构造函数,使用自定义资源管理器构造可逆模块的开始。 90 /// </summary> 91 /// <param name="resource">IEnlistmentNotification接口对象</param> 92 public ReversibleManager(IEnlistmentNotification resource) 93 { 94 _resourceManager = resource; 95 InitLoad(IsolationLevel.Serializable); 96 } 97 /// <summary> 98 /// 重载构造函数,使用自定义资源管理器、内部事务范围的事务隔离级别构造可逆模型的开始。 99 /// </summary> 100 /// <param name="resource">IEnlistmentNotification接口对象</param> 101 /// <param name="isolationlevel">IsolationLevel枚举成员</param> 102 public ReversibleManager(IEnlistmentNotification resource, IsolationLevel isolationlevel) 103 { 104 _resourceManager = resource; 105 InitLoad(isolationlevel); 106 } 107 /// <summary> 108 /// 事务初始化阶段的参数对象 109 /// </summary> 110 TransactionOptions _options; 111 /// <summary> 112 /// 重载构造函数,使用自定义资源管理器、内部事务范围的事务隔离级别、事务超时时间范围构造可逆模块的开始。 113 /// </summary> 114 /// <param name="resource">IEnlistmentNotification接口对象</param> 115 /// <param name="isolationlevel">IsolationLevel枚举成员</param> 116 /// <param name="span">TimeSpan时间范围</param> 117 public ReversibleManager(IEnlistmentNotification resource, IsolationLevel isolationlevel, TimeSpan span) 118 { 119 _options = new TransactionOptions(); 120 _options.Timeout = span; 121 InitLoad(isolationlevel); 122 } 123 /// <summary> 124 /// 构造CommittableTransaction对象实例。 125 /// </summary> 126 /// <param name="level">事务隔离级别</param> 127 private void InitLoad(IsolationLevel level) 128 { 129 if (_options == null) 130 _options = new TransactionOptions(); 131 _options.IsolationLevel = level; 132 _commiTransaction = new CommittableTransaction(_options); 133 _commiTransaction.EnlistVolatile(_resourceManager, EnlistmentOptions.None); 134 //作为事务栈的头开始整个可逆结构。 135 _tranStack.Push(_commiTransaction);//压入事务栈 136 _resourceStack.Push(_resourceManager);//压入资源栈 137 //设置环境事务,让所有支持事务性感知框架的代码都能执行。 138 Transaction.Current = _commiTransaction; 139 } 140 #endregion 141 142 /// <summary> 143 /// 事务栈,依次存放事务。 144 /// </summary> 145 private System.Collections.Generic.Stack<Transaction> _tranStack = new Stack<Transaction>(); 146 /// <summary> 147 /// 资源栈,依次存放事务使用的资源。 148 /// </summary> 149 private System.Collections.Generic.Stack<IEnlistmentNotification> _resourceStack = new Stack<IEnlistmentNotification>(); 150 /// <summary> 151 /// 阶段性事件委托 152 /// </summary> 153 /// <param name="tran">Transaction环境事务</param> 154 public delegate void PhaseHanlder(System.Transactions.Transaction tran); 155 /// <summary> 156 /// 下一步事件 157 /// </summary> 158 public event PhaseHanlder NextEvent; 159 /// <summary> 160 /// 上一步事件 161 /// </summary> 162 public event PhaseHanlder PreviousEvent; 163 /// <summary> 164 /// 开始下一步操作 165 /// </summary> 166 /// <typeparam name="S">IEnlistmentNotification接口实现</typeparam> 167 /// <param name="level">IsolationLevel事务的隔离级别(对全局事务处理设置)</param> 168 /// <param name="source">下一步操作的自定义数据管理器</param> 169 public void Next<S>(IsolationLevel level, S source) 170 where S : class,IEnlistmentNotification, new() 171 { 172 Transaction tran = _tranStack.Peek();//获取事务栈的顶端事务 173 if (tran == null) 174 tran = Transaction.Current;//主事务 175 DependentTransaction depentran = tran.DependentClone(DependentCloneOption.BlockCommitUntilComplete); 176 //将本次事务处理的资源管理器压入资源栈中 177 depentran.EnlistVolatile(source, EnlistmentOptions.None); 178 _tranStack.Push(depentran); 179 _resourceStack.Push(source); 180 //切换环境事务场景 181 Transaction.Current = depentran; 182 if (NextEvent != null) 183 if (NextEvent.GetInvocationList().Length > 0) 184 NextEvent(Transaction.Current); 185 } 186 /// <summary> 187 /// 返回上一步操作 188 /// </summary> 189 /// <typeparam name="T">需要接受的数据对象类型</typeparam> 190 /// <param name="refadd">需要接受的数据对象引用</param> 191 public void Previous<T>(out T refadd) where T : class,new() 192 { 193 Transaction tran = _tranStack.Pop(); 194 if (tran == null)//顶层事务 195 Transaction.Current.Rollback(); 196 // tran.Rollback();//回滚本事务,将触发所有克隆事务的回滚。 197 if (PreviousEvent != null) 198 if (PreviousEvent.GetInvocationList().Length > 0) 199 { 200 //设置上一步数据对象 201 refadd = (_resourceStack.Pop() as IReversibleGetResourceData<T>).GetPreviousData(); 202 PreviousEvent(Transaction.Current); 203 return; 204 } 205 refadd = new T();//事务处理异常 206 } 207 /// <summary> 208 /// 提交事物堆栈中的所有事物 209 /// </summary> 210 public void Commit() 211 { 212 if (Transaction.Current is DependentTransaction) 213 (Transaction.Current as DependentTransaction).Complete(); 214 for (int i = 0; i < _tranStack.Count - 1; i++) 215 { 216 //依赖事务 217 (_tranStack.Pop() as DependentTransaction).Complete(); 218 } 219 //提交事务,主事务。必须进行克隆主体的提交才能完成所有阶段的操作。 220 (_tranStack.Pop() as CommittableTransaction).Commit(); 221 } 222 /// <summary> 223 /// 回滚事物堆栈中的所有事物 224 /// </summary> 225 public void RollBack() 226 { 227 if (Transaction.Current is DependentTransaction) 228 (Transaction.Current as DependentTransaction).Rollback(); 229 for (int i = 0; i < _tranStack.Count - 1; i++) 230 { 231 //依赖事务 232 (_tranStack.Pop() as DependentTransaction).Rollback(); 233 } 234 //提交事务,主事务。必须进行克隆主体的提交才能完成所有阶段的操作。 235 (_tranStack.Pop() as CommittableTransaction).Rollback(); 236 } 237 } 238 }
3.示例
这里我使用了一个简单的String Builder作为资源管理器需要管理的对象。
请看代码:
1 /*** 2 * author:深度训练 3 * blog:http://wangqingpei557.blog.51cto.com/ 4 * **/ 5 using System; 6 using System.Collections.Generic; 7 using System.Text; 8 using System.Data; 9 using System.Transactions; 10 using ReversibleLib; 11 12 namespace ConsoleApplication1 13 { 14 class Program 15 { 16 static void Main(string[] args) 17 { 18 //构造数据 19 StringBuilder strbuilder = new StringBuilder(); 20 strbuilder.Append("0");//初始数据为0 21 22 //资源管理器 23 ReResourceManager<StringBuilder, StringBuilderCopy> strResource = 24 new ReResourceManager<StringBuilder, StringBuilderCopy>(strbuilder, new StringBuilderCopy()); 25 strResource.Name = "0资源管理器"; 26 //开始进入可逆框架处理环境 27 using (ReversibleManagerScope reversible = new ReversibleManagerScope(strResource)) 28 { 29 try 30 { 31 ReversibleManager.Current.PreviousEvent += new ReversibleManager.PhaseHanlder(Current_PreviousEvent); 32 ReversibleManager.Current.NextEvent += new ReversibleManager.PhaseHanlder(Current_NextEvent); 33 strbuilder.Append("1");//首次修改数据为01 34 35 //获取下一步操作的数据 36 StringBuilder strbuilder2 = (strResource as IReversibleGetResourceData<StringBuilder>).GetNextData(); 37 //构造下一步操作的自定义资源管理器 38 ReResourceManager<StringBuilder, StringBuilderCopy> strResource2 = 39 new ReResourceManager<StringBuilder, StringBuilderCopy>(strbuilder2, new StringBuilderCopy()); 40 strResource2.Name = "2资源管理器"; 41 ReversibleManager.Current.Next<ReResourceManager<StringBuilder, StringBuilderCopy>>( 42 System.Transactions.IsolationLevel.Serializable, strResource2); 43 strbuilder2.Append("2");//第二步修改数据为012 44 45 //返回上一步,也就是回滚对数据进行“2”设置的前一个状态 46 StringBuilder strbuilder3; 47 ReversibleManager.Current.Previous<StringBuilder>(out strbuilder3);//获取上一步使用的数据,这里应该是01 48 49 reversible.Completed();//提交所有操作 50 Console.WriteLine(strbuilder3); 51 } 52 catch (Exception err) 53 { Console.WriteLine(err.Message); ReversibleManager.Current.RollBack(); } 54 } 55 Console.ReadLine(); 56 } 57 58 static void Current_NextEvent(Transaction tran) 59 { 60 Console.WriteLine("下一步:" + tran.TransactionInformation.LocalIdentifier); 61 Console.WriteLine("下一步:" + tran.TransactionInformation.DistributedIdentifier); 62 } 63 static void Current_PreviousEvent(Transaction tran) 64 { 65 Console.WriteLine("上一步:" + tran.TransactionInformation.LocalIdentifier); 66 Console.WriteLine("上一步:" + tran.TransactionInformation.DistributedIdentifier); 67 } 68 } 69 }
这里我使用0作为资源的初始数据,然后进入到第一个环节,我将它附加了1,然后进入到第二个环节,我将它附加了2,这里应该是012了,但是下面我突然又返回到了上一步,所以最后的数据应该是01。如果我们需要使用复杂的数据对象,如常用的Data Table类型,我们一般都是用它来展现一组数据,然后对这组数据进行一系列的操作。
总结:
这篇文章主要是想介绍一下事务的另一种使用方式,对可逆框架的设计方向算是一个抛砖引玉吧,希望大家用的着。[王清培版权所有,转载请给出署名]
源码地址:https://files.cnblogs.com/wangiqngpei557/Reversible.zip