企业应用:一个够用的、通用的状态机(管理实体的业务状态)

来源:互联网 发布:在数组中添加元素 编辑:程序博客网 时间:2024/06/04 18:44
企业应用:一个够用的、通用的状态机(管理实体的业务状态)
 
 
企业应用:一个够用的、通用的状态机(管理实体的业务状态)

目录

背景常见的状态机需求实现状态机一个简单的示例一个相对完善的例子实现代码进一步交代的问题备注

背景返回目录

企业应用下,需要关注三个状态机:

  • 业务相关的状态机。
  • 审批流程相关的状态机。
  • 持久化相关的状态机。

某些企业应用开发人员终其一生就是希望能开发出通用的一个框架以简化这些状态机的开发。本文重点关注:“业务相关的状态机”。

常见的状态机需求返回目录

产品的状态机

单据的状态机

业务相关的状态机的一般性需求如下:

  • 当处于某个状态时,可以执行哪些合法的迁移?迁移的前置条件是什么?
  • 当处于某个状态时,可以执行哪些合法的操作?如:已提交和已审核状态的单据不能被修改。

实现状态机返回目录

我目前使用过两种思路实现这种状态机:

  1. 使用状态模式。这种要求为每种单据的状态管理定义一套状态体系,有点麻烦了。
  2. 使用状态表格。这种就是本文介绍的。

下面先看两个示例。

一个简单的示例返回目录

注意下面的链式配置代码,这些代码表达的意思是:

In(Status.UnSaved).When(Operation.Save).If(CanSave).TransferTo(Status.Saved)

处于 UnSaved 状态下,当 Save 操作发生时,如果 CanSave,就迁移到 Saved 状态。

------------------------------------------------------------------------------------

.In(Status.UnSaved).When(Operation.Edit).Aways().Ok()

处于 UnSaved 状态下,当 Edit 操作发生时,总是,允许的。

代码

复制代码
 1     class Order 2     { 3         private readonly StateMachine<Status, Operation> _stateMachine; 4  5         public Status Status { get; internal set; } 6  7         public Order() 8         { 9             _stateMachine = StateMachine<Status, Operation>10                     .Config(() => this.Status, status => this.Status = status)11                     .In(Status.UnSaved).When(Operation.Save).Aways().TransferTo(Status.Saved)12                     .In(Status.Saved).When(Operation.Submit).Aways().TransferTo(Status.Submitted)13                     .Done();14         }15 16         public void Save()17         {18             _stateMachine.Schedule(Operation.Save);19         }20 21         public void Submit()22         {23             _stateMachine.Schedule(Operation.Submit);24         }25 26         public void Edit()27         {28             _stateMachine.Schedule(Operation.Edit);29         }30     }
复制代码

测试

复制代码
 1     [TestClass] 2     public class StateMachineTest 3     { 4         [TestMethod] 5         public void ValidSave() 6         { 7             var order = new Order { Status = Status.UnSaved }; 8             order.Save(); 9 10             Assert.AreEqual(Status.Saved, order.Status);11         }12 13         [TestMethod]14         [ExpectedException(typeof(StateScheduleException))]15         public void InvalidSubmit()16         {17             var order = new Order { Status = Status.UnSaved };18             order.Submit();19         }20     }
复制代码

一个相对完善的例子返回目录

代码

复制代码
 1 using System; 2 using System.Collections.Generic; 3 using System.Collections.ObjectModel; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7  8 using Happy.Domain; 9 using Happy.StateManager;10 11 namespace Happy.Examples.ManufactureManagement.Domain.Bases12 {13     public abstract class MIAggregateRoot<TItem> : AggregateRoot<Guid>14     {15         protected MIAggregateRoot()16         {17             this.StateMache =18                 StateMachine<Status, Operation>19                 .Config(() => this.Status, status => this.Status = status)20                 .In(Status.UnSaved).When(Operation.Save).If(CanSave).TransferTo(Status.Saved)21                 .In(Status.UnSaved).When(Operation.Edit).Aways().Ok()22                 .In(Status.Saved).When(Operation.Submit).If(this.CanSubmit).TransferTo(Status.Submitted)23                 .In(Status.Saved).When(Operation.Edit).Aways().Ok()24                 .In(Status.Submitted).When(Operation.Verify).If(this.CanVerify).TransferTo(Status.Verified)25                 .Done();26 27             // ReSharper disable DoNotCallOverridableMethodsInConstructor28             this.Items = new Collection<TItem>();29             // ReSharper restore DoNotCallOverridableMethodsInConstructor30         }31 32         protected StateMachine<Status, Operation> StateMache { get; private set; }33 34         protected internal virtual ICollection<TItem> Items { get; protected set; }35 36         internal Status Status { get; set; }37 38         protected virtual bool CanSave()39         {40             return true;41         }42 43         protected virtual bool CanSubmit()44         {45             return true;46         }47 48         protected virtual bool CanVerify()49         {50             return true;51         }52 53         internal void Save()54         {55             this.StateMache.Schedule(Operation.Save);56         }57 58         internal void Submit()59         {60             this.StateMache.Schedule(Operation.Submit);61         }62 63         internal void Verify()64         {65             this.StateMache.Schedule(Operation.Verify);66         }67 68         internal void AddItem(TItem item)69         {70             this.AddItems(new List<TItem> { item });71         }72 73         internal void AddItems(IEnumerable<TItem> items)74         {75             this.StateMache.Schedule(Operation.Edit);76 77             foreach (var item in items)78             {79                 this.Items.Add(item);80             }81         }82 83         internal void DeleteItem(TItem item)84         {85             this.DeleteItems(new List<TItem> { item });86         }87 88         internal void DeleteItems(IEnumerable<TItem> items)89         {90             this.StateMache.Schedule(Operation.Edit);91 92             foreach (var item in items)93             {94                 this.Items.Remove(item);95             }96         }97     }98 }
复制代码

测试

复制代码
 1 using System; 2 using Microsoft.VisualStudio.TestTools.UnitTesting; 3  4 using Happy.StateManager; 5 using Happy.Examples.ManufactureManagement.Domain.Bases; 6 using Happy.Examples.ManufactureManagement.Domain.QualityTests; 7  8 namespace Happy.Examples.ManufactureManagement.Domain.Test.QualityTests 9 {10     [TestClass]11     public class QualityTestTest12     {13         [TestMethod]14         public void TestUnSavedQualityTest()15         {16             var entity = this.MockUnQualityTest(Status.UnSaved);17             entity.AddItem(new QualityTestItem(Guid.NewGuid(), entity.Id));18             entity.Save();19 20             Assert.AreEqual(Status.Saved, entity.Status);21         }22 23         [TestMethod]24         public void TestSavedQualityTest()25         {26             var entity = this.MockUnQualityTest(Status.Saved);27             entity.AddItem(new QualityTestItem(Guid.NewGuid(), entity.Id));28             entity.Submit();29 30             Assert.AreEqual(Status.Submitted, entity.Status);31         }32 33         [TestMethod]34         [ExpectedException(typeof(StateScheduleException))]35         public void TestSubmittedQualityTest()36         {37             var entity = this.MockUnQualityTest(Status.Submitted);38             entity.AddItem(new QualityTestItem(Guid.NewGuid(), entity.Id));39         }40 41         private QualityTest MockUnQualityTest(Status status)42         {43             return new QualityTest44                 {45                     Id = Guid.NewGuid(),46                     Status = status47                 };48         }49     }50 }
复制代码

实现代码返回目录

代码

复制代码
 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6  7 using Happy.ExtentionMethods; 8 using Happy.StateManager.Configuration; 9 10 namespace Happy.StateManager11 {12     /// <summary>13     /// 状态机。14     /// </summary>15     public sealed class StateMachine<TState, TOperation>16     {17         private readonly List<Transition<TState, TOperation>> _transitions = new List<Transition<TState, TOperation>>();18         private readonly Func<TState> _stateGetter;19         private readonly Action<TState> _stateSetter;20 21         /// <summary>22         /// 构造方法。23         /// </summary>24         public StateMachine(Func<TState> stateGetter, Action<TState> stateSetter)25         {26             stateGetter.MustNotNull("stateGetter");27             stateSetter.MustNotNull("stateSetter");28 29             _stateGetter = stateGetter;30             _stateSetter = stateSetter;31         }32 33         /// <summary>34         /// 配置状态机。35         /// </summary>36         public static IConfig<TState, TOperation> Config(Func<TState> stateGetter, Action<TState> stateSetter)37         {38             stateGetter.MustNotNull("stateGetter");39             stateSetter.MustNotNull("stateSetter");40 41             return new Config<TState, TOperation>(stateGetter, stateSetter);42         }43 44         /// <summary>45         /// 配置状态迁移。46         /// </summary>47         public StateMachine<TState, TOperation> ConfigTransition(48             TState sourceState,49             TOperation operation,50             ICondition condition,51             TState targetState)52         {53             sourceState.MustNotNull("sourceState");54             operation.MustNotNull("operation");55             condition.MustNotNull("condition");56             targetState.MustNotNull("targetState");57 58             var transition = new Transition<TState, TOperation>(59                     sourceState,60                     operation,61                     condition,62                     targetState);63 64             _transitions.Add(transition);65 66             return this;67         }68 69         /// <summary>70         /// 使用<paramref name="operation"/>调度状态机。71         /// </summary>72         public void Schedule(TOperation operation)73         {74             operation.MustNotNull("operation");75 76             var currentState = _stateGetter();77             var transition = _transitions78                 .FirstOrDefault(x =>79                     x.SourceState.Equals(currentState)80                     &&81                     x.Operation.Equals(operation)82                     &&83                     x.Condition.IsSatisfied());84 85             if (transition == null)86             {87                 throw new StateScheduleException(currentState, operation);88             }89 90             _stateSetter(transition.TargetState);91         }92     }93 }
复制代码

说明

内部就是一个状态表格,没啥交代的,有兴趣的朋友可以去 http://happy.codeplex.com/SourceControl/latest,找到 Happyframework/Src/Happy.StateManager 下载最新代码看看。

进一步交代的问题返回目录

如果需要在真实的项目中使用这个模式,有两个问题还需要解决:

第一个问题:迁移的前置条件判断和后置操作的执行如果需要更多的信息,而这些信息不在实体内,怎么办?处理这个问题有很多种方式,这里介绍一下我目前最偏好的一种,引入领域服务:

领域服务代码

复制代码
 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6  7 using Happy.Examples.ManufactureManagement.Domain.QualityTests; 8 using Happy.Examples.ManufactureManagement.Domain.SmallCuts; 9 10 namespace Happy.Examples.ManufactureManagement.Domain.Services11 {12     public sealed class QualityTestManager13     {14         private readonly ISmallCutRepository _smallCutRepository;15 16         public QualityTestManager(ISmallCutRepository smallCutRepository)17         {18             _smallCutRepository = smallCutRepository;19         }20 21         public void Save(22             QualityTest qualityTest,23             IEnumerable<QualityTestItem> addedItems,24             IEnumerable<QualityTestItem> deletedItems)25         {26             qualityTest.AddItems(addedItems);27             qualityTest.DeleteItems(deletedItems);28 29             qualityTest.Save();30 31             this.AcquireLocks(qualityTest, addedItems);32             this.ReleaseLocks(qualityTest, deletedItems);33         }34 35         public void Submit(36             QualityTest qualityTest,37             IEnumerable<QualityTestItem> addedItems,38             IEnumerable<QualityTestItem> deletedItems)39         {40             qualityTest.AddItems(addedItems);41             qualityTest.DeleteItems(deletedItems);42 43             qualityTest.Submit();44 45             this.AcquireLocks(qualityTest, addedItems);46             this.ReleaseLocks(qualityTest, deletedItems);47         }48 49         public void Verify(QualityTest qualityTest)50         {51             qualityTest.Verify();52 53             foreach (var item in qualityTest.Items)54             {55                 var smallCut = _smallCutRepository.Load(item.SmallCutId);56                 smallCut.ReleaseLock(CreateLockInfo(qualityTest));57             }58         }59 60         private void AcquireLocks(QualityTest qualityTest, IEnumerable<QualityTestItem> addedItems)61         {62             foreach (var item in addedItems)63             {64                 var smallCut = _smallCutRepository.Load(item.SmallCutId);65                 smallCut.AcquireLock(CreateLockInfo(qualityTest));66             }67         }68 69         private void ReleaseLocks(QualityTest qualityTest, IEnumerable<QualityTestItem> deletedItems)70         {71             foreach (var item in deletedItems)72             {73                 var smallCut = _smallCutRepository.Load(item.SmallCutId);74                 smallCut.ReleaseLock(CreateLockInfo(qualityTest));75             }76         }77 78         private static LockInfo CreateLockInfo(QualityTest qualityTest)79         {80             return new LockInfo(LockType.LockByQualityTest, qualityTest.Id);81         }82     }83 }
复制代码

第二个问题:之前我只需要在 UI 中控制好这种状态机就行了,如果移动到了领域层,UI 也要重复一遍了,如何消除这种重复,答案是:引入元编程,让 UI 能自动识别这些元数据,最小化重复,这里就不给出实现(还没做)。

备注返回目录

上面状态机的配置过程也很有意思,In后只能是When,When后可以是If或Always,有点类似语法树了,找个机会可以写篇文章(实现是很简单的)。

原创粉丝点击