项目地址:https://github.com/rails-engine/flow_core
由于 Ruby/Rails 在这个领域没有很好的积累,导致在这些场景下,语言和技术本身的高生产力的优点也就无法得到释放。
我要来补上这一块。
大约一年半前,我写了 WorkflowCore,后来我用这个 Gem 实施了几个项目,也发现了一些问题:
春节期间,成功的鼓(fu)动(you)了 @hooopo 一起学习 PetriNet(主要是让高材生学完教我 )和工作流引擎的设计思路,重新完成了一个版本。 炮哥的版本已经先发了,我们基本使用了相同的 PetriNet 的表结构设计,但是在上层的设计有很大的差异,方便交流,我还是把这个版本完成了。 当然我对我的版本有充分自信!
数据模型全部是标准的 AR 模型,没有也不需要用到任何数据库特殊功能,所以对数据库没有限制
这可能是我在去年体会最深的一点... 帮某公司实施了一套 OA,投产前提出一系列黑人问号的流转要求,听上去又很合理(光指派就加了根据表单数据指定发起人,根据发起人所在部门指定特定级别的领导等等),又让我想到谈需求的时候甲方就提了一堆流转规则,我到家查了查发现是 Jaba 那边 Activiti 也要魔改才能解决的... 还好 WorkflowCore 原理简单解决这些需求并不会让代码变脏
体现在几点:
type
字段,也就是说可以也鼓励通过 Rails 标准的 单表继承(STI)方法来扩展虽然 Petri-net 流派这些年式微了,这种方法也确实比较底层,不如 BPMN 或者类似的基于活动(Activity-based)的方案直观容易被理解,但另一方面是他的表达能力更强,掌握仅有的几条规则后,就可以表达非常复杂的流转了。
Petri-net 可以被映射到 Activity 去,这个在我的后续计划里,搞定会容易实现拖拉拽编辑器
工作流可以创建实例前需要经过验证,FlowCore 提供了相关的约束和基础设施。
目前只做了一些基础检查,比如图的连通性,实际这个问题非常复杂,请 @luikore 帮忙了,
在调度过程中,工作流引擎内部要维护比较复杂的状态,通过 Rails 多态关联和一些基本的 OOP 技巧, 内部的状态管理不会泄漏到应用端实现的用户任务上。
工作流在运行过程中,是存在可能性如用户状态的变化,使得工作流流转下去的前提被破坏, 导致流转被卡住,FlowCore 提供了将当前任务设置为异常的功能,这样可以保存现场,同时给方便提供给用户友善的提示。
暂停流转在非交互流转上可能会用到,同样提供了支持。
来自 @dsh0416 的贡献,赞美队长,我做了一些改动。
大概长这样:
FlowCore::Definition.new name: "Timed split" do |net|
net.start_place :start
net.end_place :end
net.transition :t1, input: :start, output: :p
net.transition :t2, input: :p, output: :end
net.transition :t3, input: :start, output: :end do |t|
t.with_trigger TransitionTriggers::Timer,
countdown_in_seconds: 5
end
end.deploy!
清爽!
最本质的区别在于,FlowCore 只解决工作流最本质的问题:定义和调度, PetriFlow 则把指派、表单、分支条件判断等全部实现了。
我不是很赞同这个观点,这种贴近业务的功能,我最大的担心就是简单需求太复杂,奇葩场景不够用, 此外增加了 Gem 本身的复杂度。
但这些问题确实是要解决的,我的做法是核心提供够用的扩展机制, 之后我会在 FlowCore 之上实现一个 FlowKit 来实现这些功能, 如果 FlowKit 里提供的不能满足需要,可以继承或者利用基础设施开发自己的, 通过分层来解决问题。
作为最基本的演示,项目里包含了一个 Dummy app,里面演示了:
启动方法参考 repo 里的说明,注意需要安装 graphviz
(支持流程图可视化用的),没有会报错
最底下一层 PetriNet 和 Scheduling 的实现基本来自 https://www.tonymarston.net/php-mysql/workflow.html
中间的抽象层是需要集成的应用实现的部分:
请假时间 > 10 天
则放行之类的逻辑FlowKit 是计划中的项目,因为 FlowCore 并不解决真实业务的需求,这些将在 FlowKit 里实现(应该会覆盖 PetriFlow 和我之前 WorkflowCore 的所有功能), 对于非常复杂的功能,我可能考虑卖 Pro 版
gem "flow_core"
到 Gemfile
,运行 bundle
rails flow_core_engine:install:migrations
创建需要的 migration,运行 rails db:migrate
目前提供了 DSL 来创建,代码可以参考 test/dummy/db/seeds.rb
更复杂的例子可以参考 test/dummy/app/models/internal_workflow.rb
workflow.create_instance!
参考 test/dummy/app/models/arc_guards/dentaku.rb 这里实现了一个使用 Dentaku 做表达式判断的例子
参考 test/dummy/app/models/transition_triggers/timer.rb 实现一个定时器任务(可以用来实现超时过期这样的流转)
参考 test/dummy/app/models/transition_triggers/user_task.rb 实现了一个简单的用户任务和指派功能
test/dummy/app/models/transition_callbacks/notification.rb 实现了一个简单的任务创建后给处理人发送通知的 Callback
参考 test/dummy/app/models/user_task.rb 实现的简单的用户任务,
参考 test/dummy/app/models/approval_task.rb 实现的审批任务,完成时设置变量给 Task,以便 ArcGuard 使用
参考 test/dummy/app/models/internal_workflow.rb 在 Workflow 模型上利用 STI 创造了 InternalWorkflow 这一子类。
参考 test/dummy/app/overrides/models/flow_core/workflow_override.rb 利用标准的 Rails 技巧增强 Workflow 的功能,这里是添加了生成可视化流程图。
PetriNet 只有五条规则,掌握后就可以表达几乎所有的 Workflow pattern 也就是人们总结出来的所有可能的流转方式
注意连线上没有 Guard,这种情况下,当迁移执行完会产生两个 Token 到两个 Place 去,也就是说实现了并发流程。
例子:组织活动,物资组采购物资、场务组安排活动流程、志愿者组安排工作人员部署,这三个流程是同时进行的
搭配 And split 使用,意思是,当图中两个 Place 上边都有 Token 后,才可以继续。
例子:接 And join 的例子,三个流程都执行完,才能进入彩排
这个是最需要注意的,多个驳回应该指向一个已驳回 Place,而不是分别 组长驳回、HR 驳回... 否则就会卡住
没啥好说的,根据条件走不同的分支
简单的理解就是,两个分支都会走,谁先完成就干掉没完成的,最典型的用途就是超时处理
没啥好说的,通常是配合 Explicit OR split 使用
MRGA !!!