Online edition for BLOGE
0.9.8-RC1· facts verified 2026-09-15 · 中文
Chapter 33 — Migration and Comparison
Promise: By the end of this chapter you will be able to state constraints before choosing BLOGE, name at least three cases where BLOGE should not be chosen, and complete a short ADR with evidence, a counterfactual, and a first migration probe.
Learning Goals
- Restate a tool-selection question as deployment, ownership, recovery, verification-governance, and ecosystem constraints.
- Eliminate unsuitable candidates with hard stops instead of totaling feature check marks.
- Separate “the feature exists,” “the team can operate it,” and “the evidence qualifies a release.”
- Design a reversible first migration step instead of using a full rewrite to test the choice.
- Write a short ADR containing the choice, evidence, counterfactual, migration probe, and exit criteria.
Prerequisites
- Chapter 20 — Spring and Production Wiring —
you need the
bloge-springwiring model to understand how BLOGE embeds into a running application. - Chapter 13 — Durable Execution — checkpoint stores, suspend/resume, and recovery.
- Chapter 21 — Scheduling and Complexity in One JVM — engine statelessness, virtual-thread scheduling, and sharding.
Source Examples
| File | What it shows |
|---|---|
BpmnToDslExample.java | End-to-end: translate BPMN XML → .bloge DSL → compile → execute |
bloge-bpmn-transformer/README.md | Translation pipeline, supported scope, diagnostics, and known limitations |
order-process.bloge | Canonical order-processing graph — parallel fetch, branch, resilience |
OrderProcessingDslExample.java | DSL compilation, registry wiring, and execution from Java |
order-saga.bloge | Saga-style compensation: compensate on each node |
payment-wait.bloge | Long-running await with event correlation and timeout |
batch-order-processing.bloge | foreach parallel fan-out over a collection |
SpringTicketTriageService.java | Injecting GraphEngine and List<Graph> into a Spring service |
| ADR-001 | Core zero-dependency decision |
| ADR-002 | Virtual threads over CompletableFuture |
| ADR-004 | HCL-inspired DSL over YAML/JSON |
Why This Matters
You have spent sixteen chapters learning how to build, test, and run BLOGE workflows. Now a harder question arrives: should you use BLOGE for this particular project?
Teams rarely start with a blank slate. They have:
- Existing service methods with embedded orchestration logic.
- Spring Batch jobs handling step-oriented batch processing.
- BPMN diagrams authored in a visual modeller.
- Or they are evaluating workflow-as-code platforms that require a separate server cluster.
This chapter gives you a structured way to compare what BLOGE actually does against what those alternatives actually do — grounded in architectural properties you can point to in the repository, not marketing claims.
Mental Model
Think of orchestration tools as sitting on two axes:
Vertical axis — workflow shape visibility. The top half captures the workflow structure in a model (XML, DSL, or visual graph). The bottom half embeds structure entirely in code.
Horizontal axis — deployment topology. The left requires a separate server cluster. The right embeds into your existing application process.
BLOGE sits in the upper-right quadrant: a declarative DAG model that runs as a library inside your JVM, with no external infrastructure required by default.
This is not universally best — it is a specific set of trade-offs:
| Property | BLOGE's choice | Where it is documented |
|---|---|---|
| Zero external dependencies in core | bloge-core depends on java.base alone | ADR-001 |
| Virtual-thread execution | Each node runs in its own virtual thread | ADR-002 |
| HCL-inspired DSL | Block-oriented, expression-capable, comment-friendly | ADR-004 |
| Checkpoint-based durability | Opt-in via bloge-durable; stores are pluggable | Chapter 13, README |
| Embeddable engine | GraphEngine is a stateless library object | Chapter 21 |
| Resilience per node | Retry → Timeout → Fallback chain, per node | ADR-007 |
Three orchestration modules expand the platform beyond graph-only orchestration:
| Module | What it adds |
|---|---|
bloge-agent-ext | LLM-driven agent loops with agent DSL, tool dispatch, memory strategies, and streaming |
bloge-session-durable | DurableSessionManager — crash-safe multi-turn sessions with checkpoint persistence |
bloge-state-durable | DurableStateMachineManager — crash-safe state machines with checkpoint persistence |
Constraints Before Comparisons
The loan platform has just completed business-correctness verification. The architecture board is not asking “how many features does BLOGE have?” It is asking: under the project's non-negotiable constraints, can BLOGE become an operable, verifiable, and reversible choice?
Write hard constraints first, evaluate preferences second, and design a migration probe last. A candidate that violates a hard constraint stops; other feature scores cannot average the conflict away.
| Constraint | Loan-platform question | BLOGE fact | Decision meaning |
|---|---|---|---|
| Deployment topology | Can the deployment add a workflow cluster? | Engine embeds in the JVM; durable stores are optional | Still a candidate when new clusters are forbidden |
| Model ownership | Who reviews and changes the flow? | .bloge is an explicit, code-friendly DAG | A conflict when the business must own BPMN directly |
| Recovery semantics | Does recovery require checkpoints or full history replay? | BLOGE checkpoints; it is not a general history-replay platform | Stop when replay is mandatory |
| Verification governance | Must releases bind approved Scenarios, evidence, and source identity? | RC1 provides Scenario/Policy/Fixture, source-bound evidence, and claims | Supports the gate, but still needs organisational owners |
| Ecosystem boundary | Must workers run natively in several languages? | The primary runtime is JVM-based | Stop when polyglot workers are mandatory |
This matrix does not produce a fake score such as “BLOGE: 82.” It produces
STOP, PROBE, or ADOPT. PROBE means a decisive fact is unknown and must
be measured; it is not a polite pass.
Three Clear Reasons Not to Choose BLOGE
- A non-development team directly owns BPMN models. If visual collaboration, human-task inboxes, and the existing process server are organisational contracts, migration breaks ownership; it does not merely replace syntax.
- The recovery contract requires complete history replay. When audit or disaster recovery must reconstruct every step from event history, checkpoint recovery cannot substitute for replay merely because both can resume work.
- Polyglot workers are a hard constraint. If one orchestration must natively dispatch Go, Python, and TypeScript workers, an embedded JVM model transfers the adapter cost to the team.
There is also a simpler stop: when the flow is three sequential calls with no waiting, branching, independent recovery, or shared governance need, a normal method is cheaper. Architectural capability must pay for itself by reducing real complexity.
A Completed Loan-Platform ADR
The loan platform forbids a new standalone cluster, runs on the JVM, and
requires every release to bind approved Scenarios to source evidence. Full
history replay and polyglot workers are not current requirements. The decision
is therefore PROBE BLOGE, not immediate fleet-wide adoption.
| ADR field | This decision |
|---|---|
| Choice | Run a two-week reversible probe on the auto-approve path |
| Evidence | JVM embedding, explicit DAGs, RC1 verification, and source-bound evidence meet hard constraints |
| Counterfactual | If history replay or non-JVM workers become mandatory, choose a server-based workflow platform |
| First migration step | Move read-only risk checks into operators; leave the disbursement effect on the old path |
| Exit criteria | Stop expansion if the graph contract, business Scenario, or recovery drill fails |
The ADR does not “approve BLOGE.” It turns the next step into a falsifiable
experiment. A green graph test cannot replace a business Scenario, and a
PASS cannot replace source binding and release governance.
First Working Example
The most common migration scenario is replacing a hand-written Java orchestration method with a BLOGE graph. The migration below proceeds step by step.
Before: hand-written glue code
// Typical procedural orchestration buried in a service method
public OrderResult processOrder(String userId, List<String> productIds) {
// 1. Sequential — but these two calls are independent
User user = userService.fetchUser(userId);
List<Product> products = productService.fetchProducts(productIds);
// 2. Retry logic tangled with business code
BigDecimal total = null;
for (int attempt = 0; attempt < 3; attempt++) {
try {
total = pricingService.calculate(user, products);
break;
} catch (Exception e) {
if (attempt == 2) throw e;
Thread.sleep(100 * (attempt + 1));
}
}
// 3. Branch logic buried in an if-statement
CreditResult credit = creditService.check(user.id(), total);
if (credit.approved()) {
return orderService.create(user, total);
} else {
return orderService.reject(user.id(), credit.reason());
}
}
Problems:
- Steps 1 and 2 are independent but execute sequentially.
- Retry logic is hand-rolled and mixed into the business method.
- No one can see the workflow shape without reading every line.
After: BLOGE graph
The same workflow, expressed as
order-process.bloge:
The 46-line graph stays whole because the comparison needs one copyable unit that exposes parallel fetch, fan-in, resilience, and both branch targets.
graph orderProcess {
node fetchUser : FetchUserOperator {
input { userId = ctx.userId }
timeout = 3s
retry = { attempts: 2, backoff: 200ms, strategy: exponential }
}
node fetchProducts : FetchProductsOperator {
input { productIds = ctx.productIds }
timeout = 5s
}
node calcPrice : CalcPriceOperator {
depends_on = [fetchUser, fetchProducts]
input {
user = fetchUser.output
products = fetchProducts.output
}
}
node checkCredit : CreditCheckOperator {
depends_on = [fetchUser, calcPrice]
input {
userId = fetchUser.output.id
amount = calcPrice.output.total
}
retry = { attempts: 3, backoff: 100ms, strategy: jitter }
fallback = { approved: false, reason: "credit service unavailable" }
}
branch on checkCredit.output.approved {
true -> createOrder
false -> rejectOrder
}
node createOrder : CreateOrderOperator {
depends_on = [calcPrice]
input { user = fetchUser.output, price = calcPrice.output }
}
node rejectOrder : RejectOrderOperator {
depends_on = [checkCredit]
input { userId = fetchUser.output.id, reason = checkCredit.output.reason }
}
}
What changed:
fetchUserandfetchProductsrun in parallel — the engine sees they have no dependency on each other.- Retry and timeout are declared on the node, not coded in a loop.
- The branch is a first-class routing decision, not a hidden
if. - The shape is visible, reviewable, and toolable.
The same registry/engine wiring looks like this when the DSL lives in a file:
var registry = new DefaultOperatorRegistry();
registry.register("FetchUserOperator", fetchUserOp);
registry.register("FetchProductsOperator", fetchProductsOp);
// … register remaining operators …
Graph graph = new GraphLoader(registry).load(
Path.of("bloge-examples/src/main/resources/bloge/order-process.bloge"));
GraphEngine engine = GraphEngine.builder().registry(registry).build();
GraphResult result = engine.execute(graph, new GraphContext(Map.of(
"userId", "user-42",
"productIds", List.of("prod-1", "prod-2", "prod-3")
)));
Break It Apart
BLOGE vs hand-written orchestration
| Dimension | Hand-written glue code | BLOGE graph |
|---|---|---|
| Concurrency | You manage threads, pools, futures | Engine schedules virtual threads from the DAG shape (ADR-002) |
| Resilience | Hand-rolled retry loops, try/catch fallbacks | Declared per node: retry, timeout, fallback (ADR-007) |
| Visibility | Read the code; hope the comments are accurate | The .bloge file is the documentation; Studio renders it visually |
| Testability | Mock every service call in each test | MockOperator, GraphTestRunner, DslTestHelper from bloge-test |
| Durability | Build your own checkpoint/recovery | Opt-in via bloge-durable stores (Chapter 13) |
When hand-written code is fine: If the workflow is three sequential calls with no retry, no branching, and no durability need, a plain method is simpler. BLOGE adds value when the orchestration shape becomes complex enough that you need concurrency, resilience, or visibility.
BLOGE vs step-oriented batch frameworks
Step-oriented batch frameworks (like Spring Batch) model work as a linear pipeline of steps with chunk-oriented reading, processing, and writing.
| Dimension | Step-oriented batch | BLOGE graph |
|---|---|---|
| Execution model | Step 1 → Step 2 → Step 3, linear | DAG: independent nodes run in parallel automatically |
| Iteration | Chunk-oriented reader/processor/writer | foreach with parallel or sequential mode (batch-order-processing.bloge) |
| Resilience | Skip/retry policies at the step or chunk level | Per-node retry → timeout → fallback chain |
| Long-running waits | Not a primary concern | await with event correlation and timeout (payment-wait.bloge) |
| Dependencies | Typically requires the framework runtime | bloge-core has zero external dependencies (ADR-001) |
When step-oriented batch is a better fit: If your workload is pure ETL — read chunks, transform, write — with no fan-out, no branching, and no external-event waits, a batch framework is purpose-built for that pattern. BLOGE adds value when batch steps need to fan out in parallel, wait for external signals, or share orchestration patterns with non-batch workflows.
BLOGE vs BPMN-oriented tools
BPMN-oriented tools model workflows in a visual XML-based notation, typically backed by a separate execution server.
| Dimension | BPMN-oriented tools | BLOGE graph |
|---|---|---|
| Workflow notation | BPMN 2.0 XML (verbose, machine-oriented) | .bloge DSL (expression-capable, comment-friendly; ADR-004) |
| Gateways | Explicit fork/join gateway elements | Implicit from DAG shape: no gateway nodes needed |
| Deployment | Separate process engine server | Embedded library — GraphEngine runs in your JVM |
| Migration path | — | bloge-bpmn-transformer translates BPMN XML → .bloge DSL (README) |
The bloge-bpmn-transformer module provides a concrete migration path.
It reads BPMN 2.0 XML, lowers it through an intermediate representation,
and outputs either .bloge DSL text or an executable Graph object.
Supported BPMN elements include: start/end events, service/script/user tasks, exclusive, parallel, and inclusive gateways, timer/message/signal events, call activities, and subprocess structures. Unsupported elements produce diagnostics instead of silently inventing semantics.
BPMN inclusive gateway support. The BPMN transformer supports inclusive gateways
(translated to branch mode=inclusive), user task metadata preservation
(assignee, candidateGroups, formKey), extension element parsing
(<inputOutput> parameters and <properties>), and improved UEL-to-BLOGE
expression translation (safe methods like toString(), isEmpty() no longer
trigger COMPLEX_EXPRESSION diagnostics).
Translation example from
BpmnToDslExample.java:
BpmnTranslator translator = new BpmnTranslator(OperatorMappingConfig.EMPTY);
try (InputStream bpmn = getClass().getResourceAsStream("/bpmn/order-process.bpmn")) {
TranslationResult<String> dsl = translator.translateToDsl(bpmn);
if (dsl.hasErrors()) {
throw new IllegalStateException(dsl.diagnostics().toString());
}
// dsl.result() is valid .bloge DSL text
Graph graph = new GraphLoader(registry).load(dsl.result());
}
| Diagnostic code | Meaning |
|---|---|
UNSUPPORTED_ELEMENT | BPMN construct the translator does not support yet |
UNMAPPED_OPERATOR | No operator mapping matched a task |
COMPLEX_EXPRESSION | UEL expression could not be translated one-to-one |
POTENTIAL_DATA_LOSS | Translation possible, but some BPMN intent may not survive unchanged |
AMBIGUOUS_TIMER | Timer definition uses a form that does not map cleanly to BLOGE wait syntax |
When BPMN tools are a better fit: If your organisation's business analysts author and own the process models in BPMN, and the execution server is already deployed and operated, migrating to BLOGE may not be worth the disruption. BLOGE adds value when the developer team owns the workflow and wants a code-friendly authoring experience with an embeddable runtime.
BLOGE vs workflow-as-code platforms
Workflow-as-code platforms let you write workflows in a general-purpose language (Go, Java, TypeScript) with durability handled by a separate server cluster that replays function histories.
| Dimension | Workflow-as-code platforms | BLOGE |
|---|---|---|
| Deployment | Requires a dedicated server cluster | Embeddable library; no external server required |
| Durability model | History replay / event sourcing | Checkpoint-based: includes node outputs, loop/foreach progress, suspend context, partial results, event correlation, and session snapshots (Chapter 14, built on Chapter 13) |
| Workflow shape | Implicit in code control flow | Explicit DAG model visible in .bloge files and Studio |
| Dependencies | Client SDK + server infrastructure | bloge-core is java.base only (ADR-001) |
| Concurrency | Server manages worker task dispatch | Engine uses virtual threads locally (ADR-002) |
| Compensation | Framework-specific saga patterns | Declarative compensate per node (order-saga.bloge) |
When a workflow-as-code platform is a better fit: If you need built-in multi-language support across Go, TypeScript, and Python, or you already operate the server cluster and rely on its history-replay semantics, that platform's ecosystem advantage is real. BLOGE adds value when you want to avoid operating a separate cluster, when your team is JVM-native, or when the explicit DAG model and embeddable engine matter more than cross-language SDK support.
Common Trap
❌ Feature-list comparison instead of architecture-fit comparison
A common mistake is building a spreadsheet of features — "Does it have retry? Does it have branching?" — and picking the tool with the most checkmarks.
Every tool on this page supports some form of retry, branching, and persistence. The differences that matter are architectural:
- Where does the engine run? (embedded library vs. separate cluster)
- How is the workflow shape expressed? (explicit model vs. implicit in code)
- How does durability work? (checkpoints vs. replay)
- What are the dependency and JDK requirements? (zero-dep core vs. SDK + server)
If you compare at the wrong level, you'll pick a tool that has all the features but the wrong deployment model for your team.
Guided Rewrite
Migrating from hand-written code to BLOGE in four steps
Take any orchestration method in your codebase and apply this sequence:
Step 1 — Identify the independent calls.
Mark every pair of calls that have no data dependency on each other. In the
hand-written example above, fetchUser and fetchProducts are independent.
Step 2 — Extract operators.
Each call becomes an Operator<I, O> implementation. Register them in a
DefaultOperatorRegistry:
registry.register("FetchUserOperator", (input, ctx) -> {
String userId = ((Map<String, Object>) input).get("userId").toString();
return Map.of("id", userId, "name", userService.fetchUser(userId).name());
});
Step 3 — Declare the graph.
Write the .bloge file. Use depends_on for explicit fan-in. Use branch on
for conditional routing. Add retry, timeout, and fallback on nodes that
call external services.
Step 4 — Verify behaviour.
Use GraphTestRunner from bloge-test to assert
node execution order, status, and outputs:
var runner = new GraphTestRunner(
registry,
Map.of(
"FetchUserOperator", fetchUserOp,
"FetchProductsOperator", fetchProductsOp
// … remaining operators …
)
);
var result = runner.execute(graph, new GraphContext(Map.of("userId", "u1")));
runner.assertNodeExecuted("fetchUser");
runner.assertNodeExecuted("fetchProducts");
runner.assertNodeSkipped("rejectOrder"); // credit was approved
Migrating from BPMN to BLOGE
Step 1 — Translate.
Use BpmnTranslator.translateToDsl() with an operator mapping config.
Step 2 — Read diagnostics.
Fix any UNMAPPED_OPERATOR or COMPLEX_EXPRESSION diagnostics. Map BPMN
service task IDs to BLOGE operator names in OperatorMappingConfig.
Step 3 — Review the generated DSL.
The translator generates // Source: bpmn:serviceTask id="xxx" comments
for traceability (when generateSourceComments is enabled).
Step 4 — Compile and run.
Graph graph = new GraphLoader(registry).load(dsl.result());
GraphEngine engine = GraphEngine.builder().registry(registry).build();
engine.execute(graph, context);
Brain Check
-
What are the two axes in the mental model diagram, and where does BLOGE sit? (Workflow shape visibility vs. deployment topology; BLOGE is in the upper-right: declarative model + embeddable library.)
-
Name three architectural properties of BLOGE that are documented in ADRs and explain what each trades away. (Zero-dep core → some convenience reimplemented in adapter modules. Virtual threads → requires modern JDK. HCL-inspired DSL → needs its own parser, not compatible with generic YAML/JSON tooling.)
-
If you translate a BPMN file and get a
COMPLEX_EXPRESSIONdiagnostic, what should you do? (Manually rewrite the UEL expression as a BLOGE path expression or move the logic into an operator.) -
When would you choose a workflow-as-code platform over BLOGE? (When you need multi-language SDK support across Go/TypeScript/Python, or you already operate the server cluster and rely on its replay semantics.)
Lab
Lab — Write a Falsifiable Short ADR
Choose a flow in your own system with at least five steps. Deliver an ADR of no more than one page:
- State three hard constraints and two preferences; each hard constraint must be decidable.
- Compare at least a plain method, BLOGE, and one server-based candidate.
- State one counterfactual under which BLOGE is not selected; “it depends” is not allowed.
- Design a reversible, two-week migration probe with a bounded change scope and real-effect boundary.
- Record a
STOP / PROBE / ADOPToutcome and the evidence that would invalidate it.
Done means another reader can recover the choice, evidence, unknowns, first
step, and stop condition from the ADR alone. A feature checklist is not
sufficient. The reference answer and rubric are in
solutions/ch33/adr-review-guide.md;
do not open it first.
Bridge: from Change Attribution to Architecture Choice
This chapter consumes change-attribution
evidence to make a choice; it does not turn design judgment into a controlled
causal experiment. A comparison receipt can locate a changed result only when
both runs share Scenario, Policy, Fixture, business runtime, and source cohort
while changing exactly one of DSL_GRAPH or BLOGE_TOOLCHAIN. The ADR must
still add organisational constraints, operating cost, and exit criteria.
compareEvidence(...) cannot decide those for you.
Experiment acceptance card
- Expected and observed: The constraint funnel returns STOP, PROBE, or ADOPT without assuming BLOGE wins.
- Failure and recovery: Add a hard constraint that invalidates the choice; recover with a stop or reversible probe.
- Proof boundary: Proves a falsifiable selection, not universal fit.
- Exercise contract: Loan constraints; change one constraint; deliver a short ADR; stop when a counterfactual changes the decision and exit is explicit.
Recap
- BLOGE occupies a specific architectural position: **declarative DAG model
- embeddable library engine** with zero external dependencies in the core.
- Compare tools on architecture-fit (deployment model, durability semantics, workflow visibility, dependency footprint), not feature checklists.
- Hand-written code is fine for simple sequences; BLOGE adds value when you need concurrency, resilience, or visibility in the orchestration shape.
- Step-oriented batch is purpose-built for ETL; BLOGE adds value when batch steps need fan-out, branching, or external-event waits.
- BPMN tools excel when business analysts own the model; BLOGE adds
value when developers own the workflow and want code-friendly authoring.
The
bloge-bpmn-transformerprovides a concrete migration path. - Workflow-as-code platforms shine for multi-language, server-managed durability; BLOGE adds value when you want an embeddable, JVM-native engine without a separate cluster.
- Migration from hand-written code follows four steps: identify independence, extract operators, declare the graph, verify behaviour.
- A decision produces
STOP / PROBE / ADOPT: stop on a violated hard constraint and probe when a decisive fact is unknown. - A short ADR must contain the choice, evidence, counterfactual, first migration step, and exit criteria.
Next Step
In Chapter 34 — Labs you will work through end-to-end exercises that combine everything from the series — graphs, resilience, subgraphs, durability, observability, and the migration patterns from this chapter — into complete, runnable projects.
Reference Links
- Project README — module overview and quick start
- ADR-001 — Core Zero Dependencies
- ADR-002 — Virtual Threads over CompletableFuture
- ADR-004 — HCL-Inspired DSL
- ADR-007 — Resilience Order
- DSL Specification — full language grammar and compilation semantics
- Operator Design Specification — operator boundary and granularity rules
bloge-bpmn-transformerREADME — BPMN translation pipeline and diagnosticsorder-process.bloge— canonical order-processing exampleorder-saga.bloge— saga compensation examplepayment-wait.bloge— long-running await exampleBpmnToDslExample.java— BPMN → DSL → execute exampleOrderProcessingDslExample.java— DSL compilation and execution- Getting Started — full setup guide
Coding Agent: Open the versioned task guide.