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


    背景

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

    备注

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

  • 相关阅读:
    蓝鸥0c考试 绝密
    省市县用对象实现
    省市区用字典实现
    3道比较有用的oc练习题
    ios 开发 OC编程 类的扩展 协议 延展和 类目
    ios 开发 OC编程 内存管理
    ios 开发 OC编程 块语法bolck的一些应用
    ios 开发 OC编程 块语法bolck
    中等难度的通讯录.字典 动态分组法
    TestFlight
  • 原文地址:https://www.cnblogs.com/happyframework/p/3207916.html
Copyright © 2020-2023  润新知