Library featuring CQRS pattern and mediator implementations in conjuction with functional Results to achieve exception-safe operations.
Uses a functional Result approach for failure-prone operations.
To utilize all features using Autofac is required.
ICommand
andIQuery
abstractionsICommandHandler
andIQueryHandler
abstractionsICommandDispatcher
andIQueryDispatcher
abstractions and default implementations- Supports decorators and adapters via Autofac's methods (for decorators with Microsoft's DI you may consider something like Scrutor)
For both commands and queries, there are two return options - one that only returns a Result and one that returns an additional entity/DTO contained within the Result.
Every handler must return a Result struct which determines whether the operation succedeed or not, handlers may or may not return additional results contained within the Result struct.
To register the library with the DI container use the ContainerBuilder
or IServiceCollection
extension methods provided by the library:
builder.AddResultCQRS(assembliesToScan);
To register decorators (or adapters) use:
- the attributes provided by AttributeBasedRegistration
- or directly through Autofac, Scrutor or other libraries providing DI decoration support.
If you're using Autofac (or built-in attribute based Autofac) - you can register multiple decorators and they'll be applied in the order that you register them - read more at Autofac's docs regarding decorators and adapters.
Documentation available at https://result-cqrs.mikym.me/.
Library offers a simple error catching and logging within the provided default Dispatcher
implementations.
A command without a concrete result:
public record SimpleCommand : ICommand
{
public bool IsSuccess { get; }
public SimpleCommand(bool isSuccess = true)
=> IsSuccess = isSuccess;
}
And a handler that handles it:
public class SimpleCommandHandler : ICommandHandler<SimpleCommand>
{
public async Task<Result> HandleAsync(SimpleCommand command)
{
if (command.IsSuccess)
return Result.FromSuccess();
return new InvalidOperationError();
}
}
A command with a concrete result:
public record SimpleCommandWithConcreteResult : ICommand<int>
{
public bool IsSuccess { get; }
public SimpleCommandWithConcreteResult(bool isSuccess = true)
=> IsSuccess = isSuccess;
}
And a handler that handles it:
public class SimpleCommandHandlerWithConcreteResult : ICommandHandler<SimpleCommandWithConcreteResult, int>
{
public async Task<Result<int>> HandleAsync(SimpleCommandWithConcreteResult command)
{
if (command.IsSuccess)
return 1;
return new InvalidOperationError();
}
}
Then in your service you can use:
public class Service : IService
{
private readonly ICommandDispatcher _commandDispatcher;
public Service(ICommandDispatcher commandDispatcher)
=> _commandDispatcher = commandDispatcher;
public async Task<Result> DoAsync()
{
var result = await _commandDispatcher.DispatchAsync(command)
return new InvalidOperationError();
}
}
Using queries is pretty much same.