CleanCQRS 1.0.7-preview

This is a prerelease version of CleanCQRS.
There is a newer version of this package available.
See the version list below for details.
dotnet add package CleanCQRS --version 1.0.7-preview                
NuGet\Install-Package CleanCQRS -Version 1.0.7-preview                
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="CleanCQRS" Version="1.0.7-preview" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add CleanCQRS --version 1.0.7-preview                
#r "nuget: CleanCQRS, 1.0.7-preview"                
#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 CleanCQRS as a Cake Addin
#addin nuget:?package=CleanCQRS&version=1.0.7-preview&prerelease

// Install CleanCQRS as a Cake Tool
#tool nuget:?package=CleanCQRS&version=1.0.7-preview&prerelease                

Clean CQRS

Clean CQRS is a library to set up code abstractions that runs commands and queries against a unit of work.

It is minimal library to promote a predictable and testable code base.

Concepts

Without delving into more details about CQRS and Units Of Work, for which there are numerous resources, CleanCQRS breaks concepts down to

1. Unit of Work

A unit of work controls the isolation scope of the work, for example a transaction. Everything is run against a unit of work.

It can be used to compose dependencies, and can also be extended to support more complex concepts.

2. Commands

A command performs an operation and can return a result.

Command handlers can call on to other commands and queries to allow composable reuse.

Command handlers could also inherit from base handlers as an alternative way to reuse code.

3. Queries

A query returns data.

Similarly to command handlers, query handlers can also inherit or call other queries.

4. Pipeline

An pipeline can optionally be used to wrap every command and query run. You can chose to setup a pipeline or run without one.

Download & Install

NuGet

Install-Package CleanCQRS

Command Line

dotnet add package CleanCQRS

Setup

There are multiple of ways to set up your project but the basics are

1. Define a unit of work interface

  • This interface has Run methods on it to invoke a query or command
  • There are base interfaces to inherit from eg IUnitOfWorkCommand or IUnitOfWorkCommandWithoutCancellationToken
  • Add any other dependencies you chose that might be common to many queries and command eg a clock interface IClock to abstract away System.DateTime from your testing.

2. A command must inherit from the ICommand<T> or ICommand interface.

3. A query must inherit from the IQuery<T> interface.

4. Every Command or Query has a single handler

  • Its usually (depending on your style preferences) a good idea to nest them within the Command or Query definition class.

  • This handler must implement IRequestHandler<TUnitOfWork, TCommand, TResult> however base classes are provided to make it easier eg AsyncCommandHandlerBase<TUnitOfWork, TCommand, TResult>

  • It is often a good idea to have your own base class in your central/core project to tie down the Unit of Work interface used, but its up to you.

  • The constructor of a handler can take any necessary dependencies that aren't exposed by the unit of work.

  • Handlers are automatically registered with Dependency Injection

5. Decide if you want a pipeline.

  • If so inherit it from IPipeline<TUnitOfWork, TRequest, TResponse>

  • Pipelines are great for logging/exceptions etc, but the same can be achieved with base handler classes, so its a question of preference.

  • By design a single pipeline is supported as you can separate concerns by calling any child dependencies or methods and a single pipeline removes the possibility of ordering complications etc.

  • Avoid adding logic to pipelines but you have full access to the command / query / handler and unit of work

  • Pipelines need registering with Dependency Injection

6. Implement your Unit of Work interface

  • Take a dependency on ICQRSRequestHandler<IUnitOfWork> and call this to run commands or queries

      	public Task Run(ICommand command, CancellationToken cancellationToken) 
      		=> _requestHandler.HandleCommand(this, command, cancellationToken);`
    
      	public Task<T> Run<T>(ICommand<T> command, CancellationToken cancellationToken) 
      		=> _requestHandler.HandleCommand(this, command, cancellationToken);`
    
      	public Task<T> Run<T>(IQuery<T> query, CancellationToken cancellationToken) 
      		=> _requestHandler.HandleQuery(this, query, cancellationToken);
    
  • Its often good to add IDisposable to support using(var uow = UnitOfWork()){}

7. Decide how you are going to construct a unit of work

  • Its often nice to define a IUnitOfWorkProvider that construct your unit of work so that it very explicit where new units or work are started.

      	public interface IUnitOfWorkProvider
      	{
      		IUnitOfWork Start();
      	}
    
  • You can also just depend on a Func<IUnitOfWork>()

8. Register with dependency injection passing in the assembly that holds all your commands and queries

  • Uses Microsoft DI by default

      services.AddCleanCQRS(Assembly);
    
  • Add your UnitOfWork and any other dependencies

      services.AddTransient<IUnitOfWorkProvider, UnitOfWorkProvider>();
      // or
      services.AddTransient<Func<IUnitOfWork>>(provider => new UnitOfWork(provider));
    
  • Although its possible, registering IUnitOfWork with Dependency Injection is often best avoided as you want to control how Units Of Work are constructed and make it explicit

There are some examples and how to test them included here.

Examples are all a bit contrived and simplistic but hopefully convey the power of this set up for testing and being able to change decisions in the future...

  1. Minimal
  2. Simple
  3. Pipeline
  4. Composition
  5. Isolated
  6. Barebones
  7. Synchronous
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  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 is compatible.  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
1.0.8 14,706 6/2/2024
1.0.7-preview 94 6/2/2024
1.0.6 2,748 4/25/2024
1.0.5 1,391 1/3/2024
1.0.4 144 10/7/2023
0.0.3-beta 95 10/5/2023
0.0.2-beta 95 10/5/2023