6 min read
Let durable orchestration earn its place
Start with the failure you need to recover from
A long-running task is not automatically an orchestration problem. I would first ask what must still be true if the process handling it stops halfway through.
If the work is a single, independent operation, a queue and a consumer may be enough. The consumer receives a message, performs the operation, and acknowledges the message only when its chosen success condition is met. If it fails, the system needs a defined way to retry or route the message for investigation. The operation also needs to tolerate being attempted again, or have a way to recognize that it has already taken effect.
That is not a lesser design. It is a smaller one. The queue holds work until a consumer can act on it; the consumer owns the operation. I do not need a workflow history just because processing is asynchronous or might be retried.
The case changes when the work is a sequence of steps whose progress matters. Suppose a process must request approval, wait for a person, and then continue with a later action. If a process restart should not make me reconstruct whether approval was requested, received, or acted on, then persisted progress is part of the requirement. A durable orchestrator can own that state and coordinate the next action after a delay or failure.
The .NET blog’s October 12, 2026 article on problems durable task actually solves is a useful prompt for this question. I would not start by asking whether a particular durable-task framework is interesting. I would ask which recovery responsibility the system actually needs.
Keep the responsibilities visible
A queue-and-consumer design separates the producer, the message transport, and the handler. That separation is useful when a message describes work that can be handled on its own. The consumer decides how to execute that work, and the queue provides a place for work to wait. Retry policy, failure inspection, and idempotency still need deliberate design; the queue does not make those questions disappear.
An orchestrator has a different job. It owns a process that spans steps and time: which step is current, what result allows the process to advance, and what should happen after interruption. Activities can still run as messages or independent workers. Orchestration does not replace messaging so much as give a larger process a durable owner.
I find this boundary easier to see as a division of responsibility:
flowchart LR
accTitle: Queue work and orchestrated work
accDescr: Independent work goes from a producer to a queue and consumer, while multi-step work is tracked by an orchestrator that invokes activities.
Producer --> Queue
Queue --> Consumer
Request --> Orchestrator
Orchestrator --> Activity
Activity --> Orchestrator
The diagram is a design distinction, not a mandate to introduce two separate platforms. A system might use the same messaging infrastructure to dispatch activities and still have an orchestration component tracking the larger process. What matters is that I can point to the owner of progress. If a request is partway through several steps, some component must be able to say what has happened and what should happen next.
For a standalone task, that owner can simply be the consumer plus the data it writes. For a process with meaningful intermediate state, leaving progress implicit across handlers and messages can make recovery hard to reason about. I would add orchestration when the process itself needs to be durable, not merely because one of its steps uses a queue.
Make the extra machinery pay for itself
Durable orchestration has a cost. I need to model the process as state and steps, decide what results are persisted, and account for how changes to the process interact with work already in flight. I also need to operate and inspect that state. Those are reasonable costs when they buy reliable recovery across a process that matters. They are overhead when the process is only a single handler doing work that can safely be retried.
A queue-and-consumer design has costs too. The application must decide what a retry means, how it avoids repeating side effects, and how someone investigates work that cannot succeed. If a business process is spread across several consumers, each with its own local assumptions about what has completed, the system may be difficult to recover even though every individual message is durable. Adding more messages does not by itself give the overall process an owner.
I would therefore distinguish between retrying an operation and resuming a process. A failed operation may be put back into the work stream and attempted again. A process that needs to resume must also know where it was, what prior steps established, and which later action is valid. Those are related needs, but they are not interchangeable.
Here is the kind of boundary I would aim to make explicit in code. This is illustrative application code, not an API from a particular orchestration product:
public async Task HandleAsync(GenerateReport message, CancellationToken cancellationToken)
{
var report = await reportGenerator.GenerateAsync(
message.ReportId,
cancellationToken);
await reportStore.SaveAsync(report, cancellationToken);
await messageAcknowledger.AcknowledgeAsync(message, cancellationToken);
}
This handler expresses one operation and its completion boundary: save the result before acknowledging the work. If the actual requirement is to wait for a decision, remember that decision through a restart, and then carry out a later step, I would not hide that process in an increasingly elaborate handler. I would give it an explicit durable owner and let the handler remain responsible for its activity.
Choose the smallest honest owner
I would choose a queue and consumer when work is independent, its completion can be described at the operation boundary, and the surrounding system can handle retry and duplicate attempts safely. I would choose durable orchestration when progress through multiple steps must survive process loss and the next action depends on that recorded progress.
The uncomfortable middle is a workflow built from messages where nobody owns the whole workflow. It can be correct, but only if the state and recovery rules are explicit somewhere. If that means a coordinator, a database record, and carefully designed consumers, that may be a good choice. If those pieces amount to rebuilding orchestration without naming it, I would rather use an orchestrator and accept its operational cost openly.
I would not route every asynchronous interaction through durable orchestration. I would use it for work whose progress deserves to be remembered; for the rest, a queue and a consumer are often the more direct design. The boundary is not about which pattern is more advanced. It is about who must know what happened when the process stops.
Drafted with AI assistance, reviewed and edited by Ali.