-
-
Notifications
You must be signed in to change notification settings - Fork 41
Architecture
This page explains how the Azure Event Grid Simulator works internally. It's intended for contributors and anyone interested in understanding the system design.
The simulator acts as a local replacement for Azure Event Grid, receiving events via HTTPS and delivering them to configured subscribers.
flowchart LR
Publisher["Event Publisher"]
Developer["Developer"]
subgraph Simulator["Azure Event Grid Simulator"]
API["POST /api/events"]
Dashboard["Dashboard UI"]
end
HTTP["HTTP Webhook"]
SB["Azure Service Bus"]
SQ["Azure Storage Queue"]
EH["Azure Event Hub"]
Publisher -->|HTTPS| API
API -->|HTTP/HTTPS| HTTP
API -->|AMQP| SB
API -->|HTTPS| SQ
API -->|AMQP| EH
Developer -->|HTTPS| Dashboard
The simulator is built with ASP.NET Core and follows a layered architecture:
Controllers Layer
-
NotificationController- Receives event POST requests and dispatches commands -
SubscriptionValidationController- Handles webhook validation handshakes -
DashboardController- Serves the monitoring UI
Middleware Layer
-
EventGridMiddleware- Validates requests, parses events, checks SAS key authentication - Request validation includes size limits (1MB per event, 1.5MB total payload)
Domain Layer
-
SendNotificationEventsCommand- Filters events per subscriber and queues deliveries -
DeliveryQueue- In-memory queue holding pending deliveries -
RetryDeliveryBackgroundService- Background worker polling the queue every second - Delivery services (
HttpEventDeliveryService,ServiceBusEventDeliveryService,StorageQueueEventDeliveryService,EventHubEventDeliveryService) - Type-specific delivery logic -
DeadLetterService- Writes failed events to disk -
EventHistoryStore- Tracks events for dashboard display
Infrastructure Layer
- Settings classes for configuration binding
- Schema parsers and formatters for EventGrid/CloudEvents conversion
When an event is published to the simulator, it flows through several stages:
sequenceDiagram
participant P as Publisher
participant M as Middleware
participant C as Controller
participant H as CommandHandler
participant Q as DeliveryQueue
participant R as RetryService
participant D as DeliveryService
participant S as Subscriber
P->>M: POST /api/events
M->>M: Validate SAS key
M->>M: Check size limits
M->>M: Detect & parse schema
M->>C: Forward parsed events
C->>H: SendNotificationEventsCommand
loop For each subscriber
H->>H: Apply filters
H->>Q: Enqueue delivery
end
Note over R: Background polling (1s interval)
R->>Q: Get due deliveries
R->>D: Attempt delivery
D->>S: Send event
alt Success
S-->>D: 200 OK
D-->>R: DeliveryResult.Success
R->>Q: Remove from queue
else Failure (retryable)
S-->>D: 503 Error
D-->>R: DeliveryResult.Failure
R->>R: Calculate next retry
R->>Q: Requeue with delay
else Failure (non-retryable)
S-->>D: 400 Bad Request
D-->>R: DeliveryResult.Failure
R->>R: Dead-letter event
end
-
Request Validation: The middleware validates the SAS key (if configured), checks payload size limits (1MB per event, 1.5MB total), and rejects invalid requests with appropriate error codes.
-
Schema Detection: The parser detects whether the payload is EventGrid schema or CloudEvents schema based on content-type headers and payload structure.
-
Event Parsing: Events are parsed and validated against schema requirements. Invalid events are rejected and recorded in the event history.
-
Filtering: Each subscriber's filter configuration is evaluated. Events that don't match are skipped for that subscriber.
-
Queuing: Matching events are wrapped in
PendingDeliveryobjects and added to the in-memory queue. -
Delivery: The background service polls the queue every second and attempts delivery for items whose scheduled time has passed.
-
Retry/Dead-Letter: Failed deliveries are either requeued with exponential backoff or dead-lettered based on the failure type.
Each subscriber type has a dedicated delivery service implementing IEventDeliveryService. The RetryDeliveryBackgroundService routes deliveries to the appropriate service based on subscriber type:
| Subscriber Type | Delivery Service | Protocol |
|---|---|---|
| HTTP Webhook | HttpEventDeliveryService |
HTTP/HTTPS POST |
| Service Bus | ServiceBusEventDeliveryService |
AMQP |
| Storage Queue | StorageQueueEventDeliveryService |
HTTPS REST |
| Event Hub | EventHubEventDeliveryService |
AMQP |
All delivery services share common responsibilities:
- Format events according to the configured delivery schema
- Add standard Event Grid headers (
aeg-event-type,aeg-subscription-name, etc.) - Handle connection pooling and client caching
- Return a
DeliveryResultindicating success or failure
The retry system follows Azure Event Grid's exponential backoff schedule. When delivery fails, the response status code determines the retry behaviour:
Immediate Dead-Letter (non-retryable): 400, 401, 403, 413
Retry with Minimum Delay:
- 404: Wait at least 5 minutes
- 408: Wait at least 2 minutes
- 503: Wait at least 30 seconds
- Other codes: Standard exponential backoff
Backoff Schedule:
| Attempt | Delay |
|---|---|
| 1 | 10 seconds |
| 2 | 30 seconds |
| 3 | 1 minute |
| 4 | 5 minutes |
| 5 | 10 minutes |
| 6 | 30 minutes |
| 7 | 1 hour |
| 8 | 3 hours |
| 9 | 6 hours |
| 10+ | 12 hours |
Delivery is dead-lettered when TTL expires or max attempts is reached.
The simulator supports both EventGrid and CloudEvents schemas with automatic detection and optional conversion.
Input Processing: The schema detector examines the Content-Type header and payload structure. CloudEvents uses application/cloudevents+json, while EventGrid schema is detected by the presence of eventType and dataVersion fields.
Internal Model: Both schemas are parsed into SimulatorEvent, a unified internal representation that exposes common properties regardless of the original format.
Output Formatting: Each subscriber can specify a deliverySchema to receive events in either format, independent of the input schema. The appropriate formatter (EventGridSchemaFormatter or CloudEventSchemaFormatter) converts the internal model to the requested output format.
Topics and subscribers are configured via appsettings.json and parsed into a settings hierarchy:
-
SimulatorSettings- Root configuration containing topics array and dashboard settings -
TopicSettings- Individual topic with name, port, key, input/output schema, and subscribers -
SubscribersSettings- Container with arrays for each subscriber type (Http, ServiceBus, StorageQueue, EventHub) -
ISubscriberSettings- Common interface for all subscriber types defining name, filter, delivery schema, retry policy, and dead-letter settings
| Pattern | Usage |
|---|---|
| Mediator | Decouples controllers from command handlers |
| Factory | Creates schema parsers and formatters based on detected schema |
| Strategy | Delivery services implement common interface with type-specific logic |
| Background Service | Async delivery processing without blocking requests |
| Queue | Decouples event receipt from delivery for reliability |
src/AzureEventGridSimulator/
├── Controllers/ # HTTP endpoints
├── Domain/
│ ├── Commands/ # Command handlers (mediator pattern)
│ ├── Entities/ # Domain models (SimulatorEvent, PendingDelivery)
│ └── Services/
│ ├── Delivery/ # Subscriber-specific delivery services
│ └── Retry/ # Retry scheduling, queue, dead-letter
├── Infrastructure/
│ ├── Extensions/ # Filtering logic
│ ├── Mediator/ # Custom mediator implementation
│ ├── Middleware/ # Request validation and parsing
│ └── Settings/ # Configuration models
└── Program.cs # Application entry point
- Configuration - How to configure topics and subscribers
- Retry and Dead-Letter - Retry policy details
- Filtering - Event filtering configuration
- Schema Support - Schema detection and conversion
Official Microsoft documentation for the Azure services and concepts this simulator emulates:
Getting Started
Subscribers
Features
Deployment
Reference