LiteBus
A lightweight, high-performance mediator for building clean, scalable, and testable .NET applications with CQS and DDD.
LiteBus is a modern, powerful, and perpetually free alternative to MediatR.
It is, and always will be, governed by the MIT license. LiteBus helps you implement Command Query Separation (CQS) and Domain-Driven Design (DDD) patterns by providing a clean, decoupled architecture for your application's business logic.
Why LiteBus?
- Truly Semantic: Go beyond generic requests. With first-class contracts like
ICommand<TResult>,IQuery<TResult>, andIEvent, your code becomes self-documenting. You can even publish clean POCOs as domain events. - High Performance: Designed for minimal overhead. Handler metadata is cached on startup, and dependencies are resolved lazily. Large datasets are handled efficiently with
IAsyncEnumerable<T>streaming. - Granular Pipeline Control: Go beyond simple "behaviors". LiteBus provides a full pipeline with distinct, type-safe
Pre-Handlers,Post-Handlers, andError-Handlersfor each message. - Open Generic Handlers: Write a single pre/post/error handler once and have it automatically apply to every message type matching its constraints — perfect for cross-cutting concerns like logging, validation, and metrics.
- Advanced Event Concurrency: Take full control of event processing. Configure
SequentialorParallelexecution for both priority groups and for handlers within the same group to fine-tune throughput. - Resilient & Durable: Guarantee at-least-once execution for critical commands with a built-in durable Command Inbox.
- DI-Agnostic by Design: Decoupled from any specific DI container. First-class integration for Microsoft DI and Autofac is provided, with a simple adapter pattern to support others.
Quick Start
1. Install Packages
Install the modules you need. The core messaging infrastructure is included automatically.
# For Commands dotnet add package LiteBus.Commands.Extensions.Microsoft.DependencyInjection # For Queries dotnet add package LiteBus.Queries.Extensions.Microsoft.DependencyInjection # For Events dotnet add package LiteBus.Events.Extensions.Microsoft.DependencyInjection
2. Define Your Messages and Handlers
Command: Create a Product
// The Command public sealed record CreateProductCommand(string Name, decimal Price) : ICommand<Guid>; // The Handler public sealed class CreateProductCommandHandler : ICommandHandler<CreateProductCommand, Guid> { public Task<Guid> HandleAsync(CreateProductCommand command, CancellationToken cancellationToken) { var productId = Guid.NewGuid(); // Your business logic here... Console.WriteLine($"Product '{command.Name}' created with ID: {productId}"); return Task.FromResult(productId); } }
Query: Get a Product by ID
// The Query public sealed record GetProductByIdQuery(Guid Id) : IQuery<ProductDto>; // The DTO public sealed record ProductDto(Guid Id, string Name, decimal Price); // The Handler public sealed class GetProductByIdQueryHandler : IQueryHandler<GetProductByIdQuery, ProductDto> { public Task<ProductDto> HandleAsync(GetProductByIdQuery query, CancellationToken cancellationToken) { // Your data retrieval logic here... var product = new ProductDto(query.Id, "Sample Product", 99.99m); return Task.FromResult(product); } }
Event: A Product was Created
// The Event (can be a simple POCO) public sealed record ProductCreatedEvent(Guid ProductId, string Name); // The Handler public sealed class ProductCreatedEventHandler : IEventHandler<ProductCreatedEvent> { public Task HandleAsync(ProductCreatedEvent @event, CancellationToken cancellationToken) { // Your side-effect logic here (e.g., send an email, update a projection) Console.WriteLine($"Handling side effects for new product '{@event.Name}'..."); return Task.CompletedTask; } }
3. Configure and Mediate
Register LiteBus and its modules in Program.cs, then inject the mediators into your services or controllers.
// In Program.cs builder.Services.AddLiteBus(liteBus => { var appAssembly = typeof(Program).Assembly; // Scan the assembly for all command/query/event handlers liteBus.AddCommandModule(module => module.RegisterFromAssembly(appAssembly)); liteBus.AddQueryModule(module => module.RegisterFromAssembly(appAssembly)); liteBus.AddEventModule(module => module.RegisterFromAssembly(appAssembly)); }); // In your API Controller or Service public class ProductController : ControllerBase { private readonly ICommandMediator _commandMediator; private readonly IQueryMediator _queryMediator; private readonly IEventMediator _eventMediator; public ProductController(ICommandMediator cmd, IQueryMediator qry, IEventMediator evt) { _commandMediator = cmd; _queryMediator = qry; _eventMediator = evt; } [HttpPost] public async Task<IActionResult> Create(CreateProductCommand command) { // 1. Send a command to create the product var productId = await _commandMediator.SendAsync(command); // 2. Publish an event to handle side effects await _eventMediator.PublishAsync(new ProductCreatedEvent(productId, command.Name)); // 3. Query for the newly created product to return it var productDto = await _queryMediator.QueryAsync(new GetProductByIdQuery(productId)); return Ok(productDto); } }
Key Features
A Semantic & Granular Pipeline
LiteBus provides a rich set of interfaces that make your pipeline explicit and powerful. Each message type (Command, Query, Event) has its own set of Pre-Handlers, Post-Handlers, and Error-Handlers.
This allows for fine-grained control, such as running validation logic, enriching a message, or logging results at specific stages of the pipeline. You can also share data between handlers via the AmbientExecutionContext.
// A semantic validator that runs before the main handler public sealed class PlaceOrderValidator : ICommandValidator<PlaceOrderCommand> // or ICommandPreHandler<PlaceOrderCommand> { public Task ValidateAsync(PlaceOrderCommand command, CancellationToken cancellationToken) { if (command.LineItems.Count == 0) { throw new ValidationException("At least one line item is required."); } return Task.CompletedTask; } } // A post-handler that runs after the command is successfully handled public sealed class PlaceOrderNotifier : ICommandPostHandler<PlaceOrderCommand, Guid> { public Task PostHandleAsync(PlaceOrderCommand command, Guid orderId, CancellationToken cancellationToken) { // Publish an OrderPlacedEvent with the result from the command handler return _eventPublisher.PublishAsync(new OrderPlacedEvent(orderId)); } }
The mediator also supports polymorphic dispatch, allowing handlers for a base message type to process any derived messages.
Open Generic Handlers for Cross-Cutting Concerns
Write a single handler that automatically applies to every command, query, or event. No changes to existing messages required.
// This pre-handler runs before EVERY command — registered once, applied everywhere public sealed class CommandLogger<T> : ICommandPreHandler<T> where T : ICommand { public Task PreHandleAsync(T message, CancellationToken cancellationToken) { Console.WriteLine($"Executing: {typeof(T).Name}"); return Task.CompletedTask; } } // RegisterFromAssembly automatically discovers open generic handlers in the assembly builder.Services.AddLiteBus(liteBus => { liteBus.AddCommandModule(module => { module.RegisterFromAssembly(typeof(Program).Assembly); // picks up CommandLogger<> too }); }); // Or register explicitly if the handler is in a different assembly builder.Services.AddLiteBus(liteBus => { liteBus.AddCommandModule(module => { module.Register(typeof(CommandLogger<>)); // from an external library module.RegisterFromAssembly(typeof(Program).Assembly); }); });
RegisterFromAssembly automatically discovers open generic handlers in the scanned assembly — no separate Register(typeof(...)) call is needed. Use explicit registration only when the handler lives in a different assembly. LiteBus closes the generic at startup for each concrete message type. Generic constraints (where T : ICommand, class, struct, new()) are fully respected. Registration order does not matter.
Advanced Eventing with Concurrency Control
Define execution priority and concurrency for event handlers to manage complex workflows.
// This handler runs first [HandlerPriority(1)] public class ValidateOrderHandler : IEventHandler<OrderPlacedEvent> { /* ... */ } // These two handlers run concurrently after the validation handler completes [HandlerPriority(2)] public class PersistOrderHandler : IEventHandler<OrderPlacedEvent> { /* ... */ } [HandlerPriority(2)] public class NotifyInventoryHandler : IEventHandler<OrderPlacedEvent> { /* ... */ } // Configure execution strategy at runtime await _eventMediator.PublishAsync(e, new EventMediationSettings { Execution = new EventMediationExecutionSettings { // Run priority groups one after another PriorityGroupsConcurrencyMode = ConcurrencyMode.Sequential, // Run handlers within the same group in parallel HandlersWithinSamePriorityConcurrencyMode = ConcurrencyMode.Parallel } });
Contextual Filtering with Tags
Execute specific handlers based on runtime context, such as the request origin.
// This handler only runs if the "api" tag is specified [HandlerTag("api")] public class ApiValidationPreHandler : ICommandPreHandler<CreateProductCommand> { /* ... */ } // Mediate with a tag await _commandMediator.SendAsync(command, new CommandMediationSettings { Filters = { Tags = ["api"] } });
Durable Command Inbox for Guaranteed Execution
Ensure critical commands are never lost by marking them for durable storage and deferred processing.
// This command will be stored in a durable inbox and processed by a background service [StoreInInbox] public sealed record ProcessPaymentCommand(Guid OrderId, decimal Amount) : ICommand;
Modular by Design
LiteBus is built on a modular, DI-agnostic runtime. You only install what you need.
Migrating from MediatR
LiteBus offers a more semantic and feature-rich alternative to MediatR. If you're migrating, here’s how the core concepts map.
-
Requests (
IRequest<TResponse>andIRequest)
In MediatR,IRequestis used for both commands and queries. LiteBus separates these for CQS clarity:- Use
ICommand<TResult>for operations that change state and return a value. - Use
IQuery<TResult>for read-only operations. - Use
ICommandfor fire-and-forget operations that don't return a value.
- Use
-
Notifications (
INotification)
MediatR'sINotificationis equivalent to LiteBus'sIEvent. A key advantage of LiteBus is that you don't need to implement any interface. You can publish any Plain Old C# Object (POCO) as an event, keeping your domain model completely clean. -
Stream Requests (
IStreamRequest<TResponse>)
This maps directly toIStreamQuery<TResult>in LiteBus, which returns anIAsyncEnumerable<TResult>. LiteBus semantically treats streams as a query concern. -
Pipeline Behaviors (
IPipelineBehavior<,>)
MediatR uses a genericIPipelineBehaviorfor cross-cutting concerns. LiteBus provides a more granular and type-safe pipeline with distinct stages for each message type:ICommandPreHandler<TCommand>/IQueryPreHandler<TQuery>: Run before the main handler. Ideal for validation (ICommandValidatoris a semantic shortcut for this).ICommandPostHandler<TCommand, TResult>/IQueryPostHandler<TQuery, TResult>: Run after the main handler, with access to the result.ICommandErrorHandler<TCommand>/IQueryErrorHandler<TQuery>: Centralized error handling for specific message types.- Open generic handlers (e.g.,
MyHandler<T> : ICommandPreHandler<T> where T : ICommand) work similarly to MediatR's open genericIPipelineBehavior<,>— register once and they apply to all matching message types automatically.
This granular approach eliminates the need for generic pipeline behaviors and provides a more expressive and maintainable way to build your processing pipeline.
Our Commitment to Open Source
LiteBus was created to provide the .NET community with a modern, high-performance, and truly free open-source tool. We believe essential infrastructure libraries should be community-driven and accessible to everyone without financial barriers.
LiteBus will always be free and licensed under the MIT license.
We are committed to maintaining and evolving LiteBus as a community project. Contributions are welcome, and we encourage you to get involved.
Documentation
For detailed documentation, feature guides, and examples, please visit the LiteBus Wiki.