Miguel Moura
Miguel Moura

Reputation: 39454

Create a simple Query object

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:

  1. 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?

  2. Am I using async correctly?

  3. 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

  1. It is the dispatcher I am injecting on my controllers. And then I use it:

    _dispacther.Send(new FindPostByIdQuery(22));

  2. 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?

  3. I am not sure how to make my code to work with async if I need it.

  4. Does anyone knows how to test this pattern? It has not been easy for me.

Upvotes: 1

Views: 143

Answers (1)

Mrchief
Mrchief

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

Related Questions