🌊 hexastack-flow
Hexagonal workflow engine adapter integrating
hexaflowwith Hexastack CQRS, Relational Persistence, and Event Streaming.
Part of the Hexastack Framework · Powered by Hexaflow · Governed by Hexaqual.
🎯 Architectural Intent
hexastack-flow provides first-class hexagonal adapters connecting the lightweight hexaflow workflow engine to Hexastack:
- CQRS Bridge:
CommandStepandQueryStepallow workflow steps to dispatch typed commands and queries directly onto theCommandBusandQueryBus, inheriting all CQRS middleware (auth, tracing, logging, metrics, retries). - Durable Distributed Sagas: Workflows natively subsume the Saga pattern by pairing forward command execution stages with compensating rollback stages, backed by durable checkpoints.
- Enterprise Persistence:
SqlAlchemyWorkflowStorepersists workflow execution records and step checkpoints directly to PostgreSQL/SQLite/MySQL within transactional boundaries. - Event Streaming: Emits domain events (
WorkflowStartedEvent,WorkflowStepCompletedEvent,WorkflowSuspendedEvent,WorkflowCompletedEvent,WorkflowAbortedEvent) wrapped in standardCloudEventEnvelopes throughhexastack-events. - DevTools Visualizer: Backing engine for NiceGUI and Textual DAG monitoring.
🔄 Implementing the Saga Pattern as a Workflow
Historically, the Saga Pattern structures a distributed business transaction across multiple microservices or bounded contexts as a sequence of local transactions:
- Forward transactions: $T_1, T_2, \dots, T_n$
- Compensating transactions: $C_{k-1}, \dots, C_1$ executed in reverse (LIFO) order if step $k$ fails.
In hexastack-flow, you do not need a separate, restricted saga engine. A Saga is simply a workflow DAG that dispatches CQRS commands and executes compensating commands on failure.
Example: Trip Booking Saga with CQRS & Hexaflow
from hexaflow import Workflow
from hexastack_cqrs.ports.buses import CommandBusPort
from hexastack_flow.adapters.cqrs.runner import CqrsWorkflowRunner
from hexastack_flow.adapters.cqrs.steps import as_command_step
# 1. Define CQRS commands for forward actions & compensations
from my_domain.commands import (
BookFlightCommand,
CancelFlightCommand,
BookHotelCommand,
CancelHotelCommand,
ChargePaymentCommand,
)
def build_trip_booking_workflow(bus: CommandBusPort, customer_id: str) -> Workflow:
"""Construct a durable distributed saga as a hexaflow Workflow."""
workflow = (
Workflow("trip-booking-saga")
# Step 1: Reserve Flight
.stage(
"flight",
as_command_step(
lambda ctx: BookFlightCommand(
customer_id=customer_id, destination="JFK"
),
bus,
name="book_flight",
),
)
# Step 2: Reserve Hotel (runs after flight is confirmed)
.stage(
"hotel",
as_command_step(
lambda ctx: BookHotelCommand(
customer_id=customer_id, room_type="deluxe"
),
bus,
name="book_hotel",
),
depends_on=["flight"],
)
# Step 3: Process Payment
.stage(
"payment",
as_command_step(
lambda ctx: ChargePaymentCommand(
customer_id=customer_id, amount_cents=50000
),
bus,
name="charge_payment",
),
depends_on=["hotel"],
)
)
return workflow
# 2. Execute via CqrsWorkflowRunner (with durable DB checkpoints)
runner = CqrsWorkflowRunner(engine=engine)
result = await runner.run(workflow.build())
Why Workflows are Superior to Linear Sagas
| Feature | Legacy In-Memory Saga | hexastack-flow Workflow |
|---|---|---|
| Execution Topologies | Strictly sequential ($T_1 \to T_2 \to T_3$) | Arbitrary DAGs (e.g. reserve Flight & Hotel in parallel) |
| Crash Recovery | In-memory only (lost if process restarts) | Durable Checkpoints (SqlAlchemyWorkflowStore) |
| Step Retries | Manual loop or fail immediately | Configurable RetryPolicy (exponential backoff, jitter) |
| Observability | Console logs | CloudEvents (WorkflowStepCompletedEvent) + OTel spans |
| Human-in-the-Loop | Not supported | Pause / Resume (engine.suspend(), engine.resume()) |
📦 Installation
pip install hexastack[flow]
Or standalone:
pip install hexastack-flow
📄 License
Apache 2.0
Metadata
Release files for hexastack-flow 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hexastack_flow-0.8.0.tar.gz | 14.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hexastack_flow-0.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.7 kB
Release files / hexastack_flow-0.8.0.tar.gz
| Download URL | hexastack_flow-0.8.0.tar.gz |
|---|---|
| Size | 14.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9d42a9a43fe2917272747bd32b3019ac395ed0247d99cb9a401c2a76e7538459
|
|
BLAKE2b-256 checksum How to use checksums |
e6be0e57ce2d42e6d66eb8557edc2d17874f2c06f53db1e2b3505cf832f48c96
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / hexastack_flow-0.8.0-py3-none-any.whl
| Download URL | hexastack_flow-0.8.0-py3-none-any.whl |
|---|---|
| Size | 22.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cedc8678d172f1c3c7d76be364604e10d8db0189243c5396cab4ef9b7b8e7dd0
|
|
BLAKE2b-256 checksum How to use checksums |
debd658ab9a5167c65020bd68a1f9ee1895d8ea307a2402b56a2fbda8e69fa22
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|