Reputation: 39454
I created a query as follows:
public class FindPostEditModelByIdQuery : IQuery<PostEditModel> {
private readonly ILogger _logger;
private readonly ISession _session;
public FindPostEditModelByIdQuery(ISession session, ILogger logger) {
_logger = logger;
_session = session;
}
public async Task<PostEditModel> Run(int postId, out exception) {
// Code here
}
}
So basically on the constructor I am requesting through IOC what I will use ...
This approach allows, I hope, to use async in an easy way an keep things simple.
A few questions:
In my controllers I will use many commands ... I would like to avoid having a lot of them on the constructor. Do you think injecting a factory that requests a command would be ok? How should I do this?
Am I using async correctly?
What do you think about passing the exception as a parameter? Would be possible to create something that would log an exception the momment it occurs on the Run methods. Does this even make sense?
UPDATE
What I post is a try to make it easier to use async when compared to what I am using.
I am using Queries / Replies and Commands (I called them orders at the time) and I have:
public interface IOrderHandler {
void Handle(Order order);
}
public interface IOrderHandler<TOrder> : IOrderHandler where TOrder : Order {
void Handle(TOrder order);
}
public interface IQueryHandler {
Reply Handle(Query query);
}
public interface IQueryHandler<TQuery, TReply> : IQueryHandler
where TQuery : Query
where TReply : Reply {
Reply Handle(TQuery query);
}
public abstract class OrderHandler : IOrderHandler {
public abstract void Handle(Order order); // Handle
}
public abstract class OrderHandler<TOrder> : OrderHandler, IOrderHandler<TOrder> where TOrder : Order {
public override void Handle(Order order) {
TOrder torder = (TOrder)order;
Handle(torder);
}
public abstract void Handle(TOrder order); // Handle
}
public abstract class QueryHandler<TQuery, TReply> : QueryHandler, IQueryHandler<TQuery, TReply>
where TQuery : Query
where TReply : Reply {
public override Reply Handle(Query query) {
TQuery tquery = (TQuery)query;
Reply treply = Handle(tquery);
return treply;
}
public abstract Reply Handle(TQuery query);
}
public abstract class QueryHandler : IQueryHandler {
public abstract Reply Handle(Query query);
}
public abstract class QueryHandler<TQuery, TReply> : QueryHandler, IQueryHandler<TQuery, TReply>
where TQuery : Query
where TReply : Reply {
public override Reply Handle(Query query) {
TQuery tquery = (TQuery)query;
Reply treply = Handle(tquery);
return treply;
}
public abstract Reply Handle(TQuery query);
}
public abstract class Order { } // Order
public abstract class Query { }
public abstract class Reply {
public Exception Exception { get; set; }
}
public interface IDispatcher {
void Send(Order order);
void Send(Order order, out Exception exception);
TReply Send<TReply>(Query query) where TReply : Reply, new();
}
public class Dispatcher : IDispatcher {
public void Send(Order order) {
Type type = typeof(IOrderHandler<>).MakeGenericType(order.GetType());
IOrderHandler handler = (IOrderHandler)ObjectFactory.GetInstance(type);
try {
handler.Handle(order);
} catch (Exception exception) {
ILogger logger = ObjectFactory.GetInstance<ILogger>();
logger.Send(exception);
if (Debugger.IsAttached) throw;
}
}
public void Send(Order order, out Exception exception) {
Type type = typeof(IOrderHandler<>).MakeGenericType(order.GetType());
IOrderHandler handler = (IOrderHandler)ObjectFactory.GetInstance(type);
exception = null;
try {
handler.Handle(order);
} catch (Exception ex) {
ILogger logger = ObjectFactory.GetInstance<ILogger>();
logger.Send(ex);
exception = ex;
if (Debugger.IsAttached) throw;
}
}
public TReply Send<TReply>(Query query) where TReply : Reply, new() {
Type type = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TReply));
IQueryHandler handler = (IQueryHandler)ObjectFactory.GetInstance(type);
try {
return (TReply)handler.Handle(query);
} catch (Exception exception) {
ILogger logger = ObjectFactory.GetInstance<ILogger>();
logger.Send(exception);
if (Debugger.IsAttached) throw;
return new TReply { Exception = exception };
}
}
} // Dispatcher
A FEW NOTES AND QUESTIONS
It is the dispatcher I am injecting on my controllers. And then I use it:
_dispacther.Send(new FindPostByIdQuery(22));
I am not sure if logging the exceptions in the dispatcher is a good approach. Basically I am logging all exceptions that occur on the service layer.
One thing I was considering was having an ExceptionHandler which could be attached.
But I am not sure if this could be possible. What do you think?
I am not sure how to make my code to work with async if I need it.
Does anyone knows how to test this pattern? It has not been easy for me.
Upvotes: 1
Views: 143
Reputation: 76218
#1: Your controller should not be using that many injected commands. Constructor over injection is usually a smell that the object needs to be broken down further. Concerns like logging, exception handling are often cross cutting and they are better handled at an outer layer.
#2: There is barely any code to be able to say anything conclusively. Signature-wise this is ok, although like I said above, you can probably do away with out exception
. The caller needs to handle the fault in a async chain using ContinueWith
or similar.
task.ContinueWith(x => x.Exception.Handle(ex =>
{
logger.Error(ex.Message, ex);
return false;
}), TaskContinuationOptions.OnlyOnFaulted);
#3: Exception logging should not be the responsibility of the query object, rather something that sits above it - using action filters, or at the caller level or a service or better - using aspect oriented programming.
Update:
Take a look at ShortBus. Unless you want to DIY, this has all the things you're looking for, including async handlers, ability to bubble up exceptions and unit tests.
Upvotes: 2