Aggregate¶
Aggregate implements the Aggregator EIP: it buffers incoming exchanges by a correlation key and emits a single combined exchange when a completion condition is met. The file also defines the built-in AggregationStrategy implementations used by aggregate, split, multicast, enrich, and scatter-gather.
Processors in this file¶
NewAggregate¶
YAML form:
steps:
- aggregate:
correlation: header("order-id")
strategy: list
completion-size: 5
completion-timeout: 10s
steps:
- target: kafka://order-batches
| Field | Type | Default | Description |
|---|---|---|---|
correlation |
string | required | Starlang expression yielding the correlation key (any value; stringified via %v). Exchanges sharing a key share a buffer. |
strategy |
string | required | Name of the aggregation strategy (list, latest, first, useOriginal, map). Drives how incoming bodies are merged. |
completion-size |
int | 0 (disabled) |
Flush when this many exchanges have accumulated for a correlation key. |
completion-timeout |
duration | 0 (disabled) |
Flush a buffer this long after the first exchange for the key arrives. Parsed as Go duration (500ms, 10s). |
steps |
list | required | Pipeline to run on the aggregated exchange after completion. |
Constructor:
NewAggregate(
correlation engine.Expression,
strategy engine.AggregationStrategy,
completionSize int,
completionTimeout time.Duration,
processor engine.Processor,
) *Aggregate
How it works:
- Evaluates
CorrelationExpressionagainst the incoming exchange. Stringifies the result (fmt.Sprintf("%v", val)) to form the bucket key. - Acquires the aggregate's mutex and looks up the buffer for that key. Creates a new buffer if none exists, and arms a
time.AfterFunc(CompletionTimeout, ...)ifCompletionTimeout > 0. (The timer usescontext.Background()so it fires even if the original request context is cancelled.) - Appends a copy of the exchange to the buffer's exchange list.
- Calls
Strategy.Aggregate(ctx, buf.aggregate, ex)to fold the new exchange into the running aggregate. - Checks
completion-size: if the buffer now contains ≥CompletionSizeexchanges, completion fires. - On completion: removes the buffer, stops the timer, sets
HelixAggregatedSizeandHelixAggregatedCorrelationKeyon the aggregate exchange, and runs the downstreamProcessor.
The aggregate implements engine.StoppableProcessor via Stop(), which cancels all pending completion timers — useful for tests and graceful shutdown.
Correlation expression cheat sheet¶
The correlation: field is a Starlang expression. Common shapes:
| Goal | Expression |
|---|---|
| Group by a header | header("X-Order-Id") |
| Group by a body field | body().customerId |
| Composite key (header + tenant) | header("X-Tenant") ++ ":" ++ header("X-Order-Id") |
| All into one bucket | "all" (constant) |
| Per-property bucket | property("HelixTenantId") |
Anything stringifiable works — ints, floats, bools, maps. Two exchanges land in the same bucket iff fmt.Sprintf("%v", x) == fmt.Sprintf("%v", y).
Aggregation strategies¶
The file exposes five built-in strategies. Selected by name via AggregationStrategyByName from YAML.
ListAggregationStrategy — list¶
Appends every incoming body into a []any. The aggregated exchange's body is the accumulating list. Use for classic scatter-gather (multicast + list = collect N responses).
LatestAggregationStrategy — latest¶
Keeps only the most recent exchange. Useful when only the final state of a chain matters.
FirstAggregationStrategy — first¶
Keeps only the first exchange. Useful for "fastest-responder" or race-style patterns.
UseOriginalStrategy — useOriginal, use-original, original¶
Discards all responses; the input passes through unchanged. Use this for fire-and-forget fan-out where you don't care about replies (multicast + useOriginal is a broadcast).
MapAggregationStrategy — map¶
Collects responses into a map[string]any keyed by the HelixMulticastIndex (or split index) property. Use when you need to know which processor produced which response.
AggregationStrategyByName¶
Helper that maps a name to a strategy instance:
| Input name | Returns |
|---|---|
list |
*ListAggregationStrategy |
latest |
*LatestAggregationStrategy |
first |
*FirstAggregationStrategy |
useOriginal, use-original, original |
*UseOriginalStrategy |
map |
*MapAggregationStrategy |
| anything else | nil |
Used by the reifier to look up the YAML strategy: field. To register a custom strategy, implement engine.AggregationStrategy in code and pass it to the constructor directly.
Pitfalls¶
CompletionSizeandCompletionTimeoutcan both be set — whichever fires first wins.- A buffer with no completion configured (both fields zero) will never flush, growing forever. The reifier rejects such configs, but be aware when building in Go.
- The aggregate exchange returned to
Processoris a fresh exchange built by the strategy — properties on individual inputs are not merged unless the strategy does it. - The completion timer captures
context.Background()so the downstreamProcessorruns even after the original request context is cancelled. If your downstream usesctx, derive a fresh one.
Examples¶
Batch up to 100 events per customer with a 5s flush:
workflows:
- id: batch-customer-events
trigger: { uri: kafka://events?group=batcher }
steps:
- aggregate:
correlation: body().customerId
strategy: list
completion-size: 100
completion-timeout: 5s
steps:
- log: { message: "flushing customer batch" }
- target: postgres://customer-events
Source¶
pkg/processor/aggregate.go
See also¶
- multicast — broadcast + aggregate; uses the same strategies.
- split — opposite direction; splits a slice into per-element exchanges.
- routing —
Enrichuses aggregation strategies to merge enrichment data. - eip_advanced — combined multicast + aggregate with timeout.