Flows
Redelay is the reference runtime for FlowDSL. You declare your event topology in .flowdsl.yaml files; Redelay binds the nodes to your handler implementations at runtime.
Why FlowDSL?
Without FlowDSL, the topology of an event-driven system lives implicitly in:
- hardcoded Kafka topic strings scattered across services
- consumer group configurations
- deployment docs that get out of date
FlowDSL makes the topology explicit, visual, and machine-readable.
How it works in Redelay
| FlowDSL concept | Redelay binding |
|---|---|
node (source) | An event published by any Redelay producer |
node (action / llm / transform) | A bus.Register handler in Go (Python / Node.js — coming soon) |
edge (durable) | Kafka topic — at-least-once delivery |
edge (best-effort) | Redis Streams or in-memory |
packet | Typed Go struct encoded as JSON (Python / Node.js payload types — coming soon) |
flow | One .flowdsl.yaml file |
The AsyncAPI schema Redelay auto-generates is the bridge: packet schemas in FlowDSL reference it via $ref.
Example — domain drop pipeline
# domain-pipeline.flowdsl.yaml
flowdsl: "1.0"
info:
title: Domain Drop Pipeline
version: "1.0.0"
nodes:
ZoneImport:
operationId: import_zone_file
kind: source
summary: Reads ICANN zone files and emits removed domains
WhoisLookup:
operationId: perform_whois
kind: action
summary: Queries WHOIS/RDAP for domain registration status
DropDetector:
operationId: detect_drop
kind: action
summary: Polls DNS SOA records until the domain drops
LLMScorer:
operationId: score_domain
kind: llm
summary: Scores domain memorability and value with LLM
edges:
- from: ZoneImport
to: WhoisLookup
delivery: { mode: durable, packet: DomainRemovedPayload }
- from: WhoisLookup
to: DropDetector
delivery: { mode: durable, packet: WhoisCompletedPayload }
- from: DropDetector
to: LLMScorer
delivery: { mode: durable, packet: DomainDropPayload }
components:
packets:
DomainRemovedPayload:
$ref: "https://api.redelay.com/asyncapi.json#/components/schemas/DomainRemovedPayload"
WhoisCompletedPayload:
$ref: "https://api.redelay.com/asyncapi.json#/components/schemas/WhoisCompletedPayload"
DomainDropPayload:
$ref: "https://api.redelay.com/asyncapi.json#/components/schemas/DomainDropPayload"
Binding nodes to handlers
The operationId in each node maps to a registered handler. Redelay matches it by (entity_type, action) from the event definition:
// operationId: detect_drop → this handler
bus.Register(&modules.ConsumerRegistration{
Topic: "domain.whois_completed",
GroupID: "flow-worker",
Handler: func(ctx context.Context, msg *modules.EventMessage) error {
var p WhoisCompletedPayload
json.Unmarshal(msg.Payload, &p)
return monitorAndDetectDrop(ctx, msg.EntityID, msg.CorrelationID)
},
})
Python equivalent coming soon.
Node.js equivalent coming soon.
Subworkflow nodes
Modules can expose entire workflows as reusable subworkflow nodes. A subworkflow node wraps a multi-step workflow behind a clean input/output port interface — consumers interact with it as a single node, while the internal graph handles the orchestration.
| Concept | Description |
|---|---|
kind: subworkflow | A node whose implementation is a full workflow graph |
WorkflowRef | Links the node to the internal ir.Workflow definition |
| Input ports | Derived from workflow triggers |
| Output ports | Derived from terminal nodes (kind=end or leaf nodes) |
The framework auto-generates subworkflow nodes for any module that provides workflows via
FlowDSLProvider. You can override the auto-generated node by explicitly defining a
FlowDSLNode with a matching WorkflowRef.
# Auto-generated node for a "payment_flow" workflow:
# ID: billing/payment-flow
# Kind: subworkflow
# Inputs: [InvoiceCreated]
# Outputs: [Output]
# WorkflowRef: payment_flow
Visualising flows
Open your .flowdsl.yaml in FlowDSL Studio to see a visual diagram of your pipeline. The Studio renders edges, delivery modes, and packet types.
Validating flows
# Using redelayctl
redelayctl validate domain-pipeline.flowdsl.yaml
# Or with the FlowDSL CLI
flowdsl validate domain-pipeline.flowdsl.yaml