企业应用:一个够用的、通用的状态机(管理实体的业务状态)
来源:互联网 发布:在数组中添加元素 编辑:程序博客网 时间:2024/06/04 18:44
目录
背景常见的状态机需求实现状态机一个简单的示例一个相对完善的例子实现代码进一步交代的问题备注背景返回目录
企业应用下,需要关注三个状态机:
- 业务相关的状态机。
- 审批流程相关的状态机。
- 持久化相关的状态机。
某些企业应用开发人员终其一生就是希望能开发出通用的一个框架以简化这些状态机的开发。本文重点关注:“业务相关的状态机”。
常见的状态机需求返回目录
产品的状态机
单据的状态机
业务相关的状态机的一般性需求如下:
- 当处于某个状态时,可以执行哪些合法的迁移?迁移的前置条件是什么?
- 当处于某个状态时,可以执行哪些合法的操作?如:已提交和已审核状态的单据不能被修改。
实现状态机返回目录
我目前使用过两种思路实现这种状态机:
- 使用状态模式。这种要求为每种单据的状态管理定义一套状态体系,有点麻烦了。
- 使用状态表格。这种就是本文介绍的。
下面先看两个示例。
一个简单的示例返回目录
注意下面的链式配置代码,这些代码表达的意思是:
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,有点类似语法树了,找个机会可以写篇文章(实现是很简单的)。
- 企业应用:一个够用的、通用的状态机(管理实体的业务状态)
- WF - 状态机工作流的workflow实例状态与业务实体状态的对应
- 状态机的状态序列
- MembershipUser的业务实体.
- Qt状态机框架的一个典型应用
- hibernate管理实体的三个状态
- 状态机的应用
- 超越SOA:动态业务应用的新企业应用框架
- 新一代企业应用:Forrester的动态业务应用
- 一些应用工作流管理的业务情景
- 企业中银行管理业务系统的组成。
- Web应用的状态管理
- 企业应用下的业务组件开发实践
- 企业应用下的业务组件开发实践
- 企业应用业务需求变化的分析与应对-前言
- 超越SOA──动态业务应用的新企业应用框架(2)
- 如何构建一个较为通用的业务技术架构
- 文摘:UML建模目标,业务实体建模,认知结构,基于本体的企业工程
- 在C语言环境下使用google protobuf
- 开源中国iOS客户端学习——(三)再看协议与委托
- mac下自带的apache服务器的权限不能操作。
- 开源中国iOS客户端学习——(四)GCDiscreetNotificationView提示视图
- Android AsyncTask源码解析
- 企业应用:一个够用的、通用的状态机(管理实体的业务状态)
- android -- notification使用(转)
- 开源中国iOS客户端学习——(五)网络通信ASI类库(1)
- 如何获取AHCI base address <二>
- 恢复数据使用记录
- c++ error: 'malloc' was not declared in this scope
- 最简单关闭软键盘的方法——只需要一行代码,关闭无限个输入视图
- fatal error LNK1123:转换到 COFF 期间失败: 文件无效或损坏
- 网络版 捕鱼设计思路