The EventGrid and Durable Functions Pattern
Reliable Orchestration Without Service Bus

When you want resilient orchestration in Azure, you usually reach for Azure Service Bus, Logic Apps, or Durable Functions with internal queues. That approach works fine, but it can also feel heavy handed when you just need to coordinate a few microservices or workflows without the massive footprint of a full broker.
Without sounding like Morpheus “What if I told you, you could build a fully reliable orchestration system, complete with retries, state tracking, and fan out/fan in behaviour, using just Azure Event Grid and Durable Functions?”
Below we’ll check out the EventGrid & Durable Functions pattern, a lean alternative to queue based orchestration that’s perfect for event driven distributed systems, domain workflows, and API-to-API coordination where you want durable state without centralised messaging infrastructure.
Rethinking Orchestration in the Cloud
Traditional orchestration often relies on Service Bus or Storage Queues to trigger background processing. Each step in a workflow pushes messages into a queue, which are later processed by downstream functions. This model is proven and reliable, but it comes with extra management, queue dead lettering, message expiry, scaling rules, and hidden coupling between producers and consumers.
Azure Event Grid provides a different approach, a pure publish subscribe event router that can fan out events to multiple handlers without coupling them directly. It’s lightweight, fast, and designed for event driven architectures, but by itself, it doesn’t provide state or reliability guarantees.
That’s where Durable Functions come in. Durable Functions can persist orchestration state in storage and resume execution deterministically after failures. Combining the two gives you the best of both worlds, Event Grid’s reactive decoupling and Durable Functions, built in resilience.
The Core Idea
The pattern looks like this:
Event producers publish domain events to Event Grid.
Durable Functions act as event driven orchestrators that listen for those events.
Each orchestrator replays its workflow deterministically based on persisted state.
Event Grid handles fan out, retries, and delivery, while Durable Functions handle state and coordination.
No queue management, no manual checkpoints, no external workflow engine.

This model scales horizontally, supports fan out and compensation, and uses only serverless components that scale to zero when idle.
Publishing an Event
An event can come from anywhere, an API endpoint, a blob upload, or a domain action. Publishing to Event Grid is just a single API call.
using Azure.Messaging.EventGrid;
using Azure;
var client = new EventGridPublisherClient(
new Uri("https://myapp-events.westeurope-1.eventgrid.azure.net/api/events"),
new AzureKeyCredential("<event-grid-key>")
);
var data = new { OrderId = 1234, Status = "Placed" };
var evt = new EventGridEvent(
"orders/placed",
"OrderCreated",
"1.0",
data
);
await client.SendEventAsync(evt);
Once published, Event Grid guarantees at-least-once delivery to all subscribers, including Durable Functions orchestrators.
Creating an EventGrid Triggered Function
Event Grid triggers are lightweight and cost effective. Each event delivery invokes the function once.
[Function("StartOrderOrchestration")]
public async Task RunAsync([EventGridTrigger] EventGridEvent evt,
[DurableClient] DurableClientContext context,
ILogger log)
{
var order = evt.Data.ToObjectFromJson<OrderCreated>();
log.LogInformation("Received order {OrderId}", order.OrderId);
string instanceId = await context.Client.StartNewAsync(
nameof(OrderOrchestrator),
order
);
log.LogInformation("Started orchestration {InstanceId}", instanceId);
}
Each incoming event spawns a durable workflow with a unique instance ID, perfect for correlating domain entities like orders or users.
Orchestrating the Workflow
The orchestrator function expresses the business process declaratively.
[Function(nameof(OrderOrchestrator))]
public static async Task RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext ctx)
{
var order = ctx.GetInput<OrderCreated>();
await ctx.CallActivityAsync(nameof(ValidateOrderActivity), order);
await ctx.CallActivityAsync(nameof(ReserveInventoryActivity), order);
await ctx.CallActivityAsync(nameof(SendConfirmationEmailActivity), order);
// Emit a completion event
var evt = new EventGridEvent(
"orders/completed",
"OrderCompleted",
"1.0",
new { order.OrderId }
);
var publisher = ctx.CreateEventGridPublisher();
await publisher.SendEventAsync(evt);
}
Because Durable Functions checkpoint state after every awaited call, if the function app restarts mid execution, it resumes automatically from the last completed step.
You gain durability without queues, each orchestration is replayed deterministically using the Durable Task Framework’s history log.
Handling Fan Out / Fan In
Durable Functions support fan out/fan in patterns natively. You can process multiple activities in parallel and aggregate results.
[Function(nameof(NotifyVendors))]
public static async Task RunAsync([OrchestrationTrigger] TaskOrchestrationContext ctx)
{
var order = ctx.GetInput<OrderCreated>();
var vendors = await ctx.CallActivityAsync<List<string>>(
nameof(GetVendorsActivity), order);
var tasks = vendors.Select(v =>
ctx.CallActivityAsync(nameof(SendVendorNotificationActivity), v));
await Task.WhenAll(tasks);
await ctx.CallActivityAsync(nameof(MarkOrderReadyActivity), order);
}
Event Grid isn’t doing the fan out here, Durable Functions is. Event Grid merely triggers the orchestrations, and they handle their own internal parallelism.
Publishing Completion Events
A key advantage of combining these two services is the ability to emit new domain events at each stage. When a durable orchestration completes, you can publish follow up events back to Event Grid, keeping the system reactive and decoupled.
[Function(nameof(PublishOrderCompletedEvent))]
public static async Task RunAsync([ActivityTrigger] OrderCreated order)
{
var client = new EventGridPublisherClient(
new Uri(Environment.GetEnvironmentVariable("EventGridTopicUrl")!),
new AzureKeyCredential(Environment.GetEnvironmentVariable("EventGridKey")!)
);
var evt = new EventGridEvent(
"orders/completed",
"OrderCompleted",
"1.0",
new { order.OrderId }
);
await client.SendEventAsync(evt);
}
Downstream services subscribed to OrderCompleted, analytics, shipping, invoicing, will automatically receive notifications, no queues required.
Handling Failures and Retries
Event Grid and Durable Functions both offer built-in retry mechanisms.
Event Grid retries delivery for up to 24 hours with exponential backoff.
Durable Functions replay orchestration state automatically after transient failures.
To make failure handling explicit, you can wrap activities with retry policies:
var retry = new RetryOptions(firstRetryInterval: TimeSpan.FromSeconds(5), maxNumberOfAttempts: 5)
{
Handle = ex => ex is HttpRequestException
};
await ctx.CallActivityWithRetryAsync(nameof(ReserveInventoryActivity), retry, order);
This pattern ensures that downstream systems can temporarily fail without breaking the orchestration.
Observability and Correlation
Tracing is straightforward when everything flows through Event Grid and Durable Functions.
You can enrich each event with a correlation ID and inject it into the orchestration context:
evt = evt.WithCorrelationId(ctx.InstanceId);
Durable Functions persist instance IDs in Azure Storage, and Event Grid forwards event metadata such as eventType, subject, and traceparent headers.
This makes it easy to connect distributed traces across the system using OpenTelemetry or Application Insights.
In Application Insights, you’ll see orchestration executions visualised as hierarchical traces, the orchestrator as a parent span, activities as children, and Event Grid events as external dependencies.
Benefits of the EventGrid and Durable Pattern
This combination gives you several architectural advantages:
No dedicated broker - Event Grid handles routing without message queues.
Durable state - Orchestration checkpoints are persisted in Azure Storage automatically.
True decoupling - Event producers and consumers never reference each other.
Scale to zero - Both services are fully serverless, so you pay only when events flow.
Simple retry semantics - Event Grid’s delivery guarantees plus Durable Functions’ replay logic create end-to-end reliability.
It’s a perfect fit for systems where you need resilience and traceability but don’t want the load of managing queues, topics, or dead letter policies.
Example: Processing Insurance Claims
Imagine an insurance claim processing system with several steps, claim submission, document validation, fraud scoring, and payout approval.
Traditionally, this would involve a Service Bus topic with multiple subscriptions and queue triggered functions, each one handling part of the process.
With Event Grid ans Durable Functions, the same pipeline becomes a self contained orchestration triggered by an event such as ClaimSubmitted. Each step becomes an activity, and when complete, the orchestrator emits a ClaimProcessed event.
Downstream services like notifications, analytics, and auditing all subscribe to the event type they care about. The architecture remains event driven and fully observable, but without Service Bus infrastructure.
Deployment and Security Considerations
You can deploy both Event Grid topics and Function Apps declaratively via Bicep or ARM. Each subscriber (the Function App) uses Event Grid subscriptions with Azure AD authentication.
When securing the pipeline:
Use Managed Identity for publishing events.
Ensure Event Grid topic filters restrict inbound subjects (e.g.,
orders/*).Configure Durable Function storage accounts with private endpoints.
This ensures the pattern works securely even within private VNets or hybrid networks.
Limitations
While this pattern is powerful, it’s not a universal replacement for Service Bus.
Event Grid is best for event driven communication, not command driven or transactional scenarios. It doesn’t support message sessions or ordered delivery, and while at-least-once is good enough for most workflows, it’s not exactly once.
If your system requires strict FIFO ordering, high message throughput (millions per second), or guaranteed persistence for every event, Service Bus or Event Hubs remain the right tools.
But for most orchestrations that span seconds to minutes, where each event represents a state transition, the EventGrid and Durable pattern is simpler, cheaper, and easier to reason about.
State Machines Over Events
Think of this architecture as a distributed state machine driven by events.
Each Durable Function orchestration holds the state, and each Event Grid event represents a state transition.
Instead of queuing messages, you publish facts about what happened, and orchestrators react accordingly.
Is it for you?
This pattern is one of the cleanest ways to build resilient, event driven orchestrations in modern Azure architectures. It eliminates queue complexity, scales automatically, and keeps every component naturally decoupled. By thinking in terms of events and durable state rather than messages and queues, you gain a more declarative, observable, and fault-tolerant architecture, one that’s elegant in concept and in code. Sometimes, reliable orchestration doesn’t need a broker at all, just two Azure services working together.






