蓝凌EKP18产品:整体架构
一、四层架构总览
EKP 流程引擎采用经典的四层分层架构,从外到内依次是:
┌─────────────────────────────────────────────────────────┐ │ 应用层 (Application) │ │ │ │ 业务模块调用入口:发起流程、审批、驳回、转办、加签... │ │ 提供给前端/第三方系统的 REST API / Java API │ │ │ │ 对应模块: sys-lbpmweb, sys-lbpmext, sys-lbpmdocking │ ├─────────────────────────────────────────────────────────┤ │ 引擎层 (Engine) │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ 上层:业务服务层 │ │ │ │ 流程模板管理、模拟仿真、变更日志、事件监听... │ │ │ │ 对应模块: sys-lbpmservice │ │ │ └─────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ 中层:引擎调度层 │ │ │ │ 操作行为调度、执行参数管理、节点属性解析... │ │ │ │ 包路径: engine/manager, engine/operation │ │ │ └─────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ 底层:PVM 虚拟机 │ │ │ │ 执行路径管理、原子操作调度、事件分发、状态机 │ │ │ │ 包路径: pvm/ (执行、操作、事件、上下文) │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ 对应模块: sys-lbpm │ ├─────────────────────────────────────────────────────────┤ │ 持久层 (Persistence) │ │ │ │ 数据访问对象 (DAO)、ORM映射、数据库表结构 │ │ 对应子包: engine/persistence │ │ 核心表: 流程实例表、执行路径表、工作项表、流程定义表... │ ├─────────────────────────────────────────────────────────┤ │ 基础层 (Infrastructure) │ │ │ │ Spring 容器、事务管理、权限框架、缓存、消息队列... │ │ 对应模块: CORE, com, sys-cache, sys-right 等 │ └─────────────────────────────────────────────────────────┘关键认识
这不是简单的四层堆叠,而是两套正交系统的组合:
纵向:按调用层次划分(谁调谁)
横向:按功能领域划分(管什么)
同一层里,不同模块各管一块,互不干扰;跨层时,通过接口契约通信,上层依赖下层,下层绝不反向调用。
二、引擎层深度拆解
引擎层是整套系统的核心,从下到上又可以细分为三个子层:
2.1 底层:PVM 流程虚拟机
PVM 是流程引擎的"CPU",职责纯粹而关键——只管"怎么走",不管"走什么"。
它关心的内容:
| 关心 | 不关心 |
|---|---|
| 当前执行路径在哪个节点 | 这个节点是审批还是发邮件 |
| 原子操作的排队和执行顺序 | 每个原子操作内部的业务细节 |
| 事件如何发布和分发 | 事件监听器做了什么业务处理 |
| 执行路径的父子关系和状态转换 | 这些数据怎么存到数据库 |
PVM 的六大子包各司其职:
execution(执行路径):流程的"当前位置指针",树形结构,6 种状态
operation(原子操作):最小执行单元,双队列调度
event(事件):流程生命周期事件定义(启动、进入节点、离开节点、结束…)
builder(定义模型):流程定义的抽象表示(节点、路由、条件)
context(上下文):执行环境、参数传递、变量作用域
service(服务接口):PVM 与引擎层的解耦契约(EngineWire、EngineProvider)
2.2 中层:引擎调度层
这一层是 PVM 虚拟机和具体业务之间的"中间件"。它的核心任务是:
把 PVM 发出的"指令"翻译成具体的业务动作。
关键模块:
Manager(管理器)
Manager 层掌握流程运行的全局状态和环境:
ProcessServiceManager:流程服务的总入口,协调各个子系统
ExecutionParameters:包装流程执行时需要的全部参数(实例ID、操作类型、临时变量…)
ProcessParameters:流程实例级别的参数上下文,贯通整个执行周期
ResourceCache:流程定义、节点配置的缓存,避免每次执行都重新解析
Operation(操作行为)
Operation 层定义了人工操作的抽象行为模板:
AbstractOperationBehaviour:所有操作行为的基类,定义了"接收信号→执行业务→决定路由"的标准流程
AbstractManualOperationBehaviour:人工审批操作(同意/驳回/转办等)的通用逻辑
AbstractAdditionOperationBehaviour:加签操作的通用逻辑
这些抽象类定义了"模板方法"——子类只需填充具体的业务逻辑,调度流程由父类统一控制。
Node(节点类型)
Engine 内置了四种标准节点,每种都有对应的 Behaviour(行为类):
| 节点类型 | 作用 | 对应的 Behaviour |
|---|---|---|
| StartNode | 流程起点,生成发起人待办 | StartNodeBehaviour |
| EndNode | 流程终点,触发结束事件 | EndNodeBehaviour |
| SplitNode | 并行分支网关,分裂执行路径 | SplitNodeBehaviour |
| JoinNode | 聚合分支网关,等待分支汇合 | JoinNodeBehaviour |
2.3 上层:业务服务层
这一层最接近应用层,处理流程引擎的"业务外围"功能。它不属于引擎核心但又是流程系统不可或缺的部分。
核心模块sys-lbpmservice包含:
流程模板管理:流程定义的发布、版本管理、变更日志
模拟仿真:在不产生真实数据的前提下预览流程走向
事件监听处理:审批通过后发通知、更新关联数据、触发下游流程
流程图导入导出:可视化流程图与流程定义 XML 的互转
定时任务:超时提醒、自动催办、过期处理
外部系统对接:钉钉、飞书、第三方 OA 的消息推送
三、模块间的交互流程
接下来,我们用"一次完整的审批流转"串联起各层的协作关系:
3.1 从宏观到微观的三级视角
第一级:系统间视角
用户浏览器 → 前端应用 → REST API → 流程引擎 → 数据库
这是最粗粒度的视图,每一步只看到调用方向。
第二级:模块级视角
浏览器 │ POST /api/workitem/complete ▼ sys-lbpmweb (REST Controller) │ 调用 service 接口 ▼ sys-lbpmservice (业务服务) │ 组装参数, 执行操作行为 ▼ sys-lbpm (引擎核心) │ PVM 驱动执行路径流转 ▼ engine/persistence (持久层) │ 写入工作项状态、执行路径状态 ▼ 数据库第三级:引擎内部视角(这就是第六课追踪过的路径)
ProcessServiceManager 接收请求 │ ▼ 创建 ExecutionContext(上下文) │ ▼ ExecutionWraper.signal() 激活执行路径 │ ▼ Signal 原子操作 → 节点 Behaviour.signal() │ ▼ TransitionEndTask 结束当前任务 │ ▼ TransitionStartTask 启动下一节点 │ ▼ ExecuteTask 执行节点行为3.2 关键转折点:人工节点 vs 自动节点
整个流转过程中有一个关键分叉:
ExecuteTask │ ├── [人工节点] │ 创建待办工作项 │ 设置状态 = WAITING │ 通知相关人员 │ ← 执行暂停,等待外部信号 │ └── [自动节点] 执行业务逻辑(计算、发通知、调接口…) 自动完成 继续向前流转 ← 不停顿,直接进入下一节点这就是为什么流程引擎在架构上天然分成"同步执行"和"异步等待"两部分。自动节点在引擎内部一次性跑完;人工节点则需要执行路径"停下来",等待外部用户的操作信号。
四、六大关键抽象
架构的好坏,关键看抽象设计。EKP 流程引擎定义了六组核心抽象,每一组都精准地解耦了一个维度的变化:
4.1 EngineWire —— 执行路径的"持久化抽象"
PVM 层不操作数据库——它只管内存中的执行路径对象。所有"创建执行路径""查找执行路径""删除执行路径"的操作,都通过EngineWire接口委托给引擎层。
这意味着什么?如果未来要把执行路径从关系数据库迁移到 Redis 或 MongoDB,只需换一个EngineWire实现,PVM 层一行代码不用改。
4.2 ActivityBehaviour —— 节点行为的"可插拔抽象"
每个节点类型对应一个ActivityBehaviour实现。引擎调度层只和这个接口打交道,不关心具体节点类型。
这意味着什么?要扩展自定义节点(比如"外部接口调用节点"),只需实现ActivityBehaviour接口,然后注册到系统中即可——不用修改引擎核心的任何代码。
4.3 AtomicOperation —— 执行步骤的"最小单元抽象"
"启动任务""执行任务""结束任务""移动父任务"——每一种流转动作都封装为独立的原子操作。它们通过名称互相引用,通过队列统一调度。
这意味着什么?要新增一种流转动作(比如"跳过当前节点"),只需新增一个AtomicOperation实现,然后在合适的事件监听器中触发它。
4.4 JoinStrategy —— 聚合策略的"策略抽象"
并行分支汇合时,"任意一人通过"还是"全部通过"?不同场景需要不同的聚合策略。JoinStrategy接口将策略定义与引擎核心分离:
AnyoneJoinStrategy:有一人通过即完成聚合AndJoinStrategy:所有人通过才算完成聚合
这意味着什么?如果要支持"超过半数即通过"的投票模式,只需新增一个VoteJoinStrategy实现,通过配置即可切换,无需修改分支聚合的核心逻辑。
4.5 ProcessServiceManager —— 服务调用的"门面抽象"
这是引擎对外的总入口。无论是 Web 层的 REST 接口,还是第三方系统的远程调用,最终都通过ProcessServiceManager进入引擎内部。
它就像一个前台——统一接待,分发给后台各个"职能部门"处理。
4.6 ExecutionContext —— 一次执行的"快照抽象"
每次操作都会创建一个ExecutionContext,它封装了当前执行所需的一切:
当前执行路径在哪
关联的流程定义是什么
流程参数有哪些
引擎的服务提供器是谁
这相当于把"执行现场"打了个包——后续的所有原子操作、事件监听都基于这个上下文展开,不需要反复查询数据库。
五、架构中的设计模式
EKP 流程引擎的设计中,几个经典模式贯穿始终:
5.1 模板方法模式
最典型的应用在操作行为层:
AbstractOperationBehaviour(模板) │ ├── signal() ← 模板方法,定义标准流程 │ ├── preSignal() ← 钩子,子类可选覆盖 │ ├── doExecute() ← 抽象方法,子类必须实现 │ └── postSignal() ← 钩子,子类可选覆盖 │ └── 子类实现: AgreeOperationBehaviour, RejectOperationBehaviour...价值:确保所有操作行为遵循统一的调度流程,子类开发者只需关心业务逻辑,不用管调度机制。
5.2 策略模式
JoinStrategy是典型策略模式。聚合节点的核心逻辑不依赖具体策略,运行时动态注入。
价值:新增聚合方式不影响聚合节点的核心实现,符合"对扩展开放,对修改关闭"的原则。
5.3 观察者模式(事件驱动)
PVM 的整个流转过程基于事件驱动。节点进入、节点离开、任务激活、任务结束——每个关键时刻都发布事件,监听器们各取所需地响应。
价值:新需求(比如"审批通过后自动发企业微信通知")只需新增一个事件监听器,核心流转逻辑不受影响。
5.4 门面模式
ProcessServiceManager作为引擎的统一入口,屏蔽了内部复杂度。外部调用者不需要知道 PVM、原子操作、执行路径这些概念,只需调用startProcess()、completeWorkitem()等语义明确的方法。
价值:降低使用门槛,同时保护内部实现细节,后续重构不影响外部调用方。
六、架构的核心原则
回顾整个架构设计,可以提炼出五条原则,它们共同构成了这套系统的"设计哲学":
原则一:关注点分离
引擎核心只管流转,不管业务。
PVM 不知道什么叫"审批通过",它只知道"当前节点结束了,沿着路由走到下一个节点"。审批逻辑是挂在节点上的 Behaviour 去执行的。
原则二:依赖倒置
上层依赖接口,下层实现接口。
引擎层定义ActivityBehaviour接口,业务层提供实现。不是引擎调用业务,而是引擎调用接口,接口背后是什么它不关心。
原则三:小步快走
复杂流转 = 多个原子操作的链式组合。
一次"审批通过并流转到下一节点"看似一步操作,实际分解为:结束当前任务 → 找到下一路由 → 启动下一节点 → 执行节点行为。每步都是独立、可跟踪、可回滚的小操作。
原则四:显式状态
所有状态必须显式表达,不允许"隐含状态"。
执行路径的六种状态(ACTIVE/WAITING/SUSPENDED/ENDING/ENDED/CONCURRENT)互斥且完备。不存在"既是等待又是激活"的模糊情况。这种严格的状态机设计是流程可靠性的基石。
原则五:可观测性
每个关键动作都留下痕迹。
事件机制不仅是扩展机制,也是观测手段。流程启动、节点进入、任务创建、操作完成——都有对应的事件。监控系统可以监听这些事件来构建执行轨迹、性能仪表盘和异常告警。
