Skip to content

Add replayable events on eventing. #9043

@mitchdenny

Description

@mitchdenny

Overview

We’ve identified the need for certain events in the Aspire application model to be “replayable.” This will allow late subscribers to receive previously published events, improving reliability and developer ergonomics—especially for scenarios where events are used to collect information from disparate parts of the codebase.

Proposed Design

  1. Opt-In via Marker Interface
    Events that wish to be replayable will implement an IReplayableEvent marker interface.
  2. Event Scoping
    Aspire supports two flavors of distributed application events:
    Global events: Not associated with any resource.
    Resource-scoped events: Tied to a specific resource.
    Replay buffers must be maintained for both global and resource-scoped events. When a subscriber joins, they will receive the appropriate buffered events for the subscription’s scope (global or per-resource).
  3. Event Buffering and Replay
    When a replayable event is published, it is added to an internal, thread-safe buffer.
    Global events: buffer keyed by event type.
    Resource-scoped events: buffer keyed by event type and resource identifier.
    When a new subscriber is added:
    For global events: all buffered global events of the subscribed type are replayed to the subscriber.
    For resource-scoped events: all buffered events for the specified resource/event type are replayed.
    Buffers must be thread-safe (e.g., using ConcurrentQueue or immutable collections).
    When replaying, a snapshot (copy) of the events in the buffer should be taken and iterated over for the new subscription.
  4. Dispatch Behavior Constraint
    Important: Replayable events (IReplayableEvent) may only be published using EventDispatchBehavior.NonBlocking.
    Attempting to publish a replayable event with a blocking dispatch behavior (e.g., Blocking, BlockingCollectResults) must throw an exception or otherwise fail-fast.
    This prevents subtle bugs, as replay semantics are inherently asynchronous and not compatible with blocking dispatch.
  5. Thread Safety
    All buffers and subscription logic must be thread-safe to allow concurrent publishing and subscription from different parts of the application.6.
  6. Other ConsiderationsRetention policy: Consider whether to retain all replayable events forever, only a fixed number per type/resource, or until explicitly cleared.

Documentation: The marker interface and dispatch behavior constraint should be clearly documented.
Extensibility: Start with immediate replay on subscription; if needed, more advanced signaling (subscriber readiness) can be considered later.

Example Usage

public class FooEvent : IReplayableEvent { ... }

// Publishing a global replayable event
eventing.Publish(new FooEvent(), EventDispatchBehavior.NonBlocking);

// Subscribing globally (receives previously published FooEvents)
eventing.Subscribe<FooEvent>(e => { ... });

// Subscribing for a specific resource (receives previously published events for that resource)
eventing.Subscribe<FooEvent>("resourceId", e => { ... });

This feature will allow developers to reliably receive important events even if their subscriptions are registered after those events are initially published, without introducing blocking or timing issues.

Metadata

Metadata

Assignees

Labels

area-app-modelIssues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions