- Регистрация
- 9 Май 2015
- Сообщения
- 1,605
- Баллы
- 155
This guide provides a step-by-step technical walkthrough for migrating a .NET application from MediatR to LiteBus. It maps MediatR's concepts directly to their LiteBus equivalents, focusing on code and configuration changes.
Since MediatR is shifting to a commercial model, LiteBus offers a powerful, free, and open-source alternative with a more ambitious feature set.
Core Philosophy: From Generic to Semantic
The primary difference between the two libraries is their approach to abstractions:
- MediatR uses a generic IRequest<TResponse> for both commands and queries.
- LiteBus provides distinct ICommand<TResult>, IQuery<TResult>, and IEvent interfaces.
This semantic distinction in LiteBus enforces Command Query Separation (CQS) at the compiler level, making your application's intent clearer.
Step 1: Update Project Dependencies
First, replace the MediatR packages with the LiteBus equivalents.
Remove MediatR:
dotnet remove MediatR
Add LiteBus:
# Core DI extension (pulls in Command, Query, Event modules)
dotnet add package LiteBus.Extensions.Microsoft.DependencyInjection
# Optional: For referencing only abstractions in domain/app layers
dotnet add package LiteBus.Commands.Abstractions
dotnet add package LiteBus.Queries.Abstractions
dotnet add package LiteBus.Events.Abstractions
Step 2: Update Dependency Injection (Program.cs)
The registration process is similar. LiteBus scans assemblies to discover handlers automatically.
Before (MediatR):
builder.Services.AddMediatR(cfg =>
{
cfg.LicenseKey = "<License Key here>";
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
});
After (LiteBus):
builder.Services.AddLiteBus(liteBus =>
{
var appAssembly = typeof(Program).Assembly;
// Each module scans for its specific constructs (handlers, validators, etc.)
liteBus.AddCommandModule(module => module.RegisterFromAssembly(appAssembly));
liteBus.AddQueryModule(module => module.RegisterFromAssembly(appAssembly));
liteBus.AddEventModule(module => module.RegisterFromAssembly(appAssembly));
});
Step 3: Convert Message Contracts
This is a direct mapping of interfaces.
Requests (Commands and Queries)
- IRequest -> ICommand
- IRequest<TResponse> -> ICommand<TResponse> (for state changes) or IQuery<TResponse> (for data retrieval).
Before (MediatR):
// Command
public class ShipOrderCommand : IRequest { ... }
// Query
public class GetProductQuery : IRequest<ProductDto> { ... }
After (LiteBus):
// Command
public class ShipOrderCommand : ICommand { ... }
// Query
public class GetProductQuery : IQuery<ProductDto> { ... }
Notifications (Events)
- INotification -> IEvent or a Plain Old C# Object (POCO).
Before (MediatR):
public class OrderShippedEvent : INotification { ... }
After (LiteBus):
// Option 1: Using the interface
public class OrderShippedEvent : IEvent { ... }
// Option 2: Using a POCO (recommended for domain events)
public class OrderShippedEvent { ... }
Step 4: Convert Handlers
Handler interfaces also map directly.
Before (MediatR):
public class ShipOrderCommandHandler : IRequestHandler<ShipOrderCommand> { ... }
public class GetProductQueryHandler : IRequestHandler<GetProductQuery, ProductDto> { ... }
public class OrderShippedEventHandler : INotificationHandler<OrderShippedEvent> { ... }
After (LiteBus):
public class ShipOrderCommandHandler : ICommandHandler<ShipOrderCommand> { ... }
public class GetProductQueryHandler : IQueryHandler<GetProductQuery, ProductDto> { ... }
public class OrderShippedEventHandler : IEventHandler<OrderShippedEvent> { ... }
Step 5: Convert Pipeline Behaviors and Processors
MediatR uses IPipelineBehavior for cross-cutting concerns and has separate pre/post-processors. LiteBus unifies this into a structured pipeline of Pre-Handler, Post-Handler, and Error-Handler.
- IRequestPreProcessor<TRequest> -> ICommandPreHandler<TCommand>
- IRequestPostProcessor<TRequest, TResponse> -> ICommandPostHandler<TCommand, TResponse>
- IPipelineBehavior logic can be split into pre and post handlers.
Let's convert a MediatR LoggingBehavior.
Before (MediatR):
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
{
// Pre-logic
_logger.LogInformation("Handling {RequestName}", typeof(TRequest).Name);
var response = await next();
// Post-logic
_logger.LogInformation("Handled {RequestName}", typeof(TRequest).Name);
return response;
}
}
After (LiteBus):
The logic is split into two distinct, globally-scoped handlers.
// This runs BEFORE every command.
public class GlobalLoggingPreHandler : ICommandPreHandler
{
public Task PreHandleAsync(ICommand command, CancellationToken cancellationToken)
{
_logger.LogInformation("Handling {CommandName}", command.GetType().Name);
return Task.CompletedTask;
}
}
// This runs AFTER every command.
public class GlobalLoggingPostHandler : ICommandPostHandler
{
public Task PostHandleAsync(ICommand command, object? result, CancellationToken cancellationToken)
{
_logger.LogInformation("Handled {CommandName}", command.GetType().Name);
return Task.CompletedTask;
}
}
Step 6: Convert Exception Handling
MediatR uses IRequestExceptionHandler and IRequestExceptionAction. LiteBus uses a single, powerful IErrorHandler concept that can be scoped globally or to a specific message.
Before (MediatR):
public class MyExceptionHandler : IRequestExceptionHandler<MyRequest, MyResponse, MyException>
{
public Task Handle(MyRequest request, MyException exception, RequestExceptionHandlerState<MyResponse> state, CancellationToken cancellationToken)
{
// Handle the exception, potentially setting a response
state.SetHandled(new MyResponse());
return Task.CompletedTask;
}
}
After (LiteBus):
public class MyErrorHandler : ICommandErrorHandler<MyCommand>
{
public Task HandleErrorAsync(MyCommand command, object? messageResult, Exception exception, CancellationToken cancellationToken)
{
if (exception is MyException myException)
{
// Access the execution context to set a result and stop the exception
AmbientExecutionContext.Current.Abort(new MyCommandResult());
}
// If Abort() is not called, the original exception will be re-thrown
return Task.CompletedTask;
}
}
Step 7: Update Dispatching Code
Instead of a single IMediator, LiteBus provides specific interfaces.
Before (MediatR):
public class MyController
{
private readonly IMediator _mediator;
public async Task MyAction()
{
await _mediator.Send(new MyCommand());
await _mediator.Publish(new MyEvent());
}
}
After (LiteBus):
public class MyController
{
private readonly ICommandMediator _commandMediator;
private readonly IEventPublisher _eventPublisher;
public async Task MyAction()
{
await _commandMediator.SendAsync(new MyCommand());
await _eventPublisher.PublishAsync(new MyEvent());
}
}
More Features of LiteBus
MediatR is, by its own definition, a "low-ambition" library. LiteBus is more ambitious and provides built-in solutions for complex, enterprise-level problems. Once migrated, you have access to:
- Durable Command Inbox: Guarantee at-least-once execution of critical commands with the [StoreInInbox] attribute. This provides message-queue-like resilience for in-process operations without adding heavy infrastructure.
- Advanced Event Concurrency: Configure event handlers to run sequentially or in parallel, both within priority groups and across them. This is essential for performance tuning complex event-driven workflows.
- First-Class Streaming: Use IStreamQuery<T> and IAsyncEnumerable<T> to efficiently handle large datasets without high memory usage.
- DI-Agnostic Core: Decoupled from any specific DI container, with official support for Microsoft DI and Autofac.
- Advanced Handler Filtering: Use tags and dynamic predicates to select which handlers run in a given context, enabling patterns like feature flags or environment-specific logic.
This guide covers the direct mapping of concepts for a smooth migration from MediatR. However, the true benefits of LiteBus are found in the features that go beyond basic mediation. To explore these capabilities in detail, the provides in-depth guides on the Durable Command Inbox, advanced event concurrency, handler filtering, and other best practices.
Источник: