I'm using a query object pattern (similar to this) to manage disparate queries while avoiding bloaded facades/repositories.
A query object takes a number of constructor parameters, representing query arguments. The query is then passed to an IQueryHandler
by the caller, into which is injected the IDataContext
. The IDataContext
is then passed into an Execute
method of the IQuery
.
The thing I don't like is this:
public interface IQuery<TResult>
{
public TResult Execute(IDataContext context);
}
Because the IDataContext
is passed into the method and thus explicitly declared in the interface, there is no option to have a query that fetches things via a different mechanism - IQuery
is coupled to whatever defines the IDataContext
interface. Suppose, for example, I want to switch from an SQL DB store to a document DB.
I've tried a couple of alternatives, but neither quite gets me where I want to be.
The first is to inject the data context into the query's constructor and use a factory to generate the query objects. This successfully decouples the interfaces, but now working with the queries is more cumbersome. Instead of using the constructor/object initializer to set up the query, callers have to do something like this:
var query = _queryFactory.Create<PersonQuery, Person>(); // Second type argument can't be inferred from first due to limitations in generic type inference.
query.Name = "Bob";
query.Age = 32;
var result = query.Execute();
The second is to abstract the execution of the query - and thus the dependency on IDataContext
- to a handler class, then have another class that resolves a handler for each query, which again abstracts the dependency away from any interfaces:
public class ISomeQueryHandler : IQueryHandler<SomeQuery, SomeQueryResult>
{
private readonly IDataContext _context = ...;
public SomeQueryResult Execute(SomeQuery query) { ... }
}
I don't like this option because it means every new query involves writing two classes, which, again, is cumbersome and increases the potential for a query to exist with no way of handling it (it also involves an empty marker interface for queries since the Execute
method is moved to the handler, which always feels off). It also involves some rather funky convention-based dependency resolution to get the right handler for each query.
Creating an abstraction over IDataContext
is not really feasible due to its complexity.
Is there a happy medium I haven't seen?
I...Query
interfaces for different types of data context, then use these compositionally on queries that can be executed against different types of context. Feels a bit weird though.IQuery
interfaces for different types of data context. Or unless someone has a better idea :)