自定义工作流 之 模型设计与实现

时间:2022-04-27 03:55:09

背景

在上篇文章(Workflow:自定义工作流 之 模型选择)介绍了模型的选择,这篇文章就介绍一下模型的设计与实现。

有些朋友会希望在这里看到:数据库、持久化或审批人角色处理等代码,我是领域驱动设计(DDD)的爱好者,因此很长一段时间内您是看不到这些代码的,我觉得这些不是模型的核心。

模型设计

概念模型

自定义工作流 之 模型设计与实现

模型规则如下

1、系统有活动(方块或圆形)和路由(线条)组成,每种类型的活动支持不同的路由规则。

2、方块代表人工活动,人工活动只能路由到一个目标节点,可以定义多个路由,但是只有一个路由会执行,这让模型支持:顺序和判定。

顺序执行

自定义工作流 之 模型设计与实现

判定执行

自定义工作流 之 模型设计与实现

3、圆形代表并行活动,Split(分流)和Join(合流)必须成对出现,Split会导致多个活动并行执行,Join会合并这些并行执行的活动,这让模式支持:并行。

并行执行

自定义工作流 之 模型设计与实现

设计模型

工作流中涉及两块设计模型,一、定义模型(流程本身);二、实例模型(流程的运行实例)。

定义模型

自定义工作流 之 模型设计与实现

实例模型

自定义工作流 之 模型设计与实现

我想类图就没有啥解释的,这些类图非常直观的反映了概念模型,让我们直接去看实现。

实现

活动基类

自定义工作流 之 模型设计与实现

这里定义活动可以Enter(进入)和Exit(离开),当然前提是他们CanEnter(能进入)和CanExit(能离开),如果他们Enter或Exit了,会触发OnEnter或OnExit。

Enter方法

 1         internal void Enter(WorkflowContext context, ActivityInstance activityInstance)
 2         {
 3             activityInstance.SetStateToEntering(context);
 4 
 5             if (!this.CanEnter(context, activityInstance))
 6             {
 7                 return;
 8             }
 9 
10             activityInstance.SetStateToEntered(context);
11 
12             this.OnEnter(context, activityInstance);
13         }

Exit方法

 1         internal void Exit(WorkflowContext context, ActivityInstance activityInstance)
 2         {
 3             activityInstance.SetStateToExiting(context);
 4 
 5             if (!this.CanExit(context, activityInstance))
 6             {
 7                 return;
 8             }
 9 
10             activityInstance.SetStateToExited(context);
11 
12             this.OnExit(context, activityInstance);
13 
14             if (this.IsFinal)
15             {
16                 context.WorkflowInstance.SetStateToCompleted(context);
17             }
18         }

人工活动

 1     /// <summary>
 2     /// 代表了流程中的一个人工节点。
 3     /// </summary>
 4     public class ManualActivity : Activity
 5     {
 6         /// <inheritdoc />
 7         protected override bool CanEnter(WorkflowContext context, ActivityInstance activityInstance)
 8         {
 9             return true;
10         }
11 
12         /// <inheritdoc />
13         protected override void OnEnter(WorkflowContext context, ActivityInstance activityInstance)
14         {
15 
16         }
17 
18         /// <inheritdoc />
19         protected override bool CanExit(WorkflowContext context, ActivityInstance activityInstance)
20         {
21             return true;
22         }
23 
24         /// <inheritdoc />
25         protected override void OnExit(WorkflowContext context, ActivityInstance activityInstance)
26         {
27             var routers = context
28                 .WorkflowInstance
29                 .Workflow
30                 .GetRoutersByFromId(this.Id);
31 
32             foreach (var router in routers)
33             {
34                 //遇到第一个能执行的路由,进执行路由。
35                 if (router.GetCondition().CanRoute(context, activityInstance, router))
36                 {
37                     router.Route(context, activityInstance);
38 
39                     break;
40                 }
41             }
42         }
43     }

这里可以看到,人工活动实现了:顺序和判定模式,您没能看到:会签、审批人规则等,这些会在后面增加进去,目前不是重点。

路由方法的代码

自定义工作流 之 模型设计与实现

 

这里除了分流令牌的创建,其它逻辑还是比较好理解的:创建目标活动实例,创建路由实例,执行目标活动(toActivity.Enter)。

脚本路由条件

 1     /// <summary>
 2     /// 脚本路由条件。
 3     /// </summary>
 4     public sealed class ScriptRouterCondition : IRouterCondition
 5     {
 6         private readonly string _serializedConditionContent;
 7 
 8         /// <summary>
 9         /// 构造方法。
10         /// </summary>
11         public ScriptRouterCondition(string serializedConditionContent)
12         {
13             _serializedConditionContent = serializedConditionContent;
14         }
15 
16         /// <inheritdoc />
17         public bool CanRoute(WorkflowContext context, ActivityInstance fromActivityInstance, Router router)
18         {
19             var engine = new ScriptEngine();
20 
21             foreach (var item in context.Agrs)
22             {
23                 engine.SetGlobalValue(item.Key, item.Value);
24             }
25 
26             return (bool)engine.Evaluate(_serializedConditionContent);
27         }
28 
29         /// <inheritdoc />
30         public string SerializedToString()
31         {
32             return _serializedConditionContent;
33         }
34     }

测试

  1 using System;
  2 using Microsoft.VisualStudio.TestTools.UnitTesting;
  3 using System.Collections.Generic;
  4 using System.Linq;
  5 
  6 using Happy.BasicModule.Activities.Domain.Workflows;
  7 using Happy.BasicModule.Activities.Domain.WorkflowInstances;
  8 
  9 namespace Happy.BasicModule.Activities.Test
 10 {
 11     [TestClass]
 12     public class ConditionWorkflowTest
 13     {
 14         [TestMethod]
 15         public void Condition_Test()
 16         {
 17             var workflow = new Workflow
 18             {
 19                 Id = Guid.NewGuid()
 20             };
 21 
 22             workflow.AddActivity(new ManualActivity
 23             {
 24                 Id = Guid.NewGuid(),
 25                 DisplayName = "A",
 26                 IsFinal = false,
 27                 Name = "A"
 28             }, true);
 29             workflow.AddActivity(new ManualActivity
 30             {
 31                 Id = Guid.NewGuid(),
 32                 DisplayName = "B1",
 33                 IsFinal = true,
 34                 Name = "B1"
 35             });
 36             workflow.AddActivity(new ManualActivity
 37             {
 38                 Id = Guid.NewGuid(),
 39                 DisplayName = "B2",
 40                 IsFinal = true,
 41                 Name = "B2"
 42             });
 43 
 44             var routerA_B1 = new Router
 45             {
 46                 Id = Guid.NewGuid(),
 47                 DisplayName = "A->B1",
 48                 FromId = workflow["A"].Id,
 49                 ToId = workflow["B1"].Id
 50             };
 51             routerA_B1.SetCondition(new ScriptRouterCondition("性别==\"男\""));
 52             workflow.AddRouter(routerA_B1);
 53 
 54             var routerA_B2 = new Router
 55             {
 56                 Id = Guid.NewGuid(),
 57                 DisplayName = "A->B2",
 58                 FromId = workflow["A"].Id,
 59                 ToId = workflow["B2"].Id
 60             };
 61             routerA_B2.SetCondition(new ScriptRouterCondition("性别==\"女\""));
 62             workflow.AddRouter(routerA_B2);
 63 
 64 
 65             var instance = new WorkflowInstance(workflow);
 66             var args = new Dictionary<string, object>
 67             {
 68                 { "性别", "" }
 69             };
 70 
 71 
 72             instance.Run(args);
 73             var activityInstances = instance.GetActivityInstances();
 74             var routerInstances = instance.GetRouterInstances();
 75 
 76             Assert.AreEqual(1, activityInstances.Length);
 77             Assert.AreEqual(ActivityState.Entered, activityInstances[0].State);
 78             Assert.AreEqual(WorkflowState.Running, instance.State);
 79             Assert.AreEqual(0, routerInstances.Length);
 80 
 81 
 82             instance.Resume(activityInstances[0].Id, args);
 83             activityInstances = instance.GetActivityInstances();
 84             routerInstances = instance.GetRouterInstances();
 85 
 86             Assert.AreEqual(2, activityInstances.Length);
 87             Assert.AreEqual(ActivityState.Exited, activityInstances[0].State);
 88             Assert.AreEqual(ActivityState.Entered, activityInstances[1].State);
 89             Assert.AreEqual(WorkflowState.Running, instance.State);
 90             Assert.AreEqual(1, routerInstances.Length);
 91 
 92 
 93             instance.Resume(activityInstances[1].Id, args);
 94             activityInstances = instance.GetActivityInstances();
 95             routerInstances = instance.GetRouterInstances();
 96 
 97             Assert.AreEqual(2, activityInstances.Length);
 98             Assert.AreEqual(ActivityState.Exited, activityInstances[0].State);
 99             Assert.AreEqual(ActivityState.Exited, activityInstances[1].State);
100             Assert.AreEqual(WorkflowState.Completed, instance.State);
101             Assert.AreEqual(1, routerInstances.Length);
102             Assert.AreEqual("B2", workflow[activityInstances[1].ActivityId].Name);
103         }
104     }
105 }

备注

如果去掉分流和合流,流程还是比较容易处理的,分流和合流的思想会在下一篇文章重点介绍。