Exposing IQueryable in a CQRS Query Stack
Sending IQueryable<T>
to higher layers can be a touchy subject…
- “there is nothing more frustrating (or more expensive to maintain) than a data layer leaking up the stack”
- ”…literally no case where EF + Repository that exposes IQueryable makes sense”
- “EF-generated anything should never leave the data layer”
- *“it’s bad for unit testing”*
With CQRS, it is acceptable - and even beneficial to expose IQueryable<T>
within the Query Stack.
Query Stack
“When your goal is simply creating a domain model for read-only operations, everything comes easier and classes are simpler overall” - Esposito, Saltarello
In a Query-only (Read-only) Model, we don’t have to worry about designing the Domain Model to be fully CRUD-able.
Instead of centralized Aggregates hidden behind explicit interfaces, it becomes ok for the Domain Model to be anemic and for Aggregates to be decentralized (Esposito, Saltarello).
Before… The Repository
If we were to follow everyone else’s rules about IQueryable
, we would write a method in a Repository<T>
class that filters and returns the in-memory data we need for a Use Case.
Here’s an example of this canonical IRepository
implementation:
public interface IRepository<T> where T : class {
IEnumerable<T> GetAll(Expression<Func<T, bool>> predicate = null);
T Get(Expression<Func<T, bool>> predicate);
void Add(T entity);
void Update(T entity);
void Delete(T entity);
long Count();
}
public class PartRepository : IRepository<Part> {
private readonly IPartsCatalogDbContext _dbContext;
public PartRepository(IPartsCatalogDbContext dbContext) {
_dbContext = dbContext;
}
public IEnumerable<Part> GetAll(Expression<Func<Part, bool>> predicate = null) {
return _dbContext.Parts.Where(predicate).ToEnumerable();
}
public IEnumerable<Part> GetAllParts() {
return GetAll(x => true);
}
// ... Get(), Add(Part part), Update(Part part), Delete(Part part) ...
}
After… The Read Model Facade
We can start refactoring to a Query Stack by restricting DbContext
to be read-only with a Read Model Facade:
public class PartsCatalogQueryDbContext : IDisposable {
private readonly IPartsCatalogDbContext _db;
public PartsCatalogQueryDbContext() {
_db = new PartsCatalogDbContext();
}
public IQueryable<Part> Parts {
get { return _db.Parts; }
}
public IQueryable<Component> Components {
get { return _db.Components; }
}
public IQueryable<LaborSequence> LaborSequences {
get { return _db.LaborSequences; }
}
public void Dispose() {
_db.Dispose();
}
}
Here, we are taking away all the CRUD power of DbContext
and only exposing DbSet<T>
as IQueryable<T>
.
Now that we have a Read Model Facade exposing IQueryable<T>
, we can build queries for our specific Use Cases. We no longer need specialized Repository methods beyond our GetAll(Expression<Func<Part, bool>> predicate)
method:
We can build these queries in IRequestHandler<Query, Result>
with the Mediator Pattern using MediatR, like we did in my last post:
public class Index {
public class Query : IRequest<CatalogViewModel> {
}
public class QueryHandler : IRequestHandler<Query, CatalogViewModel> {
private readonly PartsCatalogQueryDbContext _db;
public QueryHandler(PartsCatalogQueryDbContext db) {
_db = db;
}
public CatalogViewModel Handle(Query message) {
var catalogViewModel = new CatalogViewModel();
catalogViewModel.Items.AddRange(
_db.Parts.Select(p => new CatalogItemViewModel() {
PartNumber = p.PartNumber,
ExtendedDescription = p.ExtendedDescription
})
);
return catalogViewModel;
}
}
}
We create multiple IRequestHandler<Query, Result>
for each View, referencing the IMediator
instance in the Controller.
public class HomeController : Controller {
private readonly IMediator _mediator;
public HomeController(IMediator mediator) {
_mediator = mediator;
}
public ActionResult Index() {
var catalogViewModel = _mediator.Send(new Index.Query());
return View(catalogViewModel);
}
// ...
}
Request Handlers can use Layered Expression Trees to build more precise queries for other Views as needed.
Summary
We discovered a scenario where a CQRS Query Stack makes it beneficial to use IQueryable<T>
for the Presentation Layer’s “currency” (Esposito, Saltarello)
Instead of writing specialized Repository
methods for each View, we can use IQueryable
to build queries for data that we need at the Presentation Layer. This lets us make change within the layers that actually use the data.
It also makes for clean code that resembles the Ubiquitous Language more closely.
As always, stay tuned for much more on this subject! I also like hearing from you, so sign up for my newsletter, shoot me an email, and/or leave a comment below!
Sample code here
Related Posts:
- A Pattern to Decouple your Aggregates from their Clients
- A Better Way to Project Domain Entities into DTOs
Sources:
- Esposito, Dino; Saltarello, Andrea. Microsoft .NET: Architecting Applications for the Entperprise, Second Edition. Microsoft Press, September 2014
- Young, Greg. CQRS, Task Based UIs, Event Sourcing agh!
- Esposito, Dino; Saltarello, Andrea. Introducing CQRS. Microsoft Press Store, 9/10/2014
- Esposito, Dino. Cutting Edge - CQRS for the Common Application. MSDN Magazine, June, 2015.
- Bogard, Jimmy. MediatR
- Bogard, Jimmy. ContosoUniversity