The Situation
Let's be honest, many of us in the .NET world have relied on MediatR. It's a fantastic library that has shaped how we think about CQRS and in-process messaging. However, with the recent introduction of a licensing model, many developers are looking for free, open-source alternatives for its notification (publish/subscribe) capabilities.
I was in the same boat. I love the pub/sub pattern for decoupling components, but I needed a solution that was not only free but also incredibly fast and architecturally sound, especially for applications using services with specific lifetimes, like Entity Framework's DbContext
.
That's why I created FlashEvents.
FlashEvents is a high-performance, in-memory event publishing library for .NET designed with two core principles in mind: simplicity and speed.
What Makes FlashEvents Different?
FlashEvents focuses exclusively on the "notification" or "event publishing" part of the pattern. Its key features are designed to solve common problems gracefully:
- 🚀 Blazing Fast Performance: It's optimized for low-latency and minimal memory allocations. (We'll get to the benchmarks later!)
- ⚡ Parallel Execution by Default: When you publish an event, all its handlers run concurrently using
Task.WhenAll
. This maximizes throughput without any special configuration. - 🛡️ Scoped Handler Isolation: This is the killer feature. Each event handler is resolved and executed in its own
IServiceScope
. This completely prevents issues with shared state. If one handler uses aDbContext
, it gets its own instance. Another handler can use its ownDbContext
or other scoped services without any conflicts. This is a huge win for reliability. - 🔧 Simple & Fluent API: Setting it up is a breeze with clean dependency injection extensions.
- 🔍 Automatic Handler Discovery: A single line of code can register all your event handlers from an assembly.
Let's See It in Action
Here’s how you can integrate FlashEvents into your project in just a few minutes.
1. Define Your Event
using FlashEvents.Abstractions;
public record OrderCreatedEvent(int OrderId, string CustomerEmail) : IEvent;
2. Create Event Handlers
using FlashEvents.Abstractions;
// Handler 1: Sends a welcome email
public class SendWelcomeEmailHandler : IEventHandler<OrderCreatedEvent>
{
private readonly IEmailService _emailService; // Can be transient or singleton
public SendWelcomeEmailHandler(IEmailService emailService) { /* ... */ }
public async Task Handle(OrderCreatedEvent @event, CancellationToken ct)
{
// ... send email ...
}
}
// Handler 2: Updates analytics using a scoped service
public class UpdateAnalyticsHandler : IEventHandler<OrderCreatedEvent>
{
private readonly IAnalyticsService _analyticsService; // Scoped service
public UpdateAnalyticsHandler(IAnalyticsService analyticsService) { /* ... */ }
public async Task Handle(OrderCreatedEvent @event, CancellationToken ct)
{
// ... update analytics ...
}
}
3. Configure Dependency Injection
using FlashEvents;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
// 1. Add FlashEvents services
builder.Services.AddEventPublisher();
// 2. Automatically discover and register all handlers
builder.Services.AddEventHandlersFromAssembly(Assembly.GetExecutingAssembly());
// 3. Register your other application services
builder.Services.AddTransient<IEmailService, EmailService>();
builder.Services.AddScoped<IanalyticsService, AnalyticsService>(); // Our scoped service
var app = builder.Build();
// ...
4. Publish an Event
using FlashEvents.Abstractions;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class OrdersController : ControllerBase
{
private readonly IEventPublisher _eventPublisher;
public OrdersController(IEventPublisher eventPublisher)
{
_eventPublisher = eventPublisher;
}
[HttpPost]
public async Task<IActionResult> CreateOrder()
{
// ... logic to create an order ...
var orderEvent = new OrderCreatedEvent(123, "test@example.com");
// Publish it. FlashEvents handles the rest.
await _eventPublisher.PublishAsync(orderEvent);
return Ok("Order created and events are being handled.");
}
}
But Is It Fast? The Benchmarks
Talk is cheap. Let's look at the numbers against MediatR v12.5.0.
System: i5-13400F, .NET 8
Scenario 1: Single Handler (vs. MediatR's Parallel Publisher)
Method | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio |
---|---|---|---|---|---|---|
FlashEvents_Publish | 205.1 ns | 1.79 ns | 1.68 ns | 1.00 | 520 B | 1.00 |
MediatR_Publish | 141.19 ns | 1.114 ns | 0.988 ns | 1.55 | 664 B | 3.77 |
Scenario 2: Two Handlers (vs. MediatR's Parallel Publisher)
This test highlights the parallel execution for two handlers.
Method | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio |
---|---|---|---|---|---|---|
FlashEvents_Publish | 269.3 ns | 3.34 ns | 2.79 ns | 1.00 | 712 B | 1.00 |
MediatR_Publish | 171.4 ns | 0.95 ns | 0.79 ns | 0.64 | 824 B | 1.16 |
Why Choose FlashEvents?
If you need the request/response or pipeline behaviors from MediatR, then MediatR is still the tool for the job.
But if you are primarily using its notification publisher feature, FlashEvents offers a compelling alternative:
- Free & Open Source: It's licensed under MIT. No fees, no strings attached.
- Architectural Safety: Built-in scoped execution for handlers is a huge advantage that prevents a common class of bugs related to dependency lifetimes.
- Simplicity: It does one thing and does it well. The API surface is minimal and easy to learn.
- Performance: It's extremely lightweight and holds its own against the industry standard, especially regarding memory allocations.
Give It a Try!
I built FlashEvents to solve a problem I was facing, and I hope it can help you too. It's available on NuGet, and the source code is on GitHub.
- NuGet Package:
FlashEvents
- GitHub Repo:
Anton-Grebenkin/FlashEvents
Top comments (0)