点了同意后,那条站内信并非引擎写入

点了“同意”之后,那条站内信并非引擎写入。jeeflow 只有拦截器改走向和事件喊一声两类扩展点,引擎从不保证任何消息落库。从 issue 100 到 104 的迭代中,消息写路径、事件机制与抄送知会边界被逐层踩实,如今 CC_CREATE 已实现六语言 6/6 全闭环。

这一设计直接指向软件架构中的单一职责原则。引擎只管流程状态机,不碰任何副作用,尤其是持久化消息这样的外部依赖。开发者在扩展时必须清楚:想改流程就用拦截器,想知道发生了什么就听事件,写消息则是消息模块自己的事。这种边界让核心引擎保持极简,也让消息逻辑可以独立演进。

实际项目中,这种划分避免了常见陷阱。很多工作流引擎把通知逻辑塞进流程节点,导致代码混杂、测试困难、语言切换麻烦。jeeflow 把这些责任外置,引擎本身变成纯粹的状态机,扩展点只提供有限且明确的钩子。结果是系统更易维护,也更容易支持多语言环境。

引擎仅暴露拦截器和事件两类扩展点

jeeflow 引擎只定义两类扩展点:拦截器和事件。拦截器可以在流程到达某个节点时介入,修改后续走向;事件则在特定节点触发后发出信号,通知外部系统。两者职责边界清晰,前者影响控制流,后者仅传递信息。

为什么只给这两类?因为流程引擎的核心是状态转移。拦截器足以覆盖所有需要改变方向的场景,比如审批拒绝后跳转到特定处理人,或根据条件动态路由。事件则负责通知,不允许它修改状态,避免副作用污染核心逻辑。

这种极简设计符合架构最佳实践。过多扩展点会让引擎变得复杂,边界模糊,难以保证一致性。jeeflow 选择只暴露必要钩子,强制开发者把消息、日志、外部调用等行为放在引擎之外。拦截器管走向,事件管喊话,剩下的事交给专门的模块。

在实际案例里,这种限制反而提升了扩展性。开发者不会误把消息逻辑写进拦截器,因为拦截器接口根本不提供写库能力。事件监听器也只能接收信号,无法反向干预流程。这种强制分离让团队在大型项目中更容易分工,引擎团队维护核心,消息团队专注通知渠道。

拦截器只改流程走向不负责写消息

拦截器在 jeeflow 中的唯一职责是改变流程走向。它可以在节点进入或退出时被调用,决定下一步执行哪个分支或跳过哪些步骤,但绝不负责写入任何消息。

这种限制来自引擎的设计原则:不保证任何消息落库。如果拦截器能写消息,就意味着引擎间接承担了消息持久化的责任,一旦消息库不可用,整个流程就会受影响。jeeflow 明确禁止这种行为,拦截器只返回新的流向信息。

为什么这么做?因为消息写入是高风险操作。它涉及数据库事务、模板渲染、多语言切换、抄送逻辑等复杂细节。如果放在拦截器里,这些细节会污染流程定义,也让单元测试变得困难。把消息责任剥离后,拦截器保持纯粹,专注于控制流。

实际使用中,这一区分避免了很多 bug。过去一些工作流系统把“发送通知”作为拦截器的一个选项,导致审批通过后消息没发出去,整个流程状态却已经推进。jeeflow 不允许这种情况发生,拦截器只改走向,消息必须由外部模块在事件触发后独立完成。

事件仅喊一声消息落库全靠外部

jeeflow 的事件机制只做一件事:喊一声。它在流程节点到达特定状态时发出事件信号,外部监听器收到后自行决定是否写消息、写什么内容、用哪种语言。

事件不携带落库保证。引擎发出事件后就不再关心后续,消息是否成功写入数据库、是否发送给用户,都由消息模块独立处理。这种彻底解耦让事件变得轻量,也让消息模块可以独立升级或替换。

信号显示,消息写路径是独立演进的。事件只负责通知发生,写库逻辑则在消息模块中实现。这种分工让引擎不需要知道站内信、邮件或推送的具体实现细节。

在实践中,这意味着开发者可以为同一事件注册多个监听器:一个写站内信,一个发邮件,一个记录审计日志。引擎只喊一次,外部模块各司其职。这种设计显著降低了耦合,也让多语言支持变得自然,因为语言选择完全由消息模块决定。

消息模块从引擎核心彻底剥离

将消息写库责任完全外置后,jeeflow 的架构解耦效果显著。引擎核心不再包含任何与消息相关的代码,也不依赖消息存储服务。这种剥离让引擎可以专注于流程状态机的正确性,而消息模块可以独立演化。

解耦带来的第一个好处是可测试性。引擎的单元测试不再需要 mock 消息数据库,消息模块的测试也可以独立运行,不受流程引擎变化影响。第二个好处是可扩展性,新增一种通知渠道只需要扩展消息模块,无需修改引擎。

“引擎从不保证任何一条消息落库”这一原则成为整个架构的基石。它迫使所有消息相关逻辑都在边界之外实现,也让抄送、知会这类功能自然落在消息模块而非引擎。

实际项目中,这种剥离减少了生产事故。过去消息发送失败经常导致流程卡死,现在即使消息模块全部宕机,核心流程依然可以正常推进。解耦让系统各部分职责清晰,故障隔离效果明显。

issue 100 到 104 逐步踩实消息边界

从 issue 100 到 104 的迭代过程,清晰展现了消息边界的逐步明确。早期版本中消息写路径与事件机制存在模糊地带,迭代中团队逐一澄清了职责。

issue 100 重点梳理了消息写路径,明确了哪些操作属于消息模块,哪些属于引擎。后续 issue 针对事件机制进行了重构,确保事件只负责通知,不再携带任何写库逻辑。抄送和知会功能也被明确划归消息模块。

这一系列迭代不是一次性完成,而是通过具体问题逐步踩实边界。每解决一个 issue,都进一步强化了“引擎不发消息”的原则。到 issue 104 时,消息写路径、事件机制与抄送知会的分工已经非常清晰。

这种渐进式演进符合实际项目开发规律。不是一开始就设计完美架构,而是在使用中发现问题,然后通过迭代把边界越踩越实。最终结果是整个系统职责划分清晰,扩展点行为可预期。

CC_CREATE 六语言 6/6 全闭环验证设计

CC_CREATE 功能在六种语言环境下全部实现闭环,成为验证扩展点设计有效性的最佳案例。六语言 6/6 全覆盖,意味着不管用户使用哪种语言,抄送创建时的消息通知都能正确生成并落库。

这一结果直接得益于消息模块的独立性。引擎只发出 CC_CREATE 事件,消息模块根据用户语言选择对应模板进行渲染。因为消息逻辑完全独立于引擎,添加新语言支持只需要在消息模块扩展模板,无需改动任何流程定义或拦截器。

六语言全闭环也证明了事件机制的可靠性。无论底层使用何种语言环境,事件都能被正确触发,消息模块都能接收并处理。这种一致性在多语言工作流系统中非常关键。

这一案例对多语言工作流支持意义重大。它表明 jeeflow 的设计不仅适用于单一语言环境,在全球化场景下同样稳健。消息边界踩实后,国际化工作变得模块化、可并行,显著降低了支持新语言的成本。

整个设计体现了软件架构中“关注点分离”的经典实践。引擎、拦截器、事件、消息模块各司其职,边界清晰,职责明确。jeeflow 通过 issue 迭代和实际功能闭环,证明了这种极简扩展点设计在复杂业务场景下的可行性和可维护性。

参考来源