(LitwareHR研究系列) WF in LitwareHR

  作为MS推出的第一个SAAS官方demo,LitwareHR成为笔者近期关注的重点。笔者在一个月之前还完全没有接触过.Net3.0中的主要内容(WCF,WF等),所以对LitwareHR的研究也比较吃力。本文大概梳理了工作流技术在LitwareHR中的应用场景和实现方式,适用于对WF和LitwareHR都稍有了解的tx阅读。其实本文还很不成熟,但目前园子里关于LitwareHR的文章太少了,希望能看到有更多的同学对SAAS和LitwareHR给与关注。后面笔者还会逐渐把对LitwareHR的分析发上来,大家一起学习,一起讨论。

1.WF应用的业务场景
   由于只是一个示例应用,所以LitwareHR中只给出了一个业务应用:招聘流程。在这个业务应用中包括两种工作流类型,一个是主招聘流程,这是一个状态机工作流,用来发布招聘项目、启动招聘进程等;另一个是简历审核流程,用来具体对一个提交简历的个人进行一系列业务处理,如面试等,这是一个顺序工作流。主招聘流程是系统预定义的,不能被配置;而简历审核流程可以做简单配置。在招聘方(如contoso)发布了一个职位之后,就会创建一个招聘流程。任何在public site注册的人都可以去申请这个职位,一旦申请被提交,简历审核流程就会被创建。根据申请中的数据的不同(比如来自不同地区的申请),可以触发不同的简历审核流程(这是在后台的Business Process配置菜单下进行配置的)。你可以创建新的简历审核流程,也可以设定在何种条件下触发何种简历审核流程。另外,在对一个具体的简历审核流程的编辑上,由于Litware 1.0是个纯的Web程序,不包含Smart Client,所以也就没有包含一个Workflow Designer(WD),因此仅支持一些很简单的工作流编辑。首先它仅仅是一个顺序工作流,不支持状态机工作流;其次,工作流中的Activity完全是线性、顺序排列,你只能改变Activity的执行顺序,而无法添加更多的逻辑判断规则(即对rule/condition的支持较差)。另外,由于没有WD,我们甚至无法自定义活动的类型,只能从下拉列表中选择系统已经编程实现的Activity类型,下面这段代码就是用来初始化活动类型下拉框的,可以看到所有的可选活动类型都是写死在代码里的:

1public Dictionary<stringstring> GetStepTypes()
2{
3  Dictionary<stringstring> types = new Dictionary<stringstring>();
4  types.Add(typeof(InterviewActivity).ToString(), Constants.Ruleset.InterviewStep);
5  types.Add(typeof(NegotiationActivity).ToString(), Constants.Ruleset.NegotiationStep);
6  types.Add(typeof(ResultActivity).ToString(), Constants.Ruleset.ResultStep);
7  return types;
8}

另外,LitwareHR中WF是和WCF集成在一起使用,也就是说,对工作流的管理和操作都作为服务封装在WCF的服务端。我们又已经知道,LitwareHR中的WCF是寄宿在IIS上的,那在这种情况下,WF runtime会被Host到哪个进程上呢?应该就是w3wp.exe吧。

2.主要的处理流程及对应代码
 下面我们就以 单位发布招聘计划(初始化招聘工作流)--〉应聘者提交简历(初始化简历处理工作流)--〉单位进行面试等工作(运行简历处理工作流实例)--〉同意或拒绝录取(结束简历处理工作流) 为主线,具体介绍一下LitwareHR中WF技术的应用。当然,这个主线偏重于对工作流运行时的介绍,如果您比较关注工作流的编辑和工作流规则的制定(这些内容在LitwareHR中体现得比较薄弱,相对来说也比较简单),可以从Private Site中的Business Logic的配置部分入手,本文就不赘述了。
(1)发布招聘计划(open position)
  在下面的代码段里,不仅有招聘工作流的初始化过程,还包括了整个工作流运行时的初始化过程。

发布招聘计划

到这里问题还没探讨完,我们的工作流经常会像这里所展示的招聘工作流一样,要等待一个事件才能继续向下进行(本例中的事件是ResumeSubmitted)。在等待事件的过程中,运行时不会一直把工作流实例加载到内存中,而是会通过“钝化”操作将工作流实例当前的执行状态保存到数据库里,待事件发生后再继续加载执行(当然,这里牵扯到一些配置和算法,比如说运行时会在等待多长时间之后进行钝化操作,笔者目前尚未对此进行更深入地研究)。
(2)应聘者提交简历(submit resume)
  在上面的段落中我们看到,一个招聘流程是一个状态机工作流,它的初始状态会被ResumeSubmitted事件触发,而这个事件的触发过程将在这个段落中被描述。先简单看一看工作流宿主环境中的方法调用过程:

 1/*
 2 *当用户提交简历后,ProcessLogic中的这个方法将首先被调用。
 3 */

 4static public void SubmitResume(Resume resume, Upn upn)
 5{
 6    RecruitingMoniker service = Runtime.GetService<RecruitingMoniker>();
 7    Guid recruitingProcessId = GetRecruitingProcessByPositionId(resume.PositionId);
 8    /*通过本地通信服务的SubmitResume方法来触发简历提交事件*/
 9    service.SubmitResume(recruitingProcessId, resume, upn);
10    /* 
11     *在等待建立提交的漫长时间内,招聘工作流可能早已被“钝化”,其状态也已被persistence和tracking服务保存到了数据库里。
12     *现在发生了简历提交事件,工作流需要继续运行了。
13     */

14    RunWorkflowInScheduler(recruitingProcessId);
15}

16
17/*RecruitingMoniker.SubmitResume()方法引发了ResumeSubmitted事件,这将启动
18   状态及工作流的事件驱动活动(EventDrivenActivity)。*/

19public void SubmitResume(Guid recruitingProcessId, Resume resume, Upn upn)
20{
21    ResumeSubmittedEventArgs args = new ResumeSubmittedEventArgs(recruitingProcessId, resume);
22    args.Identity = upn.ToString();
23
24    EventHandler<ResumeSubmittedEventArgs> evh = ResumeSubmitted;
25    if (evh != null)
26    {
27    evh(null, args);
28    }

29}

既然事件启动,我们就重新关注一下在“发布招聘计划”中提到的名为ResumeSubmitted的EventDrivenActivity的定义吧,这个活动一共有三个子活动,第一个活动用来注册监听以及处理事件,在这个活动之后还有两个活动要被执行,分别是TenantPolicyActivity和WorkflowFactoryActivity。大家应该能想到的是,简历提交了,招聘工作流要继续进行,但我们在BusinessLogic的配置界面里可以配置多个Workflow的定义,需要根据预先定义的规则集(RuleSet)来确定究竟选用哪个。而这,正是TenantPolicyActivity所要做的。看一下这个活动的执行代码:
 1protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
 2{
 3    /*这里返回的是代表整个招聘流程的工作流。*/
 4    Activity targetActivity = this.GetRootWorkflow(this.Parent);
 5
 6    /*根据tenantID获得相应用户的规则集定义。*/
 7    string tmp = WorkflowLogic.GetRuleSetDef(TenantId);
 8    WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
 9    using (StringReader strReader = new StringReader(tmp))
10    {
11    using (XmlReader xmlReader = XmlReader.Create(strReader))
12    {
13        RuleSet ruleSet = serializer.Deserialize(xmlReader) as RuleSet;
14        if (ruleSet != null)
15        {
16        RuleValidation validation = new RuleValidation(targetActivity.GetType(), null);
17        RuleExecution execution = new RuleExecution(validation, targetActivity, executionContext);
18        /*执行规则集中的规则。大家看一看保存在RuleSetDef表中的数据就知道,其实这里就是给
19         *工作流的TargetEvaluationProcess属性赋值,而这个赋值决定了后面将构造怎样的一个工作流来完成
20         *后面的招聘工作*/

21        ruleSet.Execute(execution);
22        }

23        return base.Execute(executionContext);
24    }

25    }

26}

TenantPolicyActivity活动通过RuleSet来指定了要创建的子工作流名称,接下来的WorkflowFactoryActivity就是根据名称来创建这个工作流了,看一看它的执行代码:

 1protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
 2{
 3    /*有兴趣的话大家可以看一看和这里的事件处理有关的代码,是一个典型的工作流向其子活动传递消息(提供服务)
 4     *的应用。*/

 5    this.RaiseEvent(InvokingEvent, thisnew EventArgs());
 6
 7    if (TargetWorkflow == null || TargetWorkflow.Equals(Guid.Empty))
 8    {
 9    throw new InvalidOperationException("TargetWorkflow property must be set to a valid Type that derives from Activity.");
10    }

11
12    string str = WorkflowLogic.GetWorkflowDef(TargetWorkflow).Xoml;
13    TextReader sr = new StringReader(str);
14    XmlReader xomlReader = XmlReader.Create(sr);
15
16    WorkflowFactoryRuntimeService startWorkflowService = executionContext.GetService<WorkflowFactoryRuntimeService>();
17    /*根据tenant用户对工作流的定义,创建了一个新的简历处理工作流。值得注意的是,简历处理工作流
18     *并未作为招聘工作流的子流程存在,而是一个单独的根节点工作流。。*/
    
19    Guid instanceId = startWorkflowService.StartWorkflow(xomlReader, nullthis.Parameters);
20
21    base.SetValue(InstanceIdProperty, instanceId);
22    this.RaiseEvent(InvokedEvent, thisnew EventArgs());
23
24    return ActivityExecutionStatus.Closed;
25}

(3)简历的处理(EvaluationProcess)
 看一看通过用户定义的工作流配置,你会发现处理简历的工作流就是一个很简单的顺序工作流(这是由litware1.0中的操作界面比较简单导致的,难以设计更复杂的工作流出来):
保存在数据库中的工作流定义

下图是出现在处理简历工作流中的活动类型的类图:
 
因为这三个活动在结构和处理方式上是类似的,我们就把注意力集中到其中一个活动上,以InterviewActivity为例进行说明。下面给出这个活动的设计图:

可以看到这个活动内部包含一个条件判断,条件成立的情况下触发一个HandlerExternalEventActivity。条件定义在InterviewActivity.Rules里面,非常简单,就是整个工作流的Positive属性为true。而这个HandlerExternalEventActivity的创建过程为:
1this.handleExternalEventActivity1.EventName = "InterviewSubmitted";
2this.handleExternalEventActivity1.InterfaceType = typeof(LitwareHR.Recruiting.Workflows.IEvaluationMoniker);
3this.handleExternalEventActivity1.Name = "handleExternalEventActivity1";
4this.handleExternalEventActivity1.Invoked += 
5    new System.EventHandler<System.Workflow.Activities.ExternalDataEventArgs>(this.InterviewActivityInvoked);
6this.handleExternalEventActivity1.SetBinding(System.Workflow.Activities.HandleExternalEventActivity.RolesProperty, 
7    ((System.Workflow.ComponentModel.ActivityBind)(activitybind1)));
所以,它等待的是本地通信服务EvaludationMoniker的InterviewSubmitted事件。而拥有权限的人在Public Site上对处于面试过程的简历进行处理并提交后,InterviewSubmitted事件就会被触发。
 (4)结束工作流
   结束工作流是非常简单的。特别是对于简历审批工作流,是一个非常简单的顺序工作流,最后一个Activity运行完了,它也就运行完了,这里就不再多说了。
posted @ 2007-09-26 15:57  EagleFish(邢瑜琨)  阅读(3143)  评论(4编辑  收藏  举报