LiteBus.Queries.Abstractions
0.17.1
See the version list below for details.
dotnet add package LiteBus.Queries.Abstractions --version 0.17.1
NuGet\Install-Package LiteBus.Queries.Abstractions -Version 0.17.1
<PackageReference Include="LiteBus.Queries.Abstractions" Version="0.17.1" />
paket add LiteBus.Queries.Abstractions --version 0.17.1
#r "nuget: LiteBus.Queries.Abstractions, 0.17.1"
// Install LiteBus.Queries.Abstractions as a Cake Addin #addin nuget:?package=LiteBus.Queries.Abstractions&version=0.17.1 // Install LiteBus.Queries.Abstractions as a Cake Tool #tool nuget:?package=LiteBus.Queries.Abstractions&version=0.17.1
<h1 align="center"> <br> <a href="https://github.com/litenova/LiteBus"> <img src="assets/logo/icon.png"> </a> <br> LiteBus <br> </h1>
<h4 align="center">An easy-to-use and ambitious and in-process mediator to implement CQS</h4>
<p align="center">
<a href="https://github.com/litenova/LiteBus/actions/workflows/release.yml"> <img src="https://github.com/litenova/LiteBus/actions/workflows/release.yml/badge.svg" alt="CI/CD Badge"> </a>
<a href='https://coveralls.io/github/litenova/LiteBus?branch=main'> <img src='https://coveralls.io/repos/github/litenova/LiteBus/badge.svg?branch=main' alt='Coverage Status' /> </a> <a href="https://www.nuget.org/packages/LiteBus"> <img src="https://img.shields.io/nuget/vpre/LiteBus.svg" alt="LiteBus Nuget Version"> </a> </p>
<p align="center"> <a href="#specifications-and-features">Specifications and Features</a> • <a href="#installation">Installation</a> • <a href="#configuration">Configuration</a> • <a href="#features-and-usages">Features and Usages</a> • <a href="#extensibility">Extensibility</a> <a href="#roadmap">Roadmap</a> </p>
Specifications and Features
- Developed with .NET 7
- Independent (No external dependencies)
- Reduced use of reflection
- Provides polymorphic dispatch and handling of messages with support for covariance and contravariance
- Core Messaging Types include:
ICommand
: Command without resultICommand<TResult>
: Command with a resultIQuery<TResult>
: QueryIStreamQuery<TResult>
: Query yieldingIAsyncEnumerable<TResult>
IEvent
: Event
- Designed for flexibility and extensibility
- Modular architecture: Abstractions and implementations are provided in distinct packages
- Allows ordering of handlers
- Can handle plain messages (class types without specific interface implementations)
- Supports generic messages
- Features both global and individual pre and post handlers. These handlers also support covariance and contravariance
- Events do not necessarily need to inherit from
IEvent
, accommodating DDD scenarios. This is beneficial for maintaining clean domain events without binding them to any particular library interface.
Installation
LiteBus modules and features are released in separate Nuget Packages. Follow the instructions to install packages:
Metapackage
The metapackage contains all the features. You can find the configuration of this package in Configuration section.
LiteBus
contains the implementation of all featuresLiteBus.Extensions.MicrosoftDependencyInjection
provides integration with Microsoft Dependency Injection. It's useful for ASP.NET Core applications.
Commands
The commands feature consists of the following packages:
LiteBus.Commands.Abstractions
- contains command abstractions such asICommand
,ICommandHandler
, etc.LiteBus.Commands
- contains the implementation of commandsLiteBus.Commands.Extensions.MicrosoftDependencyInjection
- provides integration with Microsoft Dependency Injection. It's useful for ASP.NET Core applications.
Queries
The queries feature consists of the following packages:
LiteBus.Queries.Abstractions
- contains query abstractions such asIQuery
,IStreamQuery
,IQueryHandler
, etc.LiteBus.Queries
- contains the implementation of queriesLiteBus.Queries.Extensions.MicrosoftDependencyInjection
- provides integration with Microsoft Dependency Injection. It's useful for ASP.NET Core applications.
Events
The events feature consists of the following packages:
LiteBus.Events.Abstractions
- contains event abstractions such asIEvent
,IEventHandler
, etc.LiteBus.Events
- contains the implementation of eventsLiteBus.Events.Extensions.MicrosoftDependencyInjection
- provides integration with Microsoft Dependency Injection. It's useful for ASP.NET Core applications.
Configuration
Follow the instruction below to configure LiteBus.
Microsoft Dependency Injection (ASP.NET Core, etc.)
You can configure each module of LiteBus as needed in the ConfigureServices
method of Startup.cs
:
services.AddLiteBus(builder =>
{
builder.AddCommands(commandBuilder =>
{
commandBuilder.RegisterFromAssembly(typeof(CreateProductCommand).Assembly) // Register all handlers from the specified Assembly
.RegisterPreHandler<ProductValidationHandler>()
.RegisterPostHandler<ProductAuditingHandler>();
})
.AddQueries(queryBuilder =>
{
queryBuilder.RegisterFromAssembly(typeof(GetAllProducts).Assembly);
})
.AddEvents(eventBuilder =>
{
eventBuilder.RegisterFromAssembly(typeof(NumberCreatedEvent).Assembly);
});
});
Features and Usages
The following examples demonstrate the features and usages of LiteBus.
Commands
Commands are intended to perform actions that change the state of the system. To use commands, follow the instructions below.
Command Contracts
Specify your commands by implementing:
ICommand
- a command without a resultICommand<TCommandResult>
- a command with a result
Command Handler Contracts
ICommandHandler<TCommand>
- an asynchronous command handler that does not return a resultICommandHandler<TCommand, TCommandResult>
- an asynchronous command handler that returns a resultISyncCommandHandler<TCommand>
- a synchronous command handler that does not return a resultISyncCommandHandler<TCommand, TCommandResult>
- a synchronous command handler that returns a result
Command Mediator/Dispatcher
You can use the ICommandMediator
or ICommandDispatcher
to execute your commands. Use them by injecting one of the
interfaces into your desired class.
Command Pre Handlers
Pre-handlers allow you to perform actions before a command gets handled. They are handy for performing validation and starting transactions. By implementing the pre handlers, the pre handlers are executed automatically when executing a command.
ICommandPreHandler<TCommand>
- this generic pre handler is executed on the pre-handle phase of the specifiedTCommand
. This pre handler supports generic variance.ICommandPreHandler
- this pre handler acts as a global command pre handler. It's executed on every command pre-handle phase.
Command Post-Handlers
Post-handlers allow you to perform actions after a command gets handled. They are handy for committing transactions and auditing. By implementing the post-handlers, the post-handlers are executed automatically when executing a command.
ICommandPostHandler<TCommand>
- this generic post-handler is executed on the post-handle phase of the specifiedTCommand
. This post-handler supports generic variance.ICommandPostHandler<TCommand, TCommandResult>
- this generic post-handler is executed on the post-handle phase of the specifiedTCommand
which hasTCommandResult
type.ICommandPostHandler
- this post-handler acts as a global command post-handler. It's executed on every command post-handle phase.
Command Error Handlers
Error handlers allow you to catch and handle any errors thrown during the pre-handle, handle, and post-handle phase of a command. You can implement error handlers by deriving from:
ICommandErrorHandler<TCommand>
- this generic post-handler is executed on any phase of the specified command that runs into an error. It supports generic variance.ICommandErrorHandler
- acts as the global command error handler.
Examples
A command without Result
// A command without result
public class CreateProductCommand : ICommand
{
public string Title { get; set; }
}
// The async handler
public class CreateProductCommandHandler : ICommandHandler<CreateProductCommand>
{
public Task HandleAsync(CreateProductCommand command, CancellationToken cancellationToken = default)
{
// Process here...
}
}
// The sync handler
public class CreateProductSyncCommandHandler : ISyncCommandHandler<CreateProductCommand>
{
public void Handle(CreateProductCommand command)
{
// Process here...
}
}
// The pre handler
public class ProductCommandPreHandler : ICommandPreHandler<CreateProductCommand>
{
public Task PreHandleAsync(IHandleContext<CreateProductCommand> context)
{
// You can access the command through the context
// Process here...
}
}
// The post handler
public class ProductCommandPostHandler : ICommandPostHandler<CreateProductCommand, long>
{
public Task PostHandleAsync(IHandleContext<CreateProductCommand, long> context)
{
// You can access the command and its result through the context
// Process here...
}
}
A command with result
// A command with result
public class CreateProductCommand : ICommand<long>
{
public string Title { get; set; }
}
// The async handler
public class CreateProductCommandHandler : ICommandHandler<CreateProductCommand, long>
{
public Task<long> HandleAsync(CreateProductCommand command, CancellationToken cancellationToken = default)
{
// Process here...
}
}
// The sync handler
public class CreateProductSyncCommandHandler : ISyncCommandHandler<CreateProductCommand, long>
{
public long Handle(CreateProductCommand command)
{
// Process here...
}
}
// The pre handler
public class ProductCommandPreHandler : ICommandPreHandler<CreateProductCommand>
{
public Task PreHandleAsync(IHandleContext<CreateProductCommand> context)
{
// You can access the command through the context
// Process here...
}
}
// The post handler
public class ProductCommandPostHandler : ICommandPostHandler<CreateProductCommand>
{
public Task PostHandleAsync(IHandleContext<CreateProductCommand> context)
{
// You can access the command through the context
// Process here...
}
}
Queries
Queries are intended to query data without changing the state of the system. To use queries, follow the instructions below.
Query Contracts
Specify your queries by implementing:
IStreamQuery<TQueryResult>
- a query that returns a stream of data. The result is represented in the formIAsyncEnumerable<TQueryResult>
.IQuery<TQueryResult>
- a query with a result
Query Handler Contracts
IQueryHandler<TQuery, TQueryResult>
- an asynchronous query handler that returns a resultIStreamQueryHandler<TQuery, TQueryResult>
- an asynchronous query handler that returns a stream of data in the formIAsyncEnumerable<TQueryResult>
ISyncQueryHandler<TQuery, TQueryResult>
- a synchronous query handler that returns a result
Query Mediator/Dispatcher
You can use the IQueryMediator
or IQueryDispatcher
to execute your queries. Use them by injecting one of the
interfaces into your desired class.
Query Pre Handlers
Pre-handlers allow you to perform actions before a query gets handled. They are handy for performing validation and starting transactions. By implementing the pre handlers, the pre handlers are executed automatically when executing a query.
IQueryPreHandler<TQuery>
- this generic pre handler is executed on the pre-handle phase of the specifiedTQuery
. This pre handler supports generic variance.IQueryPreHandler
- this pre handler acts as a global query pre handler. It's executed on every query pre-handle phase.
Query Post-Handlers
Post-handlers allow you to perform actions after a query gets handled. They are handy for committing transactions and auditing. By implementing the post-handlers, the post-handlers are executed automatically when executing a query.
IQueryPostHandler<TQuery>
- this generic post-handler is executed on the post-handle phase of the specifiedTQuery
. This post-handler supports generic variance.IQueryPostHandler<TQuery, TQueryResult>
- this generic post-handler is executed on the post-handle phase of the specifiedTQuery
which hasTQueryResult
type.IQueryPostHandler
- this post-handler acts as a global query post-handler. It's executed on every query post-handle phase.
Query Error Handlers
Error handlers allow you to catch and handle any errors thrown during the pre-handle, handle, and post-handle phase of a query. You can implement error handlers by deriving from:
IQueryErrorHandler<TQuery>
- this generic post-handler is executed on any phase of the specified query that runs into an error. It supports generic variance.IQueryErrorHandler
- acts as the global query error handler.
Examples
A simple query
// A simple query
public class GetSingleProductQuery : IQuery<Product>
{
public long Id { get; set; }
}
// The async handler
public class GetSingleProductQueryHandler : IQueryHandler<GetSingleProductQuery, Product>
{
public Task<Product> HandleAsync(GetSingleProductQuery query, CancellationToken cancellationToken = default)
{
// Process here...
}
}
// The sync handler
public class GetSingleProductSyncQueryHandler : ISyncQueryHandler<GetSingleProductQuery, Product>
{
public Product Handle(GetSingleProductQuery query)
{
// Process here...
}
}
// The pre handler
public class ProductQueryPreHandler : IQueryPreHandler<GetSingleProductQuery>
{
public Task PreHandleAsync(IHandleContext<GetSingleProductQuery> context)
{
// You can access the query through the context
// Process here...
}
}
// The post handler
public class ProductQueryPostHandler : IQueryPostHandler<GetSingleProductQuery>
{
public Task PostHandleAsync(IHandleContext<GetSingleProductQuery> context)
{
// You can access the query through the context
// Process here...
}
}
An stream query
// A stream query
public class GetAllProductsQuery : IStreamQuery<Product>
{
}
// The async handler
public class GetSingleProductQueryHandler : IStreamQueryHandler<GetSingleProductQuery, Product>
{
public IAsyncEnumerable<Product> StreamAsync(GetSingleProductQuery query, CancellationToken cancellationToken = default)
{
// Process here...
}
}
// The pre handler
public class ProductQueryPreHandler : IQueryPreHandler<GetSingleProductQuery>
{
public Task PreHandleAsync(IHandleContext<GetSingleProductQuery> context)
{
// You can access the query through the context
// Process here...
}
}
Events
Events act as informative messages. They can have multiple handlers.
Event Contracts
Specify your events by implementing:
IEvent
Event Handler Contracts
IEventHandler<TEvent>
- an asynchronous event handlerISyncEventHandler<TEvent>
- a synchronous event handler
Event Mediator/Dispatcher
You can use the IEventMediator
, IEventDispatcher
, or IEventPublisher
to execute your events. Use them by injecting
one of the interfaces into your desired class.
Event Pre Handlers
Pre-handlers allow you to perform actions before an event gets handled. They are handy for performing validation and starting transactions. By implementing the pre handlers, the pre handlers are executed automatically when executing an event.
IEventPreHandler<TEvent>
- this generic pre handler is executed on the pre-handle phase of the specifiedTEvent
. This pre handler supports generic variance.IEventPreHandler
- this pre handler acts as a global event pre handler. It's executed on every event pre-handle phase.
Event Post-Handlers
Post-handlers allow you to perform actions after an event gets handled. They are handy for committing transactions and auditing. By implementing the post-handlers, the post-handlers are executed automatically when executing an event.
IEventPostHandler<TEvent>
- this generic post-handler is executed on the post-handle phase of the specifiedTEvent
. This post-handler supports generic variance.IEventPostHandler
- this post-handler acts as a global event post-handler. It's executed on every event post-handle phase.
Event Error Handlers
Error handlers allow you to catch and handle any errors thrown during the pre-handle, handle, and post-handle phase of an event. You can implement error handlers by deriving from:
IEventErrorHandler<TEvent>
- this generic post-handler is executed on any phase of the specified event that runs into an error. It supports generic variance.IEventErrorHandler
- acts as the global event error handler.
Examples
A simple event
// A simple event
public class ProductCreatedEvent : IEvent
{
public long Id { get; set; }
}
// The async handler 1
public class ProductCreatedEventHandler1 : IEventHandler<ProductCreatedEvent>
{
public Task HandleAsync(ProductCreatedEvent @event, CancellationToken cancellationToken = default)
{
// Process here...
}
}
// The async handler 2
public class ProductCreatedEventHandler2 : IEventHandler<ProductCreatedEvent>
{
public Task HandleAsync(ProductCreatedEvent @event, CancellationToken cancellationToken = default)
{
// Process here...
}
}
// The async handler 3
public class ProductCreatedEventHandler3 : ISyncEventHandler<ProductCreatedEvent>
{
public void HandleAsync(ProductCreatedEvent @event, CancellationToken cancellationToken = default)
{
// Process here...
}
}
// The pre handler
public class ProductEventPreHandler : IEventPreHandler<ProductCreatedEvent>
{
public Task PreHandleAsync(IHandleContext<ProductCreatedEvent> context)
{
// You can access the event through the context
// Process here...
}
}
// The post handler
public class ProductEventPostHandler : IEventPostHandler<ProductCreatedEvent>
{
public Task PostHandleAsync(IHandleContext<ProductCreatedEvent> context)
{
// You can access the event through the context
// Process here...
}
}
Inheritance
The LiteBus uses the actual type of a message to determine the corresponding handler(s). Consider the following inheritance:
// The base command
public class CreateFileCommand : ICommand
{
public string Name { get; set; }
}
// The derived command
public class CreateImageCommand : CreateFileCommand
{
public int Width { get; set; }
public int Height { get; set; }
}
// The second derived command without a handler
public class CreateDocumentCommand : CreateFileCommand
{
public string Author { get; set; }
}
// The base command handler
public class CreateFileCommandHandler : ICommandHandler<CreateFileCommand>
{
public Task HandleAsync(CreateFileCommand command, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
// The derived command handler
public class CreateImageCommandHandler : ICommandHandler<CreateImageCommand>
{
public Task HandleAsync(CreateImageCommand command, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
### Delivering Message to the Actual Type Handler
If a user tries to send the `CreateImageCommand` as `CreateFileCommand`, the LiteBus will deliver the command to `CreateImageCommandHandler`.
```csharp
CreateFileCommand command = new CreateImageCommand();
_mediator.SendAsync(command);
Delivering Message to the Less Derived (Base Type) Handler
If a user tries to send the CreateDocumentCommand
as CreateFileCommand
or as it is, the LiteBus will deliver the
command to CreateFileCommandHandler
since the CreateDocumentCommand
does not have a handler.
CreateFileCommand command = new CreateDocumentCommand();
_mediator.SendAsync(command);
// or
var command = new CreateDocumentCommand();
_mediator.SendAsync(command);
Note: In such scenarios, the LiteBus will only deliver the message to the direct base class' handler if there is any.
ExecutionContext in LiteBus
LiteBus's ExecutionContext
provides an encapsulated context that represents the current operational scope. It
primarily contains a CancellationToken
, useful for operations that may need cooperative cancellation.
Accessing ExecutionContext in Handlers
Within your handlers, you can access the current ExecutionContext
via the AmbientExecutionContext
:
var currentExecutionContext = AmbientExecutionContext.Current;
var cancellationToken = currentExecutionContext?.CancellationToken;
Extensibility
To Be Added
Roadmap
- Providing Out-of-Process Message Handling
- RabbitMQ
- Kafka
- Azure Event Bus
- Saga Support
- Outbox Support
- More Parallel Capabilities
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net7.0
- LiteBus.Messaging.Abstractions (>= 0.17.1)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on LiteBus.Queries.Abstractions:
Package | Downloads |
---|---|
LiteBus.Queries
LiteBus is an easy-to-use and ambitious in-process mediator providing the foundation to implement Command Query Separation (CQS). It is implemented with minimal reflection and instead utilizes covariance and contravariance to provide its core functionality. |
|
LiteBus
LiteBus is an easy-to-use and ambitious in-process mediator providing the foundation to implement Command Query Separation (CQS). It is implemented with minimal reflection and instead utilizes covariance and contravariance to provide its core functionality. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
0.24.3 | 1,522 | 5/2/2024 |
0.24.2 | 205 | 4/16/2024 |
0.24.1 | 155 | 1/30/2024 |
0.23.1 | 2,141 | 1/11/2024 |
0.23.0 | 228 | 12/23/2023 |
0.22.0 | 168 | 12/23/2023 |
0.21.0 | 184 | 12/18/2023 |
0.20.2 | 193 | 12/8/2023 |
0.20.1 | 179 | 12/5/2023 |
0.20.0 | 1,096 | 12/5/2023 |
0.19.1 | 172 | 11/25/2023 |
0.19.0 | 165 | 11/23/2023 |
0.18.4 | 157 | 11/23/2023 |
0.18.3 | 173 | 11/23/2023 |
0.18.2 | 174 | 11/22/2023 |
0.18.1 | 813 | 11/6/2023 |
0.18.0 | 206 | 11/1/2023 |
0.17.1 | 182 | 10/20/2023 |
0.16.0 | 221 | 9/26/2023 |
0.15.1 | 2,407 | 6/23/2023 |
0.15.0 | 207 | 6/23/2023 |
0.14.1 | 218 | 6/22/2023 |
0.14.0 | 505 | 11/30/2022 |
0.13.0 | 864 | 10/7/2022 |
0.12.0 | 8,137 | 1/6/2022 |
0.11.3 | 4,294 | 12/27/2021 |
0.11.2 | 499 | 12/27/2021 |
0.11.1 | 505 | 12/26/2021 |
0.11.0 | 531 | 12/23/2021 |
0.10.2 | 497 | 12/22/2021 |
0.10.1 | 510 | 12/20/2021 |
0.10.0 | 613 | 12/15/2021 |
0.9.1 | 1,032 | 10/25/2021 |
0.9.0 | 683 | 10/25/2021 |
0.8.1 | 3,932 | 10/9/2021 |
0.8.0 | 1,224 | 9/7/2021 |
0.7.0 | 1,486 | 8/19/2021 |
0.6.3 | 1,060 | 8/9/2021 |
0.6.2 | 680 | 8/9/2021 |
0.6.1 | 670 | 8/9/2021 |
0.6.0 | 702 | 8/9/2021 |
0.5.0 | 720 | 7/8/2021 |
0.4.0 | 999 | 4/13/2021 |
0.3.1 | 654 | 4/13/2021 |
0.2.1 | 672 | 4/9/2021 |
0.2.0 | 678 | 4/6/2021 |
0.1.0 | 752 | 3/17/2021 |
v.0.17.1
⦁ Add `Items` property to `IExecutionContext` to allow passing data between handlers.
v.0.17.0
⦁ Rename `AddCommands` method to `AddCommandModule`
⦁ Rename `AddEvents` method to `AddEventModule`
⦁ Rename `AddQueries` method to `AddQueryModule`
v.0.16.0
⦁ Introduced execution context using AsyncLocal functionality, accessible through AmbientExecutionContext.
⦁ Renamed `RegisterFrom` to `RegisterFromAssembly` in module builders.
⦁ Standardized namespace for all files in the `LiteBus.Messaging.Abstractions` project to `LiteBus.Messaging.Abstractions`, irrespective of folder path.
⦁ Removed `HandleContext` as a parameter from post and pre handlers.
v.0.15.1
⦁ Removed `IEvent` constraint from event handlers, allowing objects to be passed as events without implementing the `IEvent` interface.
v.0.15.0
⦁ Added overload method to event publisher for passing an object as a message.
⦁ Removed `LiteBus` prefix from module constructor names.
v.0.14.1
⦁ Upgraded dependency packages.
v.0.14.0
⦁ Upgraded to .NET 7.
v.0.13.0
⦁ Replaced `ICommandBase` with `ICommand`.
⦁ Replaced `IQueryBase` with `IQuery`.
⦁ Renamed `ILiteBusModule` to `IModule`.
⦁ Removed methods `RegisterPreHandler`, `RegisterHandler`, and `RegisterPostHandler`, replacing them with `Register`.
⦁ Removed superfluous base interfaces.
v.0.12.0
⦁ Added support to message registry for registering any class type as a message.
v.0.11.3
⦁ Fixed bug: Execute error handlers instead of pre handlers during error phase.
v.0.11.2
⦁ Fixed bug: Considered the count of indirect error handlers when determining if an exception should be rethrown.
v.0.11.1
⦁ Disabled nullable reference types.
⦁ Ensured error handlers cover errors in pre and post handlers.
v.0.11.0
⦁ Introduced non-generic message registration overloads for events, queries, and messaging configuration.
⦁ Removed the sample project.
⦁ Added unit tests for events and queries.