EngineWire │ ← PVM.... 惊觉,一个优质的创作社区和技术社区。"/> EngineWire │ ← PVM..."/> EngineWire │ ← PVM..."/>
愚公移码头像
关注
蓝凌EKP18产品:适配器模式:PVM 如何独立于具体引擎封面图

蓝凌EKP18产品:适配器模式:PVM 如何独立于具体引擎

一、业务问题

流程虚拟机(PVM)的职责是"驱动流程流转"——它需要读写执行路径、创建子路径、查找任务、删除路径。

这些数据存在数据库里,由引擎层负责持久化。于是 PVM 面临一个两难的境地:

方案 A:PVM 直接调引擎层的类

// 反面教材:PVM 直接依赖引擎实现
LbpmExecution entity = hibernateTemplate.get(LbpmExecution.class, id);
entity.setFdState("active");
hibernateTemplate.update(entity);

问题:

  • PVM 绑死在 Hibernate 上——想换 MongoDB?重写 PVM

  • PVM 认识持久化实体——LbpmExecution 是数据库表映射,PVM 不该知道"表结构"

  • 无法并行多套引擎——引擎层的注释说得很清楚:"需提供不同的流程引擎层并行的情形,即可以同时运行多套流程解析引擎"

  • 测试困难——测 PVM 必须先起数据库

方案 B:PVM 定义自己需要的接口,引擎层来实现

这正是 EKP 的选择。PVM 说:"我需要这些能力,但我不关心你怎么实现。"引擎层说:"这些能力我有,我来适配给你。"

这就是适配器模式。


二、模式解法

2.1 标准适配器模式回顾

┌──────────────┐         ┌──────────────────┐
│   Client     │────────▶│  Target          │  ← 客户端期望的接口
│  (调用方)   │         │  (目标接口)     │
└──────────────┘         └────────▲─────────┘
                                  │ 实现
                         ┌────────┴─────────┐
                         │   Adapter        │  ← 适配器
                         │  (转换层)       │
                         └────────┬─────────┘
                                  │ 持有/委托
                         ┌────────▼─────────┐
                         │   Adaptee        │  ← 被适配者(已有实现)
                         │  (现有代码)     │
                         └──────────────────┘

核心价值:让两个"接口不兼容"的部分能协作,而双方都不用改。

2.2 EKP 的形态:接口由"使用方"定义

标准适配器模式里,Target 接口通常是"已有的一套标准"。但 EKP 的情况更彻底:

接口由 PVM(使用方)定义,引擎层(提供方)来实现。

这叫依赖倒置——不是 PVM 依赖引擎,而是双方都依赖 EngineWire 这个抽象。

┌─────────────────────────────────────┐
│            PVM 层                    │
│  (只认识 EngineWire 接口)           │
└──────────────┬──────────────────────┘
               │ 依赖
               ▼
┌─────────────────────────────────────┐
│   <<interface>>  EngineWire          │  ← PVM 定义的契约
│                                     │
│   + createProcessExecution(...)     │
│   + createExecution(...)            │
│   + findExecution(...)              │
│   + deleteExecution(...)            │
└──────────────▲──────────────────────┘
               │ 实现(适配)
┌──────────────┴──────────────────────┐
│         引擎层实现                    │
│  (Hibernate / 未来可能的其他实现)    │
└─────────────────────────────────────┘

PVM 不知道引擎层有什么类,引擎层不知道 PVM 内部怎么跑。 两边通过 EngineWire 对话。


三、源码证据

3.1 契约接口:EngineWire

接口的类注释把设计意图说得很直白:

/**
 * 提供流程引擎必须的流程元素:流程定义和流程实例。
 * PVM层不关注如何去创建流程定义,定义中的活动,流程实例和流程执行路径,
 * 这些对象的创建都是由此接口提供。
 * <p>
 * PVM层是一个流程虚拟机,需提供不同的流程引擎层并行的情形,
 * 即可以同时运行多套流程解析引擎。
 * 流程运行时可以把当前接口实现提供给PVM层。
 *
 * @author 龚健
 */
public interface EngineWire {

三个关键信息:

原文含义
"PVM层不关注如何去创建…"PVM 只管用,不管造
"可以同时运行多套流程解析引擎"这是适配器模式的价值主张:可替换
"把当前接口实现提供给PVM层"运行时注入实现(策略化的适配器)

3.2 九个方法:PVM 的全部"外部需求"

接口只有九个方法,但覆盖了 PVM 的全部外部依赖:

public interface EngineWire {
    /** ① 创建流程主执行路径 */
    StateProcessExecution createProcessExecution(
            OpenProcessDefinition definition, Parameters parameters);
​
    /** ② 根据流程实例ID查找主执行路径 */
    StateProcessExecution findProcessExecutionByInstance(
            String processInstanceId, Parameters parameters);
​
    /** ③ 创建子执行路径(并发分支) */
    StateExecution createExecution(String name, Execution parent,
            Parameters parameters);
​
    /** ④ 获取指定id的执行路径 */
    StateExecution getExecution(String executionId, Parameters parameters);
​
    /** ⑤ 在指定范围内查找匹配任务ID和活动类型的执行路径 */
    StateExecution findExecution(String taskId, String activityType,
            Execution scope, Parameters parameters);
​
    /** ⑥ 查找指定状态的执行路径集 */
    List<StateExecution> findExecutions(String fdState, Execution scope,
            Parameters parameters);
​
    /** ⑦ 获取指定父路径下的直接子路径集 */
    List<StateExecution> getChildExecutions(Execution parent,
            Parameters parameters);
​
    /** ⑧ 删除执行路径 */
    void deleteExecution(Execution execution, Parameters parameters);
​
    /** ⑨ 获取流程定义 */
    OpenProcessDefinition getProcessDefinition(String processDefinitionId,
            Parameters parameters);
}

逐一归类,PVM 的需求其实只有四类:

类别方法语义
创建① ③造执行路径
查找② ④ ⑤ ⑥ ⑦ ⑨找到已有的执行路径/定义
删除⑧销毁执行路径
(隐含)更新通过 StateExecution 接口见 3.4

这是"最小完备接口"的典范——四个基本操作(增删查改),不多一个方法。

设计启示:契约接口要"刚好够用"。 接口方法越多,实现方的负担越重,可替换性越差。九个方法,就是 PVM 全部的外部依赖。

3.3 一个值得注意的细节:处处有 Parameters

九个方法,每一个都带 Parameters 参数。

createProcessExecution(OpenProcessDefinition definition, Parameters parameters)
findProcessExecutionByInstance(String processInstanceId, Parameters parameters)
createExecution(String name, Execution parent, Parameters parameters)
// ... 全部如此

Parameters 是什么?它是跨越适配器边界的"上下文包"。

它携带:

  • 流程实例(LbpmProcess)

  • 执行参数(ExecutionParameters)

  • 引擎层的服务容器(ProcessServiceManager)

  • 访问管理器(AccessManager)

为什么不把这些做成 EngineWire 的成员变量,而要每次传?

因为执行上下文是运行时的、每次调用都不同的。同一个 EngineWire 实例要服务多个并发执行,不能有实例状态。

设计启示:适配器接口要"无状态",状态通过参数传入。 这样适配器可以是单例、可复用的,天然线程安全。

3.4 双向适配:StateExecution

适配不是单向的。PVM 不仅要调用引擎层,还要修改引擎层的对象状态。

PVM 需要"把执行路径状态改成 active"这个能力。但 LbpmExecution 是引擎层的持久化实体,PVM 不认识它。

解法:PVM 定义 StateExecution 接口,引擎层的对象实现这个接口。

/**
 * 提供能更新状态的执行路径,引擎层的执行路径需实现此接口,
 * 以提供给PVM层能及时更新相应执行路径的状态
 */
public interface StateExecution extends Execution {
    void setFdId(String id);
    void setFdTaskId(String fdTaskId);
    void setFdActivityType(String fdActivityType);
    void setFdActivityId(String fdActivityId);
    void setFdState(String fdState);
    void setFdSuspendHistoryState(String fdSuspendHistoryState);
​
    @Override
    StateProcessExecution getProcessExecution();
​
    @Override
    StateExecution getFdParent();
}

类注释一句话点透:"引擎层的执行路径需实现此接口,以提供给PVM层能及时更新相应执行路径的状态"。

这是一个"反向适配器":

标准适配器:Adapter 实现 Target,内部委托 Adaptee
反向适配:  Adaptee 直接实现 Target,无需额外适配层

为什么这里不需要独立的 Adapter 类?

因为 LbpmExecution 既是"持久化实体",也可以同时"实现 PVM 的接口"。一个类扮演两个角色,省掉了一层包装。

设计启示:适配器不一定非要"额外写一个类"。 如果被适配者能直接实现目标接口,就没有必要多加一层。适配器是手段,不是目的——目的是让两边能协作。

3.5 服务提供器:EngineProvider 三元组

EngineWire 不是孤立的。PVM 需要三样东西,打包在一个"提供器"里:

/**
 * PVM主服务提供服务的提供器
 */
public interface EngineProvider {
    /** @return 活动提供器 */
    ActivityProvider getActivityProvider();
​
    /** @return 引擎持久化服务 */
    EngineWire getEngineWire();
​
    /** @return 事件分发器 */
    EngineEventMulticaster getEventMulticaster();
}

三样东西,对应 PVM 的三大组件:

提供的能力对应接口PVM 拿它做什么
活动提供器ActivityProvider拿到节点行为、创建任务
持久化服务EngineWire读写执行路径
事件分发器EngineEventMulticaster广播事件

这是"打包适配"的思路:与其让 PVM 分别依赖三个接口,不如打包成一个 EngineProvider,PVM 只需认一个入口。

3.6 上下文:适配的"组装点"

ExecutionContextImpl 是两个世界的组装点:

public class ExecutionContextImpl implements ExecutionContext {
    /** 被包裹执行路径 */
    private StateExecution execution;
    /** 流程主执行路径 */
    private StateProcessExecution processExecution;
    /** 流程引擎提供器 */
    private EngineProvider provider;
    /** 流程定义 */
    private OpenProcessDefinition definition;
    /** 流程参数 */
    private Parameters parameters;
    /** 执行路径是否记录到数据库中 */
    private boolean persistent = true;
​
    /** 构造函数一:启动新流程 */
    public ExecutionContextImpl(OpenProcessDefinition definition,
            Parameters parameters, EngineProvider provider) {
        this.provider = provider;
        this.parameters = parameters;
        this.definition = definition;
        // 通过 EngineWire 创建流程主执行路径
        this.processExecution = provider.getEngineWire()
                .createProcessExecution(definition, parameters);
        this.execution = processExecution;
    }
​
    /** 构造函数二:操作已有流程 */
    public ExecutionContextImpl(BehaviourParameters behaviour,
            OpenProcessDefinition definition, Parameters parameters,
            EngineProvider provider, boolean persistent) {
        // ...
        initExecution(provider.getEngineWire(), parameters, behaviour);
    }
​
    private void initExecution(EngineWire wire, Parameters parameters,
            BehaviourParameters behaviour) {
        this.processExecution = wire.findProcessExecutionByInstance(
                behaviour.getFdProcessInstanceId(), parameters);
        // 找到指定的执行路径
        String taskId = behaviour.getFdTaskId();
        if (StringUtil.isNull(taskId)) {
            // 若任务id为空,则寻找任何一个激活的节点相对应的执行路径
            List<StateExecution> executions = wire.findExecutions(
                    Execution.STATE_WAITING, null, parameters);
            if (executions == null || executions.isEmpty()) {
                throw new TaskRefExecutionIsNullException();
            }
            this.execution = executions.get(0);
        } else {
            this.execution = wire.findExecution(taskId,
                    behaviour.getFdActivityType(), processExecution, parameters);
            if (execution == null) {
                throw new TaskRefExecutionIsNullException();
            }
        }
    }
}

两个构造函数,对应两个场景:

构造函数场景用的 EngineWire 方法
一启动新流程createProcessExecution
二操作已有流程findProcessExecutionByInstance + findExecution

注意第二个构造函数里的"兜底逻辑":

if (StringUtil.isNull(taskId)) {
    // 任务id为空 → 找不到具体任务 → 退而求其次,找任何一个等待中的执行路径
    List<StateExecution> executions = wire.findExecutions(
            Execution.STATE_WAITING, null, parameters);
}

业务含义:有些操作(比如特权人跳转)可能没有明确的任务 ID,只知道"这个流程实例"。这时就找"第一个等待中的执行路径"。

设计启示:适配器接口的能力要能覆盖"模糊场景"。 如果 EngineWire 只有"按 taskId 精确查找",这种模糊场景就没法处理。所以接口里既有精确查找(findExecution),也有条件查找(findExecutions)。

3.7 委托:OpenExecutionWraper 的适配职责

PVM 的包裹类 OpenExecutionWraper 把 EngineWire 的调用封装成便捷方法:

public abstract class OpenExecutionWraper extends ExecutionVisitor
        implements OpenExecution {
    /** 流程持久化服务 */
    private EngineWire wire;
    /** 流程定义 */
    private OpenProcessDefinition definition;
    /** 流程参数 */
    private Parameters parameters;
​
    public OpenExecutionWraper(ExecutionContext context) {
        super(context);
        this.parameters = context.getParameters();
        this.wire = context.getProvider().getEngineWire();   // ← 从 provider 取 wire
        this.definition = context.getDefinition();
    }
​
    /** 创建子执行路径 —— 委托给 EngineWire */
    protected StateExecution createStateExecution(String name) {
        return wire.createExecution(name, getSource(), parameters);
    }
​
    /** 取子执行路径集 —— 委托给 EngineWire */
    protected List<StateExecution> getChildExecutions() {
        return wire.getChildExecutions(getSource(), parameters);
    }
​
    /** 删除执行路径 —— 委托给 EngineWire */
    protected void deleteExecution(Execution execution) {
        wire.deleteExecution(execution, parameters);
    }
}

这是标准的适配器"委托"结构:

PVM 内部代码
    ↓ 调 createStateExecution(name)
OpenExecutionWraper(PVM 侧的统一包装)
    ↓ 委托 wire.createExecution(name, source, parameters)
EngineWire 实现(引擎层)
    ↓ 实际创建 + 持久化
数据库

PVM 内部代码不需要每次都写 wire.xxx(..., parameters)——parameters 和 wire 都在包裹类里存好了,调用方只管说"我要创建一个子路径"。

设计启示:适配器之上可以再封一层"便捷接口"。 EngineWire 是"能力契约"(面向引擎层),Wraper 的方法是"使用便利"(面向 PVM 内部代码)。两者分工明确。


四、设计启示

4.1 适配器 vs 依赖倒置:一对孪生兄弟

这两个模式经常一起出现,但关注点不同:

维度适配器模式依赖倒置原则
关注接口不兼容,怎么协作依赖方向,谁依赖谁
手段加一层转换定义抽象,双方都依赖抽象
EKP 场景引擎实体 → PVM 接口PVM 与引擎层都依赖 EngineWire
关系适配器实现依赖倒置的手段之一依赖倒置指导适配器设计

EKP 里两者是融合的:EngineWire 既是"适配器接口"(让引擎层适配 PVM 的需求),也是"依赖倒置的抽象"(PVM 不依赖具体引擎)。

4.2 适用场景判断

什么时候需要适配器?

信号EKP 的情况
两个模块接口不兼容PVM 要 Execution,引擎层有 LbpmExecution
一方可能被替换引擎层可能换持久化实现
一方不该依赖另一方PVM 是"虚拟机",不该依赖具体引擎
需要隔离变化数据库表结构可能变,PVM 不该受影响

反例(不该用适配器):

  • 两个类接口本来就兼容 → 直接调

  • 只有一个实现且永不变 → 直接依赖,别过度设计

4.3 三个实践要点

① 接口归"使用方"定义

EngineWire 定义在 PVM 侧(pvm/service 包),不定义在引擎侧。这个细节很重要:

接口在"要什么"的一侧  →  提供方必须迁就使用方
接口在"给什么"的一侧  →  使用方必须迁就提供方 ❌

接口归属决定了谁掌握主动权。 接口在 PVM 侧,意味着引擎层要适配 PVM;反之,PVM 就被引擎绑死了。

② 无状态 + 参数传递

前面讲过:EngineWire 的每个方法都带 Parameters。这让实现可以是单例、线程安全。

③ 提供"打包"入口

EngineProvider 把三个能力打包成一个入口。PVM 只需注入一个 provider,就能拿到全部所需服务。


五、业务价值

5.1 引擎可替换,PVM 不用改

这是最直接的价值:同一套 PVM,可以驱动不同的引擎实现。

业务上意味着什么?

  • 从 Hibernate 迁到 MyBatis → 换 EngineWire 实现,PVM 零改动

  • 高并发场景改用缓存层 → 换实现,PVM 零改动

  • 新客户用不同数据库 → 换实现,PVM 零改动

业务价值:技术选型不被锁死。 引擎核心的演进不会因为底层存储的变化而受阻。

5.2 测试可以脱离数据库

EngineWire 是接口,测试时可以注入一个内存实现:

// 测试用的内存实现(示意)
class InMemoryEngineWire implements EngineWire {
    private Map<String, StateExecution> executions = new HashMap<>();
    // 全部操作在内存 Map 上完成,不碰数据库
}

业务价值:PVM 的单测秒级完成,不用起数据库、不用造数据。测试跑得快,开发迭代就快。

5.3 分层解耦让"改一处"影响可控

PVM 和引擎层通过 EngineWire 隔离,意味着:

改动影响范围
改 PVM 内部流转逻辑引擎层不受影响
改引擎层持久化实现PVM 不受影响
改数据库表结构只改 EngineWire 实现

业务价值:改动影响可控,回归测试范围小,上线风险低。

5.4 新人能分头理解

一个新人接手代码,可以分头理解两个世界:

  • 想懂流程怎么流转 → 看 PVM 层(只依赖 EngineWire 接口)

  • 想懂数据怎么存 → 看引擎层(实现 EngineWire)

不用同时理解两边。 这是分层带来的人力可扩展性——团队可以并行开发,而不是所有人都要懂全部。


一句话:适配器模式让 PVM 说"我需要什么",引擎层说"我来给"。接口由使用方定义,实现由提供方适配,双方都依赖抽象而非彼此。 这是流程引擎能"分层解耦、引擎可替换"的技术基础。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/wwwhongxia/article/details/166253341

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--