| Branch | Status |
|---|---|
| Master | |
| Dev |
Simple CQRS library
This project composes of components for implementing the CQRS pattern (Query Handling). This library was built with simplicity, modularity and pluggability in mind.
- Send queries to registered query handler.
- Multiple ways of registering handlers:
- Simple handler registration (no IoC container).
- IoC container registration - achieved by creating implementations of IContainerAdapter.
- Attribute registration - achieved by marking methods with [QueryHandler] attributes.
You can simply clone this repository, build the source, reference the dll from the project, and code away!
Xer.Cqrs libraries are also available as Nuget packages:
To install Nuget packages:
- Open command prompt
- Go to project directory
- Add the packages to the project:
dotnetadd package Xer.Cqrs.QueryStack
- Restore the packages:
dotnetrestore
(Samples are in ASP.NET Core)
// Example query.publicclassQueryProductById:IQuery<Product>{publicintProductId{get;}publicQueryProductById(intproductId){ProductId=productId;}}// Async query handler.publicclassQueryProductByIdHandler:IQueryAsyncHandler<QueryProductById,Product>{privatereadonlyIProductReadSideRepository_productRepository;publicQueryProductByIdHandler(IProductReadSideRepositoryproductRepository){_productRepository=productRepository;}publicTask<Product>HandleAsync(QueryProductByIdquery,CancellationTokencancellationToken=default(CancellationToken)){return_productRepository.GetProductByIdAsync(query.ProductId);}}// Sync query handler.publicclassSyncQueryProductByIdHandler:IQueryHandler<QueryProductById,Product>{privatereadonlyIProductReadSideRepository_productRepository;publicQueryProductByIdHandler(IProductReadSideRepositoryproductRepository){_productRepository=productRepository;}publicProductHandle(QueryProductByIdquery){return_productRepository.GetProductById(query.ProductId);}}// Attributed query handler.publicclassQueryProductByIdHandler{privatereadonlyIProductReadSideRepository_productRepository;publicQueryProductByIdHandler(IProductReadSideRepositoryproductRepository){_productRepository=productRepository;}[QueryHandler]publicProductHandle(QueryProductByIdquery){return_productRepository.GetProductById(query.ProductId);}}Before we can dispatch any queries, first, we need to register our query handlers. There are several ways to do this:
// This method gets called by the runtime. Use this method to add services to the container.publicvoidConfigureServices(IServiceCollectionservices){ ...// Read-side repository.services.AddSingleton<IProductReadSideRepository,InMemoryProductReadSideRepository>();// Register query dispatcher.services.AddSingleton<IQueryAsyncDispatcher>((serviceProvider)=>{// This object implements IQueryHandlerResolver.varregistration=newQueryHandlerRegistration();registration.Register(()=>newQueryProductByIdHandler(serviceProvider.GetRequiredService<IProductReadSideRepository>()));returnnewQueryDispatcher(registration);});
...}// This method gets called by the runtime. Use this method to add services to the container.publicvoidConfigureServices(IServiceCollectionservices){ ...// Read-side repository.services.AddSingleton<IProductReadSideRepository,InMemoryProductReadSideRepository>();// Register query handlers to the container.// Tip: You can use assembly scanners to scan for handlers.services.AddTransient<IQueryHandler<QueryProductById,Product>,SyncQueryProductByIdHandler>();// Register query dispatcher.services.AddSingleton<IQueryAsyncDispatcher>((serviceProvider)=>// The ContainerQueryHandlerResolver only resolves sync handlers. // For async handlers, ContainerQueryAsyncHandlerResolver should be used.newQueryDispatcher(newContainerQueryHandlerResolver(newAspNetCoreServiceProviderAdapter(serviceProvider))));
...}// Container adapter.classAspNetCoreServiceProviderAdapter:Xer.Cqrs.QueryStack.Resolvers.IContainerAdapter{privatereadonlyIServiceProvider_serviceProvider;publicAspNetCoreServiceProviderAdapter(IServiceProviderserviceProvider){_serviceProvider=serviceProvider;}publicTResolve<T>()whereT:class{return_serviceProvider.GetService<T>();}}// This method gets called by the runtime. Use this method to add services to the container.publicvoidConfigureServices(IServiceCollectionservices){ ...// Read-side repository.services.AddSingleton<IProductReadSideRepository,InMemoryProductReadSideRepository>();// Register query handler resolver. This is resolved by QueryDispatcher.services.AddSingleton<IQueryAsyncDispatcher>((serviceProvider)=>{// This implements IQueryHandlerResolver.varattributeRegistration=newQueryHandlerAttributeRegistration();// Register all methods with [QueryHandler] attribute.attributeRegistration.Register(()=>newQueryProductByIdHandler(serviceProvider.GetRequiredService<IProductReadSideRepository>()));returnnewQueryDispatcher(attributeRegistration);});
...}After setting up the query dispatcher in the IoC container, queries can now be dispatched by simply doing:
...
private readonly IQueryAsyncDispatcher _queryDispatcher;
public ProductsController(IQueryAsyncDispatcherqueryDispatcher){_queryDispatcher=queryDispatcher;}[HttpGet("{productId}")]publicasyncTask<IActionResult>GetProduct(intproductId){Productproduct=await_queryDispatcher.DispatchAsync<QueryProductById,Product>(newQueryProductById(productId));if(product!=null){returnOk(product);}returnNotFound();}
...