A lightweight, high-performance mediator for building clean, scalable, and testable .NET applications with CQS and DDD.
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.
- 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. - 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.
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// 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);
}
}// 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);
}
}// 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;
}
}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);
}
}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.
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
}
});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"] }
});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;LiteBus is built on a modular, DI-agnostic runtime. You only install what you need.
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.
This granular approach eliminates the need for generic pipeline behaviors and provides a more expressive and maintainable way to build your processing pipeline.
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.
For detailed documentation, feature guides, and examples, please visit the LiteBus Wiki.