Reputation: 93
I am working on some legacy applications (.Net core and framework apps) which used CQRS design pattern.
Basically, we have one repository for data access layer using Dapper. Then we applied CQRS by creating two different interfaces , one for command and one for query. Those interfaces look like:
// Query interface
public interface IOrderQuery
{
Order GetOrderById(int id);
}
// Command interface
public interface IOrderCommand
{
bool PlaceOrder(Order placedOrder)
}
In my opinion, it might not be the correct implementation of CQRS because we are using same model for input and output, as well as there is no specific handler for each operation. However my Principal Dev said that it is correct way as we just need to separate the command and query then we archived CQRS pattern. I get confused now.
Also, we are applying the same approach for web api where the repository uses httpClient.
I, therefore, suggest using MediatR for applying CQRS with DDD but it looks like they dont want to use it because it is over complicated.
I am just a junior dev so can anyone explain for me that the current approach we are using is correct implementation of CQRS? Should I apply CQRS with HttpClient? What are the benefits of using CQRS?
thanks
Upvotes: 1
Views: 1786
Reputation: 973
You have a valid point this isn't a very typical approach to implementing CQRS. To me, it looks more like a single method repository. While CQRS's main objective is the separation of queries and commands this approach you're following might complicate things up in the long run.
A typical implementation of CQRS whether it's a query or command is that you have the query (or command) object that contains all the information needed for this query to execute + a handler to handle this query. Check the example below.
public class OrdersQuery
{
public DateTime? FromDate { get; }
public DateTime? ToDate { get; }
public OrdersQuery(DateTime? fromDate, DateTime? toDate)
{
FromDate = fromDate;
ToDate = toDate;
}
}
public class OrdersQueryHandler
{
public List<Order> Handle(OrdersQuery query)
{
//Implementation Goes Here
}
}
In this example, you've OrdersQuery that contains optional FromDate and ToDate filters (This is for demonstration only) and it should contain any input data that the query handler will need to execute the query. Or no data at all in case you want to return all data in one table like lookup tables (e.g., GetOrdersTypes, Countries)
And to use this query you do the following.
public static class Program
{
public static void Main(string[] args)
{
var handler = new OrdersQueryHandler();
var orders = handler.Handle(new OrdersQuery(DateTime.Now.AddDays(-30), DateTime.Now));
}
}
But you might be seeing the problem already, In a real-world app, you'll need to use dependency injection, factories, or some sort of way that makes it easy to work with the query handlers.
You can't just new SomeHandler() every time you need to use one, This will create hard dependencies on the handlers and will make the code harder to maintain. And if you go with the approach you're suggesting in the question you'll end up with 1 interface for each query/command handler. Think of how many interfaces you'll need to inject at some point to execute some complicated business rules.
CQRS and the Mediator pattern plays well together to solve these kind problems and in my opinion, MediatR is the best library out there that serves this purpose and it's not complicated at all. Check the example below.
public class OrdersQuery : IRequest<List<Order>>
{
public DateTime? FromDate { get; }
public DateTime? ToDate { get; }
public OrdersQuery(DateTime? fromDate, DateTime? toDate)
{
FromDate = fromDate;
ToDate = toDate;
}
}
public class OrdersQueryHandler : IRequestHandler<OrdersQuery, List<Order>>
{
public Task<List<Order>> Handle(OrdersQuery request, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
public static class Program
{
public static async Task Main(string[] args)
{
var services = new ServiceCollection()
.AddMediatR(typeof(Program).Assembly)
.BuildServiceProvider();
var mediator = services.GetRequiredService<IMediator>();
var orders = await mediator.Send(new OrdersQuery(DateTime.Now.AddDays(-30), DateTime.Now));
}
}
Here your query will implement IRequest where T is the result and your query handler will implement IRequestHandler<T, TResult> where T is the query type and TResult is the result (e.g., List) (NOTE: TResult has to match the type argument in IRequest). In this example, I'm using MS Dependency Injection but you can use the DI library of your choice.
MediatR here scans the assembly for all the handlers (You can register them manually if you want). Now to use it you only need to know about IMediator interface no more IOrdersQuery, IOrdersCommand, ...etc. You'll only reference IMediator and the mediator will take care of resolving the right handler for the right query.
Now to the question of whether you should use CQRS for resources via HTTP or not. If by CQRS you mean the pattern discussed here with the mediator. Yes, definitely you can do that.
But CQRS most often refers to the database Query/Command. And sometimes also people implement CQRS to do the reads & writes to different databases. So to avoid confusion just call it the mediator pattern and definitely it'll work for Http.
Upvotes: 2