您的位置:首页 > 其它

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

2013-07-23 08:31 477 查看

背景

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

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

模型设计

概念模型



模型规则如下

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

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

顺序执行



判定执行



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

并行执行



设计模型

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

定义模型



实例模型



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

实现

活动基类





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

Enter方法

internal void Enter(WorkflowContext context, ActivityInstance activityInstance)
{
activityInstance.SetStateToEntering(context);

if (!this.CanEnter(context, activityInstance))
{
return;
}

activityInstance.SetStateToEntered(context);

this.OnEnter(context, activityInstance);
}


Exit方法

internal void Exit(WorkflowContext context, ActivityInstance activityInstance)
{
activityInstance.SetStateToExiting(context);

if (!this.CanExit(context, activityInstance))
{
return;
}

activityInstance.SetStateToExited(context);

this.OnExit(context, activityInstance);

if (this.IsFinal)
{
context.WorkflowInstance.SetStateToCompleted(context);
}
}


人工活动

/// <summary>
/// 代表了流程中的一个人工节点。
/// </summary>
public class ManualActivity : Activity
{
/// <inheritdoc />
protected override bool CanEnter(WorkflowContext context, ActivityInstance activityInstance)
{
return true;
}

/// <inheritdoc />
protected override void OnEnter(WorkflowContext context, ActivityInstance activityInstance)
{

}

/// <inheritdoc />
protected override bool CanExit(WorkflowContext context, ActivityInstance activityInstance)
{
return true;
}

/// <inheritdoc />
protected override void OnExit(WorkflowContext context, ActivityInstance activityInstance)
{
var routers = context
.WorkflowInstance
.Workflow
.GetRoutersByFromId(this.Id);

foreach (var router in routers)
{
//遇到第一个能执行的路由,进执行路由。
if (router.GetCondition().CanRoute(context, activityInstance, router))
{
router.Route(context, activityInstance);

break;
}
}
}
}


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

路由方法的代码





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

脚本路由条件

/// <summary>
/// 脚本路由条件。
/// </summary>
public sealed class ScriptRouterCondition : IRouterCondition
{
private readonly string _serializedConditionContent;

/// <summary>
/// 构造方法。
/// </summary>
public ScriptRouterCondition(string serializedConditionContent)
{
_serializedConditionContent = serializedConditionContent;
}

/// <inheritdoc />
public bool CanRoute(WorkflowContext context, ActivityInstance fromActivityInstance, Router router)
{
var engine = new ScriptEngine();

foreach (var item in context.Agrs)
{
engine.SetGlobalValue(item.Key, item.Value);
}

return (bool)engine.Evaluate(_serializedConditionContent);
}

/// <inheritdoc />
public string SerializedToString()
{
return _serializedConditionContent;
}
}


测试

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;

using Happy.BasicModule.Activities.Domain.Workflows;
using Happy.BasicModule.Activities.Domain.WorkflowInstances;

namespace Happy.BasicModule.Activities.Test
{
[TestClass]
public class ConditionWorkflowTest
{
[TestMethod]
public void Condition_Test()
{
var workflow = new Workflow
{
Id = Guid.NewGuid()
};

workflow.AddActivity(new ManualActivity
{
Id = Guid.NewGuid(),
DisplayName = "A",
IsFinal = false,
Name = "A"
}, true);
workflow.AddActivity(new ManualActivity
{
Id = Guid.NewGuid(),
DisplayName = "B1",
IsFinal = true,
Name = "B1"
});
workflow.AddActivity(new ManualActivity
{
Id = Guid.NewGuid(),
DisplayName = "B2",
IsFinal = true,
Name = "B2"
});

var routerA_B1 = new Router
{
Id = Guid.NewGuid(),
DisplayName = "A->B1",
FromId = workflow["A"].Id,
ToId = workflow["B1"].Id
};
routerA_B1.SetCondition(new ScriptRouterCondition("性别==\"男\""));
workflow.AddRouter(routerA_B1);

var routerA_B2 = new Router
{
Id = Guid.NewGuid(),
DisplayName = "A->B2",
FromId = workflow["A"].Id,
ToId = workflow["B2"].Id
};
routerA_B2.SetCondition(new ScriptRouterCondition("性别==\"女\""));
workflow.AddRouter(routerA_B2);

var instance = new WorkflowInstance(workflow);
var args = new Dictionary<string, object>
{
{ "性别", "女" }
};

instance.Run(args);
var activityInstances = instance.GetActivityInstances();
var routerInstances = instance.GetRouterInstances();

Assert.AreEqual(1, activityInstances.Length);
Assert.AreEqual(ActivityState.Entered, activityInstances[0].State);
Assert.AreEqual(WorkflowState.Running, instance.State);
Assert.AreEqual(0, routerInstances.Length);

instance.Resume(activityInstances[0].Id, args);
activityInstances = instance.GetActivityInstances();
routerInstances = instance.GetRouterInstances();

Assert.AreEqual(2, activityInstances.Length);
Assert.AreEqual(ActivityState.Exited, activityInstances[0].State);
Assert.AreEqual(ActivityState.Entered, activityInstances[1].State);
Assert.AreEqual(WorkflowState.Running, instance.State);
Assert.AreEqual(1, routerInstances.Length);

instance.Resume(activityInstances[1].Id, args);
activityInstances = instance.GetActivityInstances();
routerInstances = instance.GetRouterInstances();

Assert.AreEqual(2, activityInstances.Length);
Assert.AreEqual(ActivityState.Exited, activityInstances[0].State);
Assert.AreEqual(ActivityState.Exited, activityInstances[1].State);
Assert.AreEqual(WorkflowState.Completed, instance.State);
Assert.AreEqual(1, routerInstances.Length);
Assert.AreEqual("B2", workflow[activityInstances[1].ActivityId].Name);
}
}
}


备注

如果去掉分流和合流,流程还是比较容易处理的,分流和合流的思想会在下一篇文章重点介绍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: