Azure & Cloud Architecture

Azure Service Bus Patterns for Reliable Business Workflows

Service Bus delivers messages; it does not guarantee a business effect happens once. Reliable workflows combine peek-lock settlement, idempotency, scoped ordering, dead-letter operations and observable outcomes.

Azure Service Bus workflow architecture

Choose loss or duplicates explicitly

Peek-lock receive gives at-least-once delivery: complete only after durable processing, and expect redelivery after lock loss or consumer failure. Receive-and-delete gives at-most-once delivery and can lose work if the consumer fails. Important business workflows normally choose duplicates and design for them.

Entity patterns

FeatureUseCost
QueueOne logical consumer groupNo fan-out history
Topic/subscriptionIndependent consumers and filtersEach subscription needs ownership
SessionPer-key ordered handling and session stateSerialises work within the key
Duplicate detectionSuppress repeated sends by MessageId windowThroughput and finite history
Dead-letter queueQuarantine terminal failuresRequires operational tooling
Business events moving through reliable cloud messaging
Delivery, settlement and business idempotency are separate guarantees.

Complete after the authoritative commit

Settle a message only after required effects are durable. A crash after database commit but before completion causes redelivery; the handler must recognise the same operation. A crash before commit must leave the message available.

Use an outbox to commit business state and outgoing-message intent atomically. Publishing directly inside a database transaction does not enlist Service Bus in that transaction.

Duplicate detection is send-side protection

Service Bus duplicate detection remembers application-supplied MessageId values for a configured window. It protects a sender retrying after an uncertain send outcome. It does not prevent receiver redelivery after lock loss and does not replace idempotent consumers.

Choose stable business message IDs before sending and keep the detection window only as long as the send ambiguity requires; larger windows affect throughput.

Sessions provide ordering per business key

Sessions demultiplex related messages and provide FIFO handling for a session. Use an order, account or workflow identifier-not one global session that removes all parallelism. Ordering delivery does not validate domain sequence; include expected versions so stale transitions are rejected safely.

Session-enabled entities require every message to carry a session ID. Hot keys remain serial bottlenecks and need monitoring.

Lock duration, renewal and prefetch interact

Prefetched messages are already locked. Fetching more than a receiver can process before expiry creates duplicates and lock-loss noise. Set concurrency, prefetch and auto-renewal from measured processing duration.

Long human or external workflows should not hold a message lock. Persist workflow state and continue through new messages or a workflow engine.

Retries need classification

Abandon transient failures for delayed redelivery with bounded attempts. Dead-letter malformed contracts, invalid business commands and unsupported versions with stable reason codes. Repeated immediate abandon creates a hot loop; use scheduled retries or application delay where appropriate.

SDK retries can hide uncertain send/settlement outcomes. Trace logical operations separately from attempts and make non-idempotent downstream calls safe.

The DLQ is a supported work queue

Messages can dead-letter because of max delivery, explicit rejection, expiry or transfer failure. Inspect both ordinary and transfer dead-letter paths. Alert on oldest age and growth, not merely total count.

Replay tooling must show reason, contract version and safe identifiers, allow correction where legitimate, preserve idempotency and throttle replay. A DLQ with no owner is silent data loss.

Topics need subscription governance

Each subscription is an independent backlog and failure domain. A filter that matches nothing silently drops that consumer's view; a filter that matches too broadly creates cost and unexpected processing. Test filter rules and provision subscriptions as versioned infrastructure.

Removing a subscription destroys its backlog. Treat lifecycle changes as data operations, not simple configuration cleanup.

Operational signals

  • Active and dead-letter message count and oldest age.
  • Completion, abandon, lock loss and retry rate.
  • Session backlog and hot-key distribution.
  • Processing duration versus lock and renewal.
  • Business outcomes correlated by message and operation ID.
  • Subscription filter and transfer failures.

Queue depth can be healthy while one old message is starved. Monitor age and terminal business completion.

The outbox closes the database-to-message gap

Saving business state and then sending a message creates a crash window in which the commit succeeds but the event disappears. Sending first creates the inverse problem: consumers observe an event for a transaction that rolls back. An outbox record committed with business state makes intent durable.

A dispatcher publishes pending records and marks them sent. That marking can fail after Service Bus accepts the message, so duplicates remain possible. Stable message identity and idempotent consumers are still required; the outbox provides at-least-once publication rather than magical exactly-once delivery.

Keep outbox payloads versioned and bounded. Purge completed records according to an operational retention policy without deleting the evidence needed to diagnose recent delivery.

Inbox processing protects the consumer transaction

A receiver can record message identity and its business update in one local transaction. Redelivery then finds the inbox record and completes without repeating the effect. Checking an in-memory cache before processing is not durable idempotency.

Idempotency keys should represent the business operation, not merely one transport attempt. Duplicate detection at Service Bus handles a configured window and message ID; it cannot prevent a logically duplicated event sent later under another ID.

External side effects need their own idempotency or outbox. Completing the message after sending email but before recording success can resend the email after a crash.

Ordering is local, expensive and easy to overstate

Queues do not promise global business ordering across competing consumers. Sessions provide ordered, exclusive processing for messages sharing a session ID, which is useful for one order or account. They also reduce parallelism for a hot session.

Sequence still does not make historical events safe to apply. A delayed message may arrive after a newer state from another route. Include domain version and reject or reconcile stale transitions.

Choose the narrowest ordering key that preserves the invariant. Making every tenant one session serialises unrelated work and turns a large customer into a bottleneck.

Lock loss creates concurrency, not merely an exception

Peek-lock delivery grants temporary ownership. If processing exceeds the lock or renewal fails, another worker can receive the same message while the first still runs. Automatic renewal reduces frequency but cannot make long processing single-owner forever.

Keep handlers bounded, checkpoint durable stages and make effects repeatable. Prefetch starts lock time before application processing; a large buffer can cause messages to expire while waiting locally. Tune prefetch, concurrency, lock duration and processing latency together.

Complete only after the authoritative local commit. Abandon transient failures, dead-letter permanent ones with useful reason and never swallow an exception then complete work that did not happen.

Deferral and scheduling are not arbitrary workflow engines

Scheduled delivery is useful for known future work, but cancellation, long horizons and changing business rules need ownership. A scheduled message contains old assumptions unless the handler revalidates current state.

Deferral keeps a message aside until the receiver retrieves its sequence number. Losing that reference leaves inaccessible work. Use it for controlled correlation patterns, not as a general “deal with this later” bucket.

Complex, long-running workflows need explicit state, timeouts and compensation. Service Bus transports messages; it does not decide what a partially completed business process means.

The dead-letter queue is an operational product

Dead-lettered messages retain work that automatic processing cannot safely finish. Record a concise failure category and diagnostic context without copying secrets. Operators need search, correction, replay and discard actions with authorisation and audit.

Monitor age and count by reason. A stable count can hide old customer work that never moves. Define service objectives for triage and resolution, and rehearse replay after code or configuration fixes.

Do not automatically replay every DLQ message into the same failing handler. That creates cycles and can repeat side effects. Inspect whether contract, data or dependency failure has actually changed.

Capacity and fairness need deliberate limits

Service Bus decouples producers and consumers but does not create unlimited capacity. Watch active count, age of oldest message, throttling, lock loss, DLQ growth and processing latency. Queue depth without arrival and completion rates is hard to interpret.

Apply per-tenant or workload concurrency where one producer can dominate. Separate interactive and bulk work when they have different latency objectives. Autoscaling consumers can overload databases or suppliers, so downstream capacity must constrain scale.

Message size affects throughput and cost. Store large payloads in protected blob storage and send a reference with integrity and lifecycle controls rather than turning the broker into a document store.

Related C3 Software guides

Frequently asked questions

Does Service Bus guarantee exactly once?

No. Design for at-least-once delivery and idempotent business effects.

Does duplicate detection prevent duplicate processing?

It suppresses repeated sends with the same MessageId inside a window; receiver redelivery still occurs.

When should sessions be used?

When related messages require ordered, exclusive handling per business key.

What should happen to dead-letter messages?

They need alerts, diagnosis, ownership and controlled idempotent replay or explicit disposition.

Make messaging outcomes explainable

C3 Software Limited has built and supported business software in the UK since 2001. For Service Bus reliability reviews, talk to C3 Software.