自定義工作流 之 模型設計與實現


背景

在上篇文章(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 }

備注

如果去掉分流和合流,流程還是比較容易處理的,分流和合流的思想會在下一篇文章重點介紹。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM