A 98KB Workflow Engine That Ships in Four Languages
jeeflow series · Part 2 (The 'Getting to Know' Season)
First, get a sense of what 98KB means:
- A high-resolution phone photo is typically 2–5MB, 20–50 times larger;
- A single Chinese font file often runs to tens of MB;
- A JS framework you load in the browser might be bigger than this.
Yet the entire engine core of jeeflow is only 98KB — in the previous article, we discussed why we built a lightweight engine from scratch in the Flowable era (see Part 1 for the selection rationale). This article tears it open directly: what exactly is inside a zero-dependency workflow engine, and how does it manage to run approvals?
This article aligns with jeeflow v1.8.11 (the four-language + documentation site synchronized release, published to Maven Central / PyPI / npm / Go proxy). This series continues from the mldong rapid development framework ecosystem; reading in order is recommended: Part 0 covers the backstory, Part 1 covers the selection rationale, and this part covers "what it is."
1. The Overall Picture: Core + Periphery, Clear Boundaries
The jeeflow-java repository is divided into modules by responsibility, with the engine core strictly isolated from the periphery:
jeeflow-core ← Engine core, 98KB, depends only on slf4j-api (provided)
├── Engine entry points: 5 methods — start / execute / jump, etc.
├── DDD Aggregate Roots: ProcessInstance / ProcessTask
├── Node Processors: task / decision / fork / join / countersign / custom
├── SPI System (see Section 3)
└── State Machine: Instance 7 states · Task 6 states (aligned with boot3 full enums since v1.4.0)
jeeflow-repository-jdbc ← Pure JDBC repository implementation (8 tables, whitelist injection prevention)
jeeflow-persist ← Business data persistence (SYNC synchronous evolution + metadata-driven read/write, v1.6.2+)
jeeflow-spring-boot-autoconfigure ← Spring auto-configuration + m_* query parsing
jeeflow-spring-boot2/3/4-starter ← Three Starters for Boot 2.x / 3.x / 4.x
jeeflow-demo-boot4 ← Demo site (integrated with the unified frontend jeeflow-ui)
Key design: the engine core knows nothing about Spring, nothing about MyBatis, nothing about any JSON library. It only knows interfaces — that is the secret of 98KB.
2. Running in 5 Lines of Code
In Java, a complete, runnable engine instance looks like this (excerpted from the README):
Configuration config = new Configuration();
ServiceContext.put("repository", new MemoryProcessRepository());
ServiceContext.put("json", new BuiltinJsonProvider());
JeeflowEngine engine = new JeeflowEngineImpl();
engine.configure(config);
ProcessInstance pi = engine.startProcessInstanceById(defineId, "张三", FlowData.create());
No Spring startup, no XML, no table creation scripts — an in-memory repository plus a built-in JSON parser runs it directly. Need to connect a database? Swap in a different repository implementation — the engine side requires zero changes.
3. The Secret of Zero Dependencies: The SPI System
The engine core depends on no specific technology because it abstracts every "variable part" into interfaces. As of v1.8.4, the SPI system is divided into three layers:
Core 6 SPIs (required or commonly used for engine operation):
| SPI Interface | Responsibility | Required |
|---|---|---|
IProcessRepository |
Aggregate repository (5 core tables: define/instance/task/task_actor/cc) | ✅ |
IJsonProvider |
JSON parsing (engine core does not bind to any JSON library) | ✅ |
IUserProvider |
User information (fetches userId/realName/dept/post in one call) | Optional |
IExpressionEvaluator |
Decision/countersign expression evaluation | Optional |
IIdGenerator |
ID generation | Optional |
ITransactionTemplate |
Transaction template (engine is transaction-unaware; business layer wraps it) | Optional |
Capability Extension SPIs (integrated on demand):
| SPI | Version | Responsibility |
|---|---|---|
IOrgUserProvider |
v1.6.0 | Organizational user retrieval: department head / supervisor / by role |
IUserSearchProvider |
v1.2.0 | Paginated search for execution candidates |
IDynamicMetaProvider |
v1.7.0 | Metadata provider (shared by persistence read/write) |
IActionPermissionProvider |
v1.8.3 | Facade action permission codes (default wf:{action:/→:}) |
IProcessExtRepository |
v1.1.0 | Management extension repository (design/history/delegation) |
Want to use Fastjson? Use Fastjson. Want Jackson? Use Jackson. Want to connect MySQL? Connect MySQL. Want to run tests in memory? Run in memory — swap any component, the engine does not move. This is the full meaning of "zero dependencies": not that there are no dependencies, but that all dependencies are pluggable.
4. Data: 5 Core Tables + 3 Management Tables
The database layer is equally restrained: 5 core tables (wf_process_define / wf_process_instance / wf_process_task / wf_process_task_actor / wf_process_cc_instance), plus 3 management extension tables since v1.1.0 (wf_process_design / wf_process_design_his / wf_process_surrogate for design drafts/history/delegation) — 8 tables in total, compared to Flowable's 70+, the schema is clear at a glance.
5. DDD Rich Domain Model: State Transitions Are Domain Behavior
The engine is not "a CRUD script operating on a database" but a DDD rich domain model:
ProcessInstance(Aggregate Root): holds instance state and task list, encapsulates all command behaviors —completeTask,finish,reject,abandonAllDoing… how the state changes is decided by the aggregate root;ProcessTask(Sub-entity): the task's own behaviors —finish(complete),abandon(discard),isAllowed(permission check).
The state machine is two-layered and more complete than most would imagine: Instance 7 states (In Progress / Completed / Rejected + Withdrawn / Terminated / Suspended / Abandoned), Task 6 states (Pending / Completed / Abandoned + Withdrawn / Terminated / Suspended cascading). The next article, "The 'Soul' of a Workflow Engine," will dissect this specifically.
6. 9 Built-in Process Patterns + One Unified Facade
Start, End, Task, Decision (expression routing), Fork, Join, Sub-process, Custom Node (customClass business takeover), Countersign (parallel/serial/by ratio) — covering over 95% of approval scenarios in business systems.
Above the engine sits a JeeflowFacade.flow(action, map) unified facade (since v1.1.0, now containing 40 actions): aligned with all boot2/boot3 endpoints — definition/instance/task/design/delegation/view. Integrators only need a single forwarding controller that passes the body JSON into the facade — one line of code to access all workflow capabilities. This is the engineering embodiment of "contract alignment" (detailed in Season 4).
7. It's Not Just a Java Library
jeeflow's engine semantics exist simultaneously in Java / Go / Python / Node implementations — the same process JSON produces identical results across four languages, with the frontend jeeflow-ui connecting to four backends through a single interface. 98KB is the size of the Java core, but the "multi-language federation" behind it is the project's true differentiator — that is the story of Season 3.
Conclusion
What can 98KB hold? A DDD-designed engine core, a pluggable SPI system, 9 process patterns, a 7+6 dual-layer state machine — what it cannot hold is any framework baggage. And it evolved from 1.0.0 to 1.8.11 in six days (management extensions, persistence, metadata, permission system, facade 40 actions with full interface documentation), with the core still at 98KB.
Next article preview: "The 'Soul' of a Workflow Engine: State Machine and submitType" — why does an instance have 7 states? How do 8 submission types drive state transitions? The underlying logic of rollback, jump, and countersign rejection explained thoroughly in one go.
Related Links
- jeeflow documentation site: https://jeeflow-doc.mldong.com
- Engine repositories (GitHub · mldong organization):
jeeflow-java/jeeflow-go/jeeflow-python/jeeflow-node/jeeflow-ui