Azure

Azure messaging services: the choice is command or event

September 1, 2026

Most comparisons of Azure messaging services start with a feature table, which is the least useful place to start. Service Bus, Event Grid and Event Hubs all move messages between systems, and if you compare them on that basis you will pick whichever one someone on the team has used before.

Microsoft’s own guidance starts somewhere better: decide what kind of message you have. There are two, the difference is not subtle, and once you have named it the service choice narrows to one obvious answer.

Two kinds of message

A command requests a specific action from the consumer. It is a high-value message with strict delivery requirements: it must be delivered at least once, and if it does not arrive, the business transaction it belongs to may fail. Equally, consumers should not process it more than once, because duplicate processing produces duplicate orders and double billing.

An event announces that something happened. The producer, properly called the publisher here, has no expectation that the event will result in any specific action. There may be many subscribers, or none. Subscribers can react differently to the same event, or unsubscribe entirely without the producer knowing or caring.

That last sentence is the real distinction, and it is about coupling rather than technology. With a command you are asking someone to do something and you care whether they did. With an event you are stating a fact and what happens next is not your concern.

Events split again. Discrete events announce individual facts, such as a resource being created or deleted. Event streams are sequences of related events over time, evaluated statistically or in windows, which is what telemetry and IoT produce.

Three message shapes, three services.

You haveUseBecause
A commandService BusGuaranteed delivery, ordering, duplicate detection
A discrete eventEvent GridPush distribution to many subscribers, filtering
An event streamEvent HubsHigh-volume ingestion, replay, partitioned consumers

What each one actually gives you

Service Bus is built for the command case and it shows. Consumers pull, and a peek-lock mechanism locks a message while it is being processed so no other consumer takes it. If the consumer crashes or times out, the lock releases and someone else picks it up, so messages are not lost in transit. It has built-in duplicate detection, sessions for guaranteed ordering, and session state so a long-running transaction can checkpoint and resume after a failure.

It also has a dead-letter queue, which is the feature most worth designing around. Messages land there in two situations: a poison message the consumer cannot handle, detected by exceeding the queue’s MaxDeliveryCount, and a message whose time to live expired before anyone processed it. The DLQ holds them until someone looks. Nothing looks by itself.

Event Grid pushes discrete events to subscribers, with filtering so a subscription receives only the events it cares about. Two properties matter more than the feature list. It attempts delivery at least once per subscription, with configurable retry, expiry and dead-lettering. And it does not guarantee order.

There is also a failure mode specific to the push model that is easy to miss: if no subscribers exist, or subscribers repeatedly fail to respond, Event Grid discards the events. An event nobody was listening for is simply gone.

Event Hubs handles streams, ingesting millions of events per second. The important difference from a queue is that it appends to a stream rather than removing from a queue, so a subscriber manages its own cursor and can move forwards and backwards, select a time offset, and replay a sequence at its own pace. Partitions allow concurrent readers, consumer groups allow several independent readers of the same stream with different purposes, and capture writes the stream to Blob Storage or Data Lake Storage so it can serve as a source of truth.

Where teams choose wrong

The most common error we see is using Event Grid for something that is actually a command, because the event-driven framing is fashionable and the service is cheap and easy to wire up.

Microsoft states the boundary explicitly: if your workload requires enterprise messaging features such as strictly ordered processing, transactions, or duplicate detection, use Service Bus instead. That sentence is the test. If any of those three words describes your requirement, you have a command, whatever the design document calls it.

The second error is subtler and it concerns routing. Event Grid subscription filters and Service Bus topic filters are both capable enough to encode business rules, and doing so is tempting because it removes code. The guidance is to resist: keep routing logic simple, avoid embedding complex business rules in subscription filters, and prefer smart endpoints and dumb pipes. Use the broker for reliable transport and broad routing, and keep decisions in the consuming service.

The reason is operational rather than architectural. Business logic in a filter is logic with no tests, no version history and no obvious home, and when a message fails to arrive the filter is the last place anyone looks.

A better tell than the paper test

Mislabelling turns up often enough that it is now one of the first things we check on any integration review. Not an exception case, a default suspicion.

And the paper test above, ordering and duplicate detection and transactions, is not the one we actually rely on, because a team can argue its way out of it. Ordering does not matter, right up until it does, and people can convince themselves of that in a design workshop without anyone lying.

The reliable tell is behavioural. Ask the team one question:

“What do we do if this does not arrive?”

If the answer involves reconciliation, a support ticket, or a manual re-send, it was never fire-and-forget. It is a command, and the organisation already knows it, because somebody has already designed a human process to compensate for the loss of a message that supposedly did not need delivering.

That question works where the paper test fails because it asks what people believe rather than what the design says. Nobody builds a reconciliation process for something they genuinely do not care about.

It is also the same question that decides whether an interface should be synchronous or asynchronous, asked at a different layer. What happens when the other side does not answer settles the coupling. What happens when the message does not arrive settles the message type. Two questions, one instinct, and both are usually the shortest route to an honest design.

What the broker is buying you

Worth being explicit, because the value is often assumed rather than designed for.

Temporal decoupling is the big one. The producer and consumer do not have to run at the same time. A producer can send regardless of whether the consumer is available, and the consumer’s availability does not constrain the producer. That is precisely the property a synchronous call does not have, and it is why asynchronous messaging survives a maintenance window.

Load levelling buffers spikes so consumers work at a steady rate rather than being overwhelmed, and load balancing through competing consumers spreads work across instances you can add or remove. Resilience means that if a consumer fails mid-message, another instance can process it, because the broker still holds it.

And when a payload is too large for the broker, the Claim-Check pattern is the answer: store the payload in Blob Storage, send a message containing a pointer, and let the consumer fetch it. That keeps large datagrams from overwhelming the broker, and it is a much better answer than raising limits.

Idempotency is not optional

One design requirement applies regardless of which service you choose, and it is the one most often deferred.

Service Bus has duplicate detection, and it can still deliver a message twice. If a consumer fails while processing, the message returns to the queue and is retrieved again, by the same consumer or another. Event Grid delivers at least once by design. So in both cases the consumer’s processing logic must be idempotent, meaning repeated processing does not change the system state.

This is the same requirement that ERP integration patterns run into, and for the same reason: retry after failure is normal operation, not an exception. Building idempotency in afterwards means auditing every consumer for side effects, which is considerably harder than designing for it once.

Our position: name the message before you choose the service

In our view the service comparison is a distraction, and the design review question should be “is this a command or an event”, asked before any product appears on a whiteboard.

Teams that answer it get a defensible architecture almost by default, because the constraints follow from the answer. Teams that skip it end up choosing on familiarity, and familiarity produces estates where the same kind of message is handled three ways.

We would not accept “event-driven” as an architectural description without that question being answered per interface. It is a style, not a decision. And in our engagements the most expensive rework is not choosing the wrong service, it is discovering months later that something modelled as a fire-and-forget event was a command all along, because the business consequences of a lost one only appear at volume.

Worth saying that the services combine well and doing so is sanctioned rather than a workaround. Microsoft documents crossover scenarios in both directions, including using Event Grid to notify when an idle Service Bus queue receives a message so a function can drain it, and using Event Grid filters to route some events into a Service Bus queue for workflow while others go straight to notification.

So it should be a named line item in design review, not an assumption baked into the architecture diagram before anyone has asked the question out loud.

That distinction is the whole of it. The decision gets made either way. The only question is whether it is made deliberately, once, by people who can see the whole estate, or implicitly, repeatedly, by whoever is building each interface. Leaving it implicit is exactly how a team ends up with three different handling patterns for the same kind of message, each one defensible on its own and collectively impossible to operate.

Where to start

Audit how you are using Azure messaging services today by labelling each existing interface command or event. It takes an afternoon and it is usually the first time anyone has written it down.

Then test the events: does anything about them require ordering, transactions or duplicate detection? Anything that does is a command wearing the wrong label, and it belongs on Service Bus.

Finally, check that every consumer is idempotent, because retries are normal and at-least-once delivery means duplicates are a certainty rather than a risk.

Veratas does the enterprise architecture work behind these decisions and the data integration delivery underneath them.

If your estate handles the same kind of message three different ways, talk to our team. Labelling them is quick, and it usually shows the consolidation is smaller than feared.

Frequently asked questions

What is the difference between a command and an event? A command requests a specific action and the producer cares whether it happened. An event announces that something occurred, with no expectation of any particular response, and may have many subscribers or none.

When should we use Service Bus rather than Event Grid? Whenever the workload needs strictly ordered processing, transactions or duplicate detection. Microsoft names those three explicitly as the point at which Event Grid is the wrong choice.

Does Event Grid guarantee delivery order? No. Event Grid delivers at least once per subscription with configurable retry and dead-lettering, but it does not guarantee order. If order matters, use Service Bus sessions.

What happens to Event Grid events with no subscriber? They are discarded. In the push model, if no subscribers exist or subscribers repeatedly fail to respond, the events are dropped, so an event nobody subscribed to is simply lost.

Why do we still need idempotent consumers if Service Bus has duplicate detection? Because a message can still be delivered twice. If a consumer fails mid-processing, the message returns to the queue and is retrieved again. Duplicate detection covers producer-side resends, not consumer-side reprocessing.

What do we do with messages too large for the broker? Use the Claim-Check pattern: store the payload in Blob Storage and send a message containing a pointer, so the consumer retrieves it when needed rather than pushing large datagrams through the broker.

How do we tell if something is really a command? Ask what happens if it does not arrive. If the answer involves reconciliation, a support ticket or a manual re-send, it is a command, whatever the design calls it. That test beats checking for ordering or duplicate requirements, because teams can argue those away and cannot argue away a compensating process they have already built.

Should business rules live in subscription filters? No. Keep routing logic simple and prefer smart endpoints and dumb pipes. Logic in a filter has no tests and no obvious home, and it is the last place anyone looks when a message fails to arrive.