LiteBus 0.17.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package LiteBus --version 0.17.1                
NuGet\Install-Package LiteBus -Version 0.17.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LiteBus" Version="0.17.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add LiteBus --version 0.17.1                
#r "nuget: LiteBus, 0.17.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install LiteBus as a Cake Addin
#addin nuget:?package=LiteBus&version=0.17.1

// Install LiteBus as a Cake Tool
#tool nuget:?package=LiteBus&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 result
    • ICommand<TResult>: Command with a result
    • IQuery<TResult>: Query
    • IStreamQuery<TResult>: Query yielding IAsyncEnumerable<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.

Commands

The commands feature consists of the following packages:

Queries

The queries feature consists of the following packages:

Events

The events feature consists of the following packages:

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 result
  • ICommand<TCommandResult> - a command with a result
Command Handler Contracts
  • ICommandHandler<TCommand> - an asynchronous command handler that does not return a result
  • ICommandHandler<TCommand, TCommandResult> - an asynchronous command handler that returns a result
  • ISyncCommandHandler<TCommand> - a synchronous command handler that does not return a result
  • ISyncCommandHandler<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 specified TCommand. 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 specified TCommand. This post-handler supports generic variance.
  • ICommandPostHandler<TCommand, TCommandResult> - this generic post-handler is executed on the post-handle phase of the specified TCommand which has TCommandResult 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 form IAsyncEnumerable<TQueryResult>.
  • IQuery<TQueryResult> - a query with a result
Query Handler Contracts
  • IQueryHandler<TQuery, TQueryResult> - an asynchronous query handler that returns a result
  • IStreamQueryHandler<TQuery, TQueryResult> - an asynchronous query handler that returns a stream of data in the form IAsyncEnumerable<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 specified TQuery. 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 specified TQuery. This post-handler supports generic variance.
  • IQueryPostHandler<TQuery, TQueryResult> - this generic post-handler is executed on the post-handle phase of the specified TQuery which has TQueryResult 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 handler
  • ISyncEventHandler<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 specified TEvent. 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 specified TEvent. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.24.3 103 5/2/2024
0.24.2 122 4/16/2024
0.24.1 133 1/30/2024
0.23.1 140 1/11/2024
0.23.0 168 12/23/2023
0.22.0 133 12/23/2023
0.21.0 126 12/18/2023
0.20.2 112 12/8/2023
0.20.1 133 12/5/2023
0.20.0 137 12/5/2023
0.19.1 140 11/25/2023
0.19.0 127 11/23/2023
0.18.4 128 11/23/2023
0.18.3 130 11/23/2023
0.18.2 129 11/22/2023
0.18.1 120 11/6/2023
0.18.0 118 11/1/2023
0.17.1 111 10/20/2023
0.16.0 154 9/26/2023
0.15.1 170 6/23/2023
0.15.0 148 6/23/2023
0.14.1 150 6/22/2023
0.14.0 344 11/30/2022
0.13.0 386 10/7/2022
0.12.0 1,401 1/6/2022
0.11.3 338 12/27/2021
0.11.2 279 12/27/2021
0.11.1 281 12/26/2021
0.11.0 299 12/23/2021
0.10.2 264 12/22/2021
0.10.1 279 12/20/2021
0.10.0 319 12/15/2021
0.9.1 379 10/25/2021
0.9.0 326 10/25/2021
0.8.1 598 10/9/2021
0.8.0 379 9/7/2021
0.7.0 364 8/19/2021
0.6.3 364 8/9/2021
0.6.2 353 8/9/2021
0.6.1 337 8/9/2021
0.6.0 354 8/9/2021
0.5.0 453 7/8/2021
0.4.0 754 4/13/2021
0.3.1 412 4/13/2021
0.2.1 427 4/9/2021
0.2.0 411 4/6/2021
0.1.0 471 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.