Flowable Workflow Engine: Modular Architecture and Design Principles
The article explains how Flowable, a fork of Activiti, achieves high extensibility and performance through a four‑layer modular architecture, MyBatis‑based persistence, core engine services, dual Java/REST APIs, pluggable extensions, a detailed state machine, async execution, transaction safety, and practical tuning tips.
Flowable is presented as a lightweight, highly extensible BPM platform derived from Activiti. Its technical architecture is divided into four decoupled layers:
1. Persistence Layer
Implemented with MyBatis, it supports multiple databases (H2, MySQL, Oracle, etc.) and organizes core tables by function:
ACT_RE_* stores process definitions (e.g., ACT_RE_PROCDEF holds BPMN 2.0 metadata).
ACT_RU_* records runtime data (e.g., ACT_RU_EXECUTION tracks instance state).
ACT_HI_* keeps historical execution records (e.g., ACT_HI_TASKINST logs task completion time).
Standardized SQL mappings make data portable; an example INSERT into ACT_RE_PROCDEF shows how a deployment creates a record.
INSERT INTO ACT_RE_PROCDEF (ID_, REV_, KEY_, VERSION_, DEPLOYMENT_ID_, RESOURCE_NAME_)
VALUES ('process-1:1:123', 1, 'orderProcess', 1, '1', 'order-process.bpmn20.xml');2. Core Engine Layer
The engine provides five core services:
RepositoryService : manage process definitions (deploy/query/delete).
RuntimeService : control process instances (start/suspend/delete).
TaskService : manage task lifecycle (assign/complete/transfer).
HistoryService : query historical data for audit and analysis.
ManagementService : configure engine jobs and database settings.
Operations are intercepted via the Command Pattern; for example, StartProcessInstanceCmd validates the definition, creates the execution tree, triggers start events, and runs initial activities.
public class StartProcessInstanceCmd implements Command<ProcessInstance> {
public ProcessInstance execute(CommandContext context) {
// 1. Validate process definition
// 2. Create execution tree
// 3. Trigger start event
// 4. Execute initial activity
}
}3. Service Layer
Two API families are exposed:
Java API : accessed through the ProcessEngine entry point.
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = engine.getRuntimeService();
ProcessInstance instance = runtimeService.startProcessInstanceByKey("loanApproval");REST API : HTTP calls, e.g.,
curl -X POST http://localhost:8080/flowable-rest/service/runtime/process-instances \
-H "Content-Type: application/json" \
-d '{"processDefinitionKey":"loanApproval","variables":[{"name":"amount","value":5000}]}'4. Extension Layer
Flowable offers three independent engines that can be plugged via SPI:
IDM (identity management) – LDAP/database authentication.
Form – dynamic form rendering.
App – low‑code application builder.
Custom extensions such as a task‑assignment listener are implemented by implementing TaskListener:
public class CustomTaskAssignmentListener implements TaskListener {
public void notify(DelegateTask task) {
if ("approval".equals(task.getTaskDefinitionKey())) {
task.setAssignee(findApproverByRiskLevel(task));
}
}
}Core Execution Mechanism
State Machine
Six states are defined: Created, Running, Suspended, Completed, Terminated, Deleted. An example loan‑approval process illustrates state transitions (image omitted).
Asynchronous Execution
The AsyncExecutor processes timer jobs, async service tasks, and history batch jobs using a configurable thread pool:
flowable:
async-executor:
core-pool-size: 10
max-pool-size: 20
queue-capacity: 1000
async-job-acquire-wait-time: 1000Transaction Consistency
All engine operations run inside a CommandContext that binds a database transaction, ensuring atomicity. Sample code shows a transaction that updates runtime tables, inserts history records, signals execution, and commits or rolls back on error.
// Transaction boundary
transaction.begin();
try {
update ACT_RU_TASK set status='completed';
insert ACT_HI_TASKINST (...);
signal ACT_RU_EXECUTION (...);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
}Extension Mechanisms
JavaDelegate : implement service tasks.
TaskListener : hook into task lifecycle.
ExecutionListener : listen to node events.
ModelChecker : validate process definitions.
Event‑driven architecture uses the observer pattern; a custom FlowableEventListener logs task completion events.
public class AuditLogger implements FlowableEventListener {
public void onEvent(FlowableEngineEvent event) {
if (event.getType() == FlowableEngineEventType.TASK_COMPLETED) {
log.info("Task {} completed by {}", event.getProcessInstanceId(), ((TaskEntity)event.getEntity()).getAssignee());
}
}
}Dynamic process modification is supported at runtime, e.g., adding a user task or updating a variable:
// Add a user task dynamically
runtimeService.addUserTask(processInstanceId, "emergencyReview", "紧急审批", Arrays.asList("manager", "director"));
// Update a variable
runtimeService.setVariable(processInstanceId, "priority", "HIGH");Practical Guidelines
Process Design : limit a single process to ≤30 nodes; move complex logic to DMN decision tables.
Error Handling : use error boundary events for unified exception capture.
Version Management : store multiple versions using the VERSION_ column and query with ORDER BY VERSION_ DESC.
Performance Tuning :
Create indexes on high‑frequency query columns (e.g., idx_flowable_ru_exec on ACT_RU_EXECUTION(PROC_INST_ID_)).
Mark long‑running tasks as async using flowable:async="true" and specify the implementation class.
Archive historic data regularly with a scheduled job:
@Scheduled(fixedRate = 86400000)
public void archiveHistoricData() {
historyService.deleteHistoricProcessInstancesByCompletionDate(
LocalDate.now().minusDays(30).atStartOfDay(),
LocalDate.now().atStartOfDay());
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer1970
Formerly called 'Code to 35'. Add our main WeChat ID to access a wealth of shared resources (algorithms, interview prep, tech stacks: Java, Python, Go, big data). We mainly share serious development techniques, focusing on output-driven input. Occasionally we post life snippets and gossip. Our aim is to attract precise traffic and test advertising opportunities.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
