In the Azure PaaS map post, the data layer got one paragraph and a rule: pick by access pattern, not by which service feels modern. That rule holds. But it’s also where most write-ups stop: SQL for relational integrity, Cosmos DB for scale, and Redis in front; for an integration architect, that’s the least interesting part of the story.
The interesting part is what the data layer has to do that’s specific to integration. Messages arrive twice. Workflows run for hours and need somewhere to keep their state. A write to your database and a publish to a queue have to succeed or fail together. So this post skips the service comparison and covers the patterns instead. Azure data patterns for integration are less about which store you pick and more about how you use it.
Why integration data is different
A typical application owns its data. It writes, it reads, it controls the whole path. Integration doesn’t work that way. Instead, integration sits between systems it doesn’t own, reacting to events it didn’t originate, and it has to stay correct when those systems misbehave.
That changes what the data layer is for. It’s no longer just persistence. It becomes the place where you enforce correctness that the messaging layer can’t guarantee on its own. Three patterns come up again and again. Let’s take them in turn.
Pattern 1: Idempotency stores
Here’s the problem. At-least-once delivery is the norm for most messaging systems, including Service Bus. So the same message can arrive twice after a retry, a redelivery, or a consumer crash-and-restart. Process it twice, and you’ve charged the card twice or created two orders. That’s not a rare edge case. In a busy integration platform, it’s a Tuesday.

The fix is an idempotency store. Before you process a message, you check whether you’ve seen its ID before. If you have, you skip it. If you haven’t, you record the ID and proceed. As a result, duplicate deliveries become harmless.
The design questions that matter:
- Where does the key come from? Ideally, the source system supplies a stable business key, an order ID, and a transaction reference. Failing that, a hash of the message content works, though it’s more fragile.
- Where do you store it? This is a high-frequency, low-latency lookup on a single key. Therefore Cosmos DB or Redis fit well, and a relational table works too if the volume is modest. The access pattern points at the store, exactly as the map post argued.
- How long do you keep it? Retention has to outlast the longest possible redelivery window. Too short, and a late duplicate slips through. So set a TTL that comfortably exceeds your retry and dead-letter timelines, then expire old keys automatically.
The honest note: idempotency at the store isn’t the same as an idempotent operation. If the downstream side effect isn’t itself safe to repeat, the store only narrows the window; it doesn’t close it. Design the operation to tolerate retries wherever you can.
Pattern 2: The outbox pattern
This one solves the dual-write problem, and the dual-write problem is subtle enough that plenty of teams ship it broken.
Picture a handler that does two things. It writes a record to the database, and it publishes an event to a queue. Both must happen, or neither. But they’re two separate systems, so there’s no shared transaction. Write succeeds, publish fails; now the database and the downstream world disagree. Publish succeeds, write fails; now you’ve announced something that didn’t happen.

The outbox pattern closes the gap. Instead of publishing directly, you write the event into an “outbox” table in the same database transaction as your business record. Because they share one transaction, they commit together or not at all. Then a separate process reads the outbox and publishes the events, marking each one done as it goes.
A few things fall out of this design:
- The database becomes the source of truth for what should be published. If the publisher crashes mid-run, it restarts and picks up where it left off. Nothing is lost, because nothing left the database until it was safely committed.
- Publishing becomes at-least-once. The publisher might send an event, crash before marking it done, and send it again on restart. So the consumer on the other end needs, you guessed it, an idempotency store. The two patterns work together.
- A change-feed makes it cleaner. Cosmos DB’s change feed, or a similar mechanism, lets the publisher tail committed changes rather than poll a table. That reduces latency and load, though a simple polling publisher is perfectly fine to start.
The trade-off is honest latency. The outbox adds a hop between commit and publish. For most integration workloads that’s a few seconds at most, and well worth it for the correctness guarantee. But if you need genuinely instant propagation, the outbox isn’t your pattern.
Pattern 3: State for long-running workflows
Synchronous request-response keeps its state in memory for the length of a call. Integration workflows don’t have that luxury. A process can span minutes, hours, or days while waiting for approval, a batch window, or an external callback. That state has to live somewhere durable, because the compute running it will scale, restart, and move underneath it.
So where does workflow state go? It depends on who’s orchestrating.

- Logic Apps and Durable Functions manage their own state: Both persist workflow state for you; that’s a large part of why they exist. Durable Functions keeps it in a storage backend; Standard Logic Apps keeps it in its own runtime store. In these cases, you rarely touch the state directly, but you should know it’s there and know that it’s what makes the workflow survive a restart.
- Hand-rolled orchestration needs an explicit store: When you’re coordinating steps in your own code rather than a workflow engine, you own the state. A document store like Cosmos DB fits well here: one document per workflow instance, updated as the process advances through its steps. The flexible schema helps, because a workflow’s state shape often evolves as you add steps.
- Correlation is the piece people forget: Long-running workflows wait for things to come back, and when a callback arrives, you have to match it to the right in-flight instance. That means a correlation ID, stored with the instance and carried on every outbound call. Without it, you have durable state you can’t reconnect to the event that needs it.
Where these patterns are the wrong answer
Consistent with the rest of the series, the honesty section. Patterns solve problems, and applying them where the problem doesn’t exist adds cost.
- Skip the idempotency store when the operation is naturally idempotent: Setting a status to “shipped” twice changes nothing. If every side effect is already safe to repeat, a dedup store is machinery you don’t need.
- Skip the outbox when you don’t dual-write: If a handler only writes to the database, or only publishes, there’s no gap to close. The outbox earns its keep specifically when one commit must produce one publish.
- Skip explicit state stores when a workflow engine already owns the state: Standing up your own Cosmos-backed state store next to Durable Functions duplicates what the runtime already gives you. Reach for the explicit store only when you’re orchestrating by hand.
The shape of it
For an integration architect, the data layer isn’t mainly a choice between SQL and Cosmos. That choice matters, but the access pattern usually makes it for you. The real work is the patterns that keep an integration platform correct when systems it doesn’t control misbehave. So an idempotency store absorbs duplicate deliveries. An outbox makes both a write and a publish succeed. A durable state store lets a workflow outlive the compute running it. Get those right, and the underlying store SQL, Cosmos, and Redis become implementation details rather than the headline.
Want the layer this sits inside? The Azure PaaS map puts data in context against compute, integration, and governance, and walks the five-question framework across all of them. And the messaging and orchestration post covers the delivery guarantees these patterns lean on.