跪拜 Guibai
← All articles
Java · Architecture

A Workflow Engine's Entire Runtime Is One JSON File

By mldong ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A workflow engine that treats JSON as its sole definition format eliminates vendor lock-in at the data layer. Any system that can emit this JSON can drive the engine, and any language that can parse it can run the processes — the five-language demo proves this isn't aspirational.

Summary

Every process in jeeflow — from simple approvals to parallel countersign with conditional branches — serializes into a single JSON file. The schema uses only four node types (start, task, decision, end) and five top-level fields. Routing decisions live on edges as expression strings evaluated through a pluggable SPI, not a hardcoded expression language. Ten shared test processes, consumed identically by Java, Go, Python, Node, and PHP engines, demonstrate that the JSON is the contract and the engine is an implementation detail. The pro demo site maps nine business scenarios — leave requests, purchase approvals, reimbursements with six-person parallel countersign — back to exactly four JSON patterns: simple approval, sequential multi-node, conditional branch, and parallel countersign. Version management keeps multiple definitions live simultaneously; in-flight instances stick to their launch version while new instances pick up the latest. The designer is a pure frontend component that produces JSON, decoupled from any backend or engine.

Takeaways
jeeflow process definitions are flat JSON files with exactly five top-level fields: name, displayName, type, instanceUrl, and the nodes/edges arrays.
Only four node types exist — snaker:start, snaker:task, snaker:decision, snaker:end — and they cover every pattern from simple approval to mixed-mode workflows with rejection.
Conditional routing uses expression strings on edges, evaluated through an IExpressionEvaluator SPI that accepts SpEL, OGNL, or any custom evaluator.
Parallel countersign creates tasks for all assignees at once in the Java engine but one-by-one in Python and Node; behavior is identical, implementation differs.
Nine business scenarios on the pro demo site — leave, purchase, reimbursement, seal, overtime, contract, business trip, transfer, asset — all reduce to four JSON patterns.
Version management keeps multiple process definitions live; in-flight instances run against their launch version, new instances pick up the highest version automatically.
The process designer is a standalone npm package that produces JSON and has no backend dependency — any engine that consumes the JSON contract can run the output.
Conclusions

Reducing a workflow engine's entire runtime definition to four node types is a deliberate constraint that shifts complexity from the engine into the expression layer and SPI implementations, where it's easier to test and swap.

The cross-language difference in countersign task creation — batch in Java, sequential in Python/Node — exposes a design principle: the JSON contract defines what must happen, not how the engine achieves it internally.

Version management that pins in-flight instances to their launch version while routing new instances to the latest is a practical answer to a problem many workflow systems over-engineer with migration scripts and state transformation logic.

Concepts & terms
IExpressionEvaluator SPI
A pluggable interface in jeeflow that evaluates routing expressions on decision nodes and conditional edges. The engine ships no built-in expression language; implementations can use SpEL, OGNL, or custom evaluators.
IUserProvider SPI
A service provider interface that resolves the assignee field on task nodes into actual user identities at runtime. It can return fixed lists, query department roles, or evaluate dynamic expressions.
Countersign (会签)
A workflow pattern where multiple people must approve a single task. jeeflow supports three modes: parallel (everyone receives tasks simultaneously), sequential (one after another), and ratio (a configurable percentage must approve).
Aggregate Root (聚合根)
In Domain-Driven Design, the single entity within an aggregate that external objects can reference directly. The next article in this series argues ProcessInstance must be the aggregate root, with ProcessTask as a child entity.
From the discussion
Featured comments
Iism

I want to ask, if from a certain step, it's sent back to the previous step, how does jeeflow draw this?

mldong

The flowchart in the process designer only defines the forward flow path (from start to end). Sending back/rejecting does not need to draw reverse lines on the diagram. Sending back is a runtime behavior, not a design-time connection: When handling, the front end provides a 'Send Back' button. After clicking, the engine automatically lists all nodes that have been traversed (jumpAbleTaskNameList), and the approver chooses which step to send back to. The engine jumps back to the target node and regenerates the to-do item. The flowchart's responsibility is to define 'how the process goes'; which step to send back to is decided by the approver at runtime and does not need to be pre-drawn in the designer.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗