Skip to content

Architecture

Paul Mcilreavy edited this page Dec 22, 2025 · 2 revisions

This page explains how the Azure Event Grid Simulator works internally. It's intended for contributors and anyone interested in understanding the system design.

System Context

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
Loading

Component Overview

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

Event Flow

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
Loading

Processing Stages

  1. 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.

  2. Schema Detection: The parser detects whether the payload is EventGrid schema or CloudEvents schema based on content-type headers and payload structure.

  3. Event Parsing: Events are parsed and validated against schema requirements. Invalid events are rejected and recorded in the event history.

  4. Filtering: Each subscriber's filter configuration is evaluated. Events that don't match are skipped for that subscriber.

  5. Queuing: Matching events are wrapped in PendingDelivery objects and added to the in-memory queue.

  6. Delivery: The background service polls the queue every second and attempts delivery for items whose scheduled time has passed.

  7. Retry/Dead-Letter: Failed deliveries are either requeued with exponential backoff or dead-lettered based on the failure type.

Subscriber Delivery

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 DeliveryResult indicating success or failure

Retry Mechanism

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.

Schema Support

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.

Configuration Model

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

Design Patterns

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

Source Code Structure

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

Related Topics

References

Official Microsoft documentation for the Azure services and concepts this simulator emulates:

Clone this wiki locally