Skip to content

Instantly share code, notes, and snippets.

@captainsafia
Last active February 3, 2024 21:51
Show Gist options
  • Save captainsafia/de20f0845bd1e7e14b403b63486834bb to your computer and use it in GitHub Desktop.
Save captainsafia/de20f0845bd1e7e14b403b63486834bb to your computer and use it in GitHub Desktop.

Note

The details shared here are based on the prototyping done as of 2/3/2024. If you're reading this gist at a later time, the details might have changed. In any case, it's here for archival reasons.

This gist is written in response to a tweet by Adam Storr (@WestDiscGolf) in response to a code sample that was shared of the current prototype of our workstream to sharpen up event-based apps.

Sure. The "with provider" and "map events" methods, how they work together and are processed under the hood. For example when/how a message arrives and triggers the binding/handler. Thanks for your reply :-)

The prototype code looks like this:

var builder = Host.CreateApplicationBuilder();

builder.AddAzureQueueService("orders");

builder.Services
  .AddEvents()
  .AddAzureStorageQueueEventProvider("orders")
  .AddTimerEventProvider("cron", "*/5 * * * *");  

var app = builder.Build();

app.UseRouting();
app.UseExceptionHandler();

var orders = app.WithProvider("orders");
var cron = app.WithProvider("cron");

orders.MapEvent("order-received", (Order order, ILogger<Program> logger) =>
{
  // Handler logic here
});

cron.MapEvent((TimerInfo timer) =>
{
  // Handler logic here
});

app.Run();

The implementation of the framework relies heavily on keyed DI to create keyed instances of various providers in the application. In the sample above, you'll see frequent references to "orders" and "crons" which are unique identifiers for the event providers they are associated with and the infrastructure that supports them.

The WithProvider method on the IHost produces a INamedEventHandlerBuilder, which is analgous to minimal API's IEndpointRouteBuilder with the caveat that any event handlers and middleware associated with a particular named instance are unique to the instance. In this framework, you can have global middleware, which runs on events coming from all event providers. You can also have provider-specific middleware, which only runs on events coming from a provider with a given name.

The INamedEventHandlerBuilder exposes an AddEventHandler API which is able to register EventHandlers onto a named EventHandlerDataSource. EventHandlers consist of an EventDelegate and the metadata required to route to the that EventDelegate.

The MapEvent method shipped with the framework is a stub that is designed to be intercepted by source generated code using Roslyn's preview interceptors feature that maps an arbitrary Delegate handler to a strongly-typed EventDelegate.

I've made references to EventDelegate several times so far. That's basically the construct that matches RequestDelegate in ASP.NET Core and has a shape like EventContext -> Task.

So, the overall flow of events that occur when at application startup consits of:

flowchart TD;
    B[Intercepted implementations for MapEvent generated at compile-time];
    A[Keyed dependencies for provider registered in DI];
    C[Intercepted MapEvent handlers are called to registered handlers to named sources];
    D[Application pipeline is compiled on host include global and provider-specific middleware];
    B-->A;
    A-->D;
    D-->C;
Loading

And the mapping of concepts from the eventing space to the ASP.NET Core space is as follows:

flowchart LR;
    A[EventContext] --> B(HttpContext)
    I[EventDelegate] --> J(RequestDelegate)
    C[NamedEventHandlerDataSource] --> D(EndpointDataSource)
    E[NamedEventHandlerBuilder] --> F(IEndpointRouteBuilder)
    G[MiddlewareDataSource] --> H(IApplicationBuilder.Middlewares)
Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment