I recently started looking into CQRS when using Entity Framework and the impact it has had on my systems has been overwhelming.
I implemented the following patterns described in these blog posts:
I have implemented these with Autofac as dependency injection, and Entity Framework with code first.
Now for the question. I have decided to let my query handlers use each other, and build upon other query handlers results. Is that considered okay? What issues may I run into in the future (if any)? Is the code SOLID?
Here's an example of my GetEmployeesQueryHandler
:
class GetEmployeesQueryHandler : IQueryHandler<GetEmployeesQuery, IQueryable<Employee>>
{
private readonly DatabaseContext database;
public GetEmployeesQueryHandler(DatabaseContext database)
{
this.database = database;
}
public IQueryable<Employee> Handle(GetEmployeesQuery query)
{
return database.Employees;
}
}
My Entity Framework DatabaseContext
gets injected into the constructor, and I simply return all employees.
Then I have a more specific query, which bases itself on this query. It does that by first fetching all employees, and then filtering them based on the Id
of the employee.
class GetEmployeeByIdQueryHandler : IQueryHandler<GetEmployeeByIdQuery, Employee>
{
private readonly IQueryProcessor processor;
public GetEmployeeByIdQueryHandler(IQueryProcessor container)
{
this.processor = container;
}
public Employee Handle(GetEmployeeByIdQuery query)
{
var employees = processor.Process(new GetEmployeesQuery());
return employees.SingleOrDefault(e => e.Id == query.EmployeeId);
}
}
And that's it. What do you think? Is it okay for my GetEmployeesByIdQueryHandler
to use the GetEmployeesQueryHandler
to get all employees first, for better code reuse?
Note that I return an IQueryable<Employee>
when I fetch all employees, so the SQL query will still be lazily generated, and evaluated in an optimized way.
For those who want a TL;DR and don't want to read the blog posts, below are the interfaces and the rest of the classes backing up my code.
Implementations
GetEmployeesQuery
public class GetEmployeesQuery : IQuery<IQueryable<Employee>>
{
}
GetEmployeeByIdQuery
public class GetEmployeeByIdQuery : IQuery<Employee>
{
public Guid EmployeeId { get; set; }
}
QueryProcessor
/// <summary>
/// Represents a class which automatically instantiates a QueryHandler that corresponds to a given Query, and processes the result of running the Query on that QueryHandler.
/// </summary>
sealed class QueryProcessor : IQueryProcessor
{
private readonly IComponentContext context;
public QueryProcessor(IComponentContext context)
{
this.context = context;
}
/// <summary>
/// Automatically figures out which QueryHandler belongs to the given Query, instantiates it, and returns the result of running the Query on that QueryHandler.
/// </summary>
/// <typeparam name="TResult">The type of result to return. This can be infered from Query given.</typeparam>
/// <param name="query">The query to return to use as basis for finding a suitable QueryHandler and returning the result.</param>
/// <returns></returns>
public TResult Process<TResult>(IQuery<TResult> query)
{
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = context.Resolve(handlerType);
return handler.Handle((dynamic)query);
}
}
Interfaces
IQueryHandler
/// <summary>
/// Defines a test-friendly query handler that can be automatically instantiated and populated with dependency injection.
/// </summary>
/// <typeparam name="TQuery">The type of query that this query handler should handle. For example, GetEmployeeByIdQuery.</typeparam>
/// <typeparam name="TResult">The type of result that the query returns. Must be the same as defined in the query itself. For example, Employee.</typeparam>
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
IQueryProcessor
public interface IQueryProcessor
{
TResult Process<TResult>(IQuery<TResult> query);
}
IQuery
public interface IQuery<TResult>
{
}
GetEmployeesQueryHandler
class is implementingIQueryable<Employee>
? Where are the members? \$\endgroup\$GetEmployeesQueryHandler
implementsIQueryHandler<GetEmployeesQuery, IQueryable<Employee>>
. I added some more code detailing these interfaces. \$\endgroup\$DatabaseContext
class directly derived fromDbContext
? \$\endgroup\$queryProcessor.Process(employeeQuery)
instead ofqueryProcessor<IQueryable<Employee>>(employeeQuery)
. \$\endgroup\$