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.

Programmer1970
Programmer1970
Programmer1970
Flowable Workflow Engine: Modular Architecture and Design Principles

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: 1000

Transaction 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());
}
Process state transition diagram
Process state transition diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaMyBatisbpmflowableProcessEngineAsyncExecutorCommandPattern
Programmer1970
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.