When a workflow needs to process multiple signals that may arrive out of order, or requires specific timeout logic between signal arrivals, using a workflow.Selector with multiple AddReceive callbacks can become complex and difficult to maintain.
An alternative pattern is to:
- Use separate goroutines to receive signals.
- Update shared variables within the workflow with the signal data.
- Use
workflow.AwaitWithTimeout in the main workflow logic to wait for specific conditions composed of those shared variables.
This approach keeps the business logic clear and separates signal reception from the workflow's state machine transitions.
// Pattern overview:
// 1. Receive signals in separate goroutines to update shared state
// 2. Use AwaitWithTimeout to progress business logic
// Example of the 'naive' (Selector-based) approach that can become convoluted:
for {
selector := workflow.NewSelector(ctx)
selector.AddReceive(workflow.GetSignalChannel(ctx, "Signal1"), func(c workflow.ReceiveChannel, more bool) {
// Process signal1
})
selector.AddReceive(workflow.GetSignalChannel(ctx, "Signal2"), func(c workflow.ReceiveChannel, more bool) {
// Process signal2
})
selector.AddReceive(workflow.GetSignalChannel(ctx, "Signal3"), func(c workflow.ReceiveChannel, more bool) {
// Process signal3
})
cCtx, cancel := workflow.WithCancel(ctx)
timer := workflow.NewTimer(cCtx, timeToNextSignal)
selector.AddFuture(timer, func(f workflow.Future) {
// Process timeout
})
selector.Select(ctx)
cancel()
// break out of the loop on certain condition
}