Zum Hauptinhalt springen
Intelligence View
⚡ tsecurity.de Intelligence

Solon Flow: Lightweight Process Orchestration Without BPMN XML

When you need process orchestration — approval workflows, business rules, data pipelines — the usual answer is a heavyweight engine: BPMN 2.0 XML, database schemas, a management UI, and a framework that drags in half of enterprise Java.

Solon Flow takes a different approach. It's a ~200KB engine that treats process definitions as flat YAML or JSON, runs without a database, and lets you resume interrupted processes from a JSON snapshot. You can embed it in any JVM framework — Solon, Spring Boot, Quarkus, or even a plain main() method.

This article walks through the core API, node types, context persistence, and driver customization — all verified against the official documentation at solon.noear.org.

Getting Started

Add the dependency:

<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-flow</artifactId>
</dependency>

Define a flow in YAML (flow/demo1.yml):

id: "c1"
layout:
  - { id: "n1", type: "start", link: "n2" }
  - { id: "n2", type: "activity", link: "n3", task: 'System.out.println("hello world!");' }
  - { id: "n3", type: "end" }

Load and execute:

FlowEngine engine = FlowEngine.newInstance();
engine.load("classpath:flow/demo1.yml");
engine.eval("c1");

That's it. No database, no XML schema, no deployment step.

In a Solon application, you can inject the engine directly and let it auto-load flow definitions:

solon.flow:
  - "classpath:flow/*.yml"
@Component
public class DemoCom implements LifecycleBean {
    @Inject
    private FlowEngine flowEngine;

    @Override
    public void start() throws Throwable {
        flowEngine.eval("c1");
    }
}

The engine scans all matching files on startup, so adding a new flow is just dropping a YAML file.

Node Types

Solon Flow supports seven node types via the NodeType enum:

Type Description Task Condition Parallel In Out
start Entry point 0 1
activity Default node Yes 1..n 1..n
exclusive Exclusive gateway (if/else) Yes Yes 1..n 1..n
inclusive Inclusive gateway (multi-select) Yes Yes 1..n 1..n
parallel Parallel gateway (fork/join) Yes Yes 1..n 1..n
loop Loop gateway (iteration) Yes 1 1
end Exit point 1..n 0

Key rules:

  • start and end are required (though in simplified mode, the engine auto-generates them).
  • inclusive, parallel, and loop must be used in pairs (open and close).
  • exclusive supports a default branch (the link without a when condition).

Exclusive Gateway Example

id: "approval"
layout:
  - { type: start, link: g1 }
  - { type: exclusive, id: g1, link: [n1, { nextId: n2, when: "day >= 3" }] }
  - { type: activity, id: n1, task: "@tl_approve", link: e }
  - { type: activity, id: n2, task: "@dm_approve", link: e }
  - { type: end, id: e }

When day >= 3, the flow goes to n2 (department manager). Otherwise, it takes the default path to n1 (team lead).

Parallel Gateway Example

id: "parallel_demo"
layout:
  - { type: start, link: g1 }
  - { type: parallel, id: g1, link: [n1, n2] }
  - { type: activity, id: n1, task: "@credit_check", link: g2 }
  - { type: activity, id: n2, task: "@fraud_check", link: g2 }
  - { type: parallel, id: g2 }
  - { type: end }

Both n1 and n2 execute. The second parallel node (g2) waits for all incoming links to arrive before proceeding.

Loop Gateway Example

id: "loop_demo"
layout:
  - { type: start, link: g1 }
  - { type: loop, id: g1, meta: { "$for": "id", "$in": "idList" } }
  - { type: activity, task: "@process_item" }
  - { type: loop }
  - { type: end }

The $for key names the loop variable pushed into context. The $in key accepts a variable name, a static array like [1,2,5,8], or a numeric range like "1:9:2".

Task and Condition Description Formats

Each node's task and when fields support multiple description styles:

Prefix Style Example
@ Component lookup from container @risk_score
# Cross-graph sub-process call #sub_flow_1
$ Script from graph meta $script.validator
(none) Inline script (default: full Java syntax via Liquor) order.setScore(1);

For conditions, the same @ prefix looks up a ConditionComponent. Without a prefix, it's an inline expression evaluated by the built-in SnEL engine.

Component-Based Tasks

Instead of inline scripts, you can delegate to a container-managed component:

@Component
public class RiskScoreTask implements TaskComponent {
    @Override
    public void run(FlowContext context, Node node) throws Throwable {
        int score = calculateScore(context.get("user"));
        context.put("score", score);
    }
}

Reference it in YAML:

- { type: activity, task: "@RiskScoreTask", link: g1 }

The @ prefix tells the driver to look up RiskScoreTask from the container (Solon's SolonContainer by default, or a MapContainer for non-Solon environments).

FlowContext: Variables, Persistence, and Recovery

FlowContext is the runtime state carrier. It holds variables, provides an event bus, and supports serialization for pause/resume.

Passing Data Through Context

FlowContext context = FlowContext.of();
context.put("amount", 1500);

flowEngine.eval("c1", context);

int score = context.getAs("score");

Inside the flow, scripts can access variables directly:

layout:
  - task: 'context.put("result", amount * 0.1);'

Interrupting and Resuming

Any task can call context.stop() to halt execution:

spec.addActivity("n3").task((ctx, node) -> {
    if (!ctx.getOrDefault("approved", false)) {
        ctx.stop();  // Halt here
    }
}).linkAdd("n4");

Serialize the state:

String snapshot = context.toJson();
db.save(context.getInstanceId(), snapshot);

Later, restore and resume:

FlowContext restored = FlowContext.fromJson(snapshot);
restored.put("approved", true);
flowEngine.eval(graph, restored);  // Resumes from n3

The engine tracks the last executed node (context.lastNodeId()) and automatically continues from the interruption point. This works because toJson() captures the full execution trace and variable state.

Event Bus

FlowContext includes a built-in event bus (backed by DamiBus) for decoupled communication between flow nodes and external listeners:

// Inside a task
context.eventBus().send("notification.topic", "order approved");

// External listener
context.eventBus().listen("notification.topic", event -> {
    System.out.println(event.getContent());
});

For synchronous request-reply:

String reply = context.eventBus()
    .<String, String>call("validation.topic", order)
    .get();

Building Graphs in Code

For dynamic flows or test cases, you can construct graphs programmatically using the Fluent API:

Graph graph = Graph.create("approval", spec -> {
    spec.addStart("s")
        .title("Initiator")
        .metaPut("role", "employee")
        .linkAdd("n1");

    spec.addActivity("n1")
        .title("Team Lead")
        .metaPut("role", "tl")
        .linkAdd("g1");

    spec.addExclusive("g1")
        .linkAdd("e", l -> l.title("Under 3 days"))
        .linkAdd("n2", l -> l.title("3+ days").condition("day >= 3"));

    spec.addActivity("n2")
        .title("Department Manager")
        .metaPut("role", "dm")
        .linkAdd("g2");

    spec.addExclusive("g2")
        .linkAdd("e", l -> l.title("Under 7 days"))
        .linkAdd("n3", l -> l.title("7+ days").condition("day >= 7"));

    spec.addActivity("n3")
        .title("VP")
        .metaPut("role", "vp")
        .linkAdd("e");

    spec.addEnd("e");
});

flowEngine.eval(graph, FlowContext.of());

The GraphSpec builder mirrors the YAML structure exactly — addStart, addActivity, addExclusive, addParallel, addInclusive, addLoop, addEnd — with fluent chaining for title(), task(), when(), metaPut(), linkAdd(), and condition().

Driver Customization

The FlowDriver interface is the execution engine's extension point. Think of it like a JDBC driver — same engine, different behavior.

public interface FlowDriver {
    void onNodeStart(FlowExchanger exchanger, Node node);
    void onNodeEnd(FlowExchanger exchanger, Node node);
    boolean handleCondition(FlowExchanger exchanger, String condition);
    void handleTask(FlowExchanger exchanger, String task);
    void postHandleTask(FlowExchanger exchanger, String task);
}

The default implementation, SimpleFlowDriver, supports:

  • Evaluation: Inline script execution via pluggable engines (Liquor for full Java syntax, Aviator, Beetl, or Magic)
  • Container: Component lookup via MapContainer (no framework) or SolonContainer (Solon IoC)
  • Executor: Custom thread pool for parallel node execution
SimpleFlowDriver driver = SimpleFlowDriver.builder()
    .evaluation(new LiquorEvaluation())
    .container(new SolonContainer())
    .executor(Executors.newVirtualThreadPerTaskExecutor())
    .build();

FlowEngine engine = FlowEngine.newInstance(driver);

This is how Solon Flow adapts to different use cases: a workflow engine, a rules engine, a data pipeline, or an AI orchestration layer — all by swapping the driver.

Interceptors

FlowInterceptor provides cross-cutting concerns — logging, metrics, access control:

engine.addInterceptor(new FlowInterceptor() {
    @Override
    public void onNodeStart(FlowContext context, Node node) {
        System.out.println("Starting: " + node.getId());
    }

    @Override
    public void onNodeEnd(FlowContext context, Node node) {
        System.out.println("Completed: " + node.getId());
    }
});

Interceptors run on every node transition, regardless of which driver is active.

Simplified Mode

For quick prototypes or single-task flows, Solon Flow can infer start and end nodes:

id: "quick"
layout:
  - { task: 'System.out.println("just one step");' }

The engine auto-generates a start before this node and an end after it. Node IDs are auto-assigned as n-1, n-2, etc.

What Makes Solon Flow Different

Aspect Solon Flow Traditional BPM Engines
Definition format Flat YAML/JSON BPMN 2.0 XML
Database dependency Optional (in-memory or Redis) Required
Framework coupling None (works in any JVM) Usually tied to a runtime
Script engine Pluggable (Liquor/Aviator/Beetl/Magic) Fixed
Persistence JSON snapshot (toJson() / fromJson()) Database state tables
Footprint ~200KB 10MB+
Resume mechanism Context deserialization + eval() Session recovery from DB

Solon Flow doesn't aim to replace full-featured BPM platforms. It targets scenarios where you need orchestration logic — approval chains, rule evaluation, data processing pipelines — without the operational overhead of a dedicated BPM server.

Workflow Extension

For approval-style workflows with task assignment, the optional solon-flow-workflow plugin adds:

  • WorkflowExecutor — orchestrates human-task flows
  • StateController variants: BlockStateController, NotBlockStateController, ActorStateController
  • StateRepository implementations: InMemoryStateRepository, RedisStateRepository
  • Task lifecycle: findTask(), claimTask(), completeTask()
<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-flow-workflow</artifactId>
</dependency>

Conclusion

Solon Flow brings three things to process orchestration:

  1. Simplicity — flat YAML, no XML schema, no database required
  2. Embeddability — runs anywhere a JVM runs, in any framework
  3. Resumability — JSON snapshots for pause/resume without infrastructure

If you're building approval flows, rule engines, or data pipelines in Java and find traditional BPM engines too heavy, Solon Flow is worth a look.

Documentation: solon.noear.org/article/learn-solon-flow
Source: github.com/opensolon/solon-flow

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-61591 | djust provides Phoenix LiveView-style reactive server-side rendering for…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Rechts: Artikel Ziehen Links: RSS
Hoch: nächster Artikel Runter: zurück / schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Rechts: Original Links: RSS-Ansicht
↗ Original-Quelle
Social Reaktionen Stimme abgeben (+5 Karma)
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick