Event Grid Domain Events¶
Trigger: HTTP + Event Grid Output | State: stateless | Guarantee: at-least-once | Difficulty: intermediate
Overview¶
The examples/messaging-and-pubsub/eventgrid_domain_events/ project shows how an HTTP-triggered Azure Function can
publish custom domain events such as OrderPlaced and OrderShipped to an Event Grid custom topic, while an Event
Grid-triggered subscriber handles those events with structured logging.
This pattern is useful when you want business actions inside a function app to emit integration-friendly domain events without coupling publishers directly to every downstream subscriber.
When to Use¶
- You want one function endpoint to accept business commands and emit domain events.
- You want subscribers to react independently to the same event stream.
- You want lightweight, stateless event publishing with Event Grid custom topics.
When NOT to Use¶
- You need strict exactly-once guarantees.
- You need ordered, session-aware, or broker-side dead-letter semantics better served by Service Bus.
- You need long-running workflow coordination or stateful sagas inside the same recipe.
Architecture¶
flowchart LR
app[Application / HTTP publisher] --> topic[Event Grid Custom Topic]
topic --> subscriberA[Subscriber: fulfillment]
topic --> subscriberB[Subscriber: notifications]
topic --> subscriberC[Subscriber: analytics]
Behavior¶
sequenceDiagram
participant Client as Client / upstream app
participant Publisher as publish_order_event
participant Topic as Event Grid custom topic
participant Subscriber as handle_order_domain_event
Client->>Publisher: POST OrderPlaced / OrderShipped
Publisher->>Publisher: Validate payload and build domain event
Publisher->>Topic: Publish custom Event Grid event
Topic->>Subscriber: Deliver subscribed event
Subscriber->>Subscriber: Log event type, subject, and payload
Implementation¶
The publisher uses an HTTP trigger plus @app.event_grid_output(...) to emit a func.EventGridOutputEvent to a custom
topic. The subscriber uses @app.event_grid_trigger(...) to receive those events after an Event Grid subscription is
configured.
Prerequisites¶
- Python 3.10+
- Azure Functions Core Tools v4
- An Event Grid custom topic and access key
- An Event Grid subscription that points the custom topic at the subscriber function
Project Structure¶
examples/messaging-and-pubsub/eventgrid_domain_events/
|-- function_app.py
|-- host.json
|-- local.settings.json.example
|-- pyproject.toml
`-- README.md
The HTTP function turns an incoming business payload into a domain event envelope:
@app.route(route="orders/events", methods=["POST"], auth_level=func.AuthLevel.ANONYMOUS)
@app.event_grid_output(
arg_name="output_event",
topic_endpoint_uri="MyEventGridTopicUriSetting",
topic_key_setting="MyEventGridTopicKeySetting",
)
def publish_order_event(
req: func.HttpRequest,
output_event: func.Out[func.EventGridOutputEvent],
) -> func.HttpResponse:
payload = req.get_json()
output_event.set(
func.EventGridOutputEvent(
id="...",
subject=f"/orders/{payload['order_id']}",
event_type="Contoso.Orders.OrderPlaced",
event_time=datetime.datetime.utcnow(),
data=_build_event_payload(payload),
data_version="1.0",
)
)
return func.HttpResponse(status_code=202)
The subscriber reacts to the custom topic event and logs the business context:
@app.event_grid_trigger(arg_name="event")
def handle_order_domain_event(event: func.EventGridEvent) -> None:
payload = event.get_json() or {}
logger.info(
"Handled order domain event",
extra={
"event_id": event.id,
"event_type": event.event_type,
"subject": event.subject,
"order_id": payload.get("orderId"),
},
)
Configuration¶
Set these values in local.settings.json when running locally:
| Variable | Purpose |
|---|---|
AzureWebJobsStorage |
Local/runtime storage used by Azure Functions Core Tools |
FUNCTIONS_WORKER_RUNTIME |
Must be python |
MyEventGridTopicUriSetting |
Event Grid custom topic endpoint URI |
MyEventGridTopicKeySetting |
Event Grid custom topic access key |
Run Locally¶
cd examples/messaging-and-pubsub/eventgrid_domain_events
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp local.settings.json.example local.settings.json
func start
Publish a sample domain event:
curl -X POST "http://localhost:7071/api/orders/events" \
-H "Content-Type: application/json" \
-d '{"event_type":"OrderPlaced","order_id":"ORD-1001","customer_id":"C-42","amount":149.99,"currency":"USD"}'
To exercise the subscriber locally, post a sample Event Grid payload to the webhook endpoint:
curl -X POST "http://localhost:7071/runtime/webhooks/EventGrid?functionName=handle_order_domain_event" \
-H "Content-Type: application/json" \
-d '[{"id":"evt-1001","topic":"demo","subject":"/orders/ORD-1001","eventType":"Contoso.Orders.OrderPlaced","eventTime":"2026-01-01T00:00:00Z","data":{"orderId":"ORD-1001","customerId":"C-42","amount":149.99,"currency":"USD"},"dataVersion":"1.0","metadataVersion":"1"}]'
Expected Output¶
Accepted order domain event publication {"event_type":"Contoso.Orders.OrderPlaced","order_id":"ORD-1001",...}
Handled order domain event {"event_type":"Contoso.Orders.OrderPlaced","subject":"/orders/ORD-1001",...}
Production Considerations¶
- Idempotency: Event Grid is at-least-once, so subscribers must tolerate duplicate delivery.
- Contracts: keep event names, subjects, and payload fields stable for downstream consumers.
- Filtering: use Event Grid subscription filters when only some subscribers need selected event types.
- Observability: log
event_id,event_type,subject, and business identifiers likeorder_id. - Security: store the topic URI and key in app settings or use identity-based connections when supported.