New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[2015-01-20] Codeplex #578166 Using Interception with support for async methods? #520
Comments
[This issue is migrated from Codeplex, Thread #578166 | Message #1343612 | 2015-01-20] If performance is a significant requirement then may I ask why you would choose interception over decorators? |
[This issue is migrated from Codeplex, Thread #578166 | Message #1343634 | 2015-01-20] Besides from a performance point of view, the code you need to write to build an interceptor that allows handling asynchronous code is far more complex than what you need if you use decorators. Here's an example of how a decorator might look like with behavior that is equal to the public class LoggingAsynchronousCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private readonly ICommandHandler<TCommand> decoratee;
public LoggingAsynchronousCommandHandlerDecorator<TCommand>(
ICommandHandler<TCommand> decoratee) {
this.decoratee = decoratee;
}
public async Task Handle(TCommand command) {
try
{
await this.decoratee.Handle(command);
Trace.TraceInformation(
"Successfully finished async operation {0}",
typeof(TCommand).Name);
}
catch (Exception e)
{
Trace.TraceWarning(
"Async operation {0} threw: {1}",
typeof(TCommand).Name, e);
throw;
}
}
} This is all it takes to create a decorator and you get compile-time support and easy generics for free. Compare that to the interceptor counter part and you start to understand why using decorators would be a good idea. And did we tell you about the performance of decorators in Simple Injector? 'Problem' of course with decorators is that you might need to rethink your design, because the reason you are now probably looking at interception is because you have many services in the system that all have their own interface. With such design, you are forced to fallback to interception. However, if you would manage to design your application around a few cleverly chosen abstractions (such as explained here and here for instance), you will find that many problems that were hard in the past, suddenly become no-brainers. |
[This issue is migrated from Codeplex, Thread #578166 | Message #1343637 | 2015-01-20] However, if you want to stick with interception (because migration to the design I propose is a too big step for now), the code presented in that MSDN article can for the most part be copied into the interception example that is given in the documentation, or interception using Castle DynamicProxy. The abstractions defined in the Simple Injector documentation are based on the Castle DynamicProxy API, so the solution would be precisely the same. The solution is to get the invocation.ReturnValue after calling invocation.Proceed(), and replace this with the wrapper task using the CreateWrapperTask method. |
[This issue is migrated from Codeplex, Thread #578166 | Message #1343654 | 2015-01-20] Thank you for the replies.
|
[This issue is migrated from Codeplex, by @dotnetjunkie, Thread #578166 | Message #1343747 | 2015-01-21] Hi fractalfractal, I do absolutely agree with you one your first point. Even if you have 100% code coverage and a great set of tests, making drastic changes to the design of your application is not something you should take lightly, and is not something you can do in any stage in the project's lifetime. So for this reason alone, using interception might b e a very valuable option, even if it is an intermediate option. I disagree with your second point however. You don't need interception or code weaving to practice AOP and apply cross-cutting concerns effectively. I think both @qujck and I have living proof for this (From earlier conversations with @qujck I think I know what he is doing, but @qujck, correct me if I'm wrong). We both work on big systems and we apply SOLID principles and use the designs similar to the ones described in the articles I linked to before. We apply many cross-cutting concerns and we ONLY use decorators to achieve this. NEVER interception. Here's a list of cross-cutting concerns that I was able to effectively, elegantly and maintainably apply using decorators in the applications I'm working on:
These are quite some different cross-cutting concerns we apply here, and the important thing to note is that for every aspect in the list above, we mostly often write 1 (or 2 at most) decorators allow us to apply that particular concern throughout the application. We regularly add new cross-cutting concerns, apply them conditionally based on type constraints or based on the environment (we remove some decorators when running integration tests for instance). Being forced to apply aspects using interception would be an absolute nightmare for us, and would basically have prevented us from applying those concerns in the first place. Do note though that although there's still quite some investment needed if you wish to apply these patterns, as long as you program to interfaces, you should be able to apply the suggested patterns by mostly writing new code and changing your container's configuration. Take the following example for instance: public class OrderController : Controller {
private IOrderServices orderServices;
public UserListController(IOrderServices orderServices) {
this.orderServices = orderServices;
}
[HttpPost]
public Task<ActionResult> Cancel(Guid id) {
await this.orderServices.CancelOrder(id);
return Redirect("/orders/" + id);
}
}
public class OrderServices : IOrderServices {
public Task CancelOrder(Guid id) {
// implementation
}
// Lots of other methods
} Above is a structure that might resemble your current application. Without the need to make any changes to both the controller and the services implementation, we can do the following: public interface ICommandHandler<TCommand> {
Task Handle(TCommand command);
}
// Message for the "cancel order" use case.
public class CancelOrder { public Guid OrderId; }
// Implementation for the "cancel order" use case.
public class CancelOrderHandler : ICommandHandler<CancelOrder> {
private OrderServices orderServices;
// NOTE: We depend on the CONCRETE OrderServices class here.
public CancelOrderHandler(OrderServices orderServices) {
this.orderServices = orderServices;
}
public Task Handle(CancelOrder cancelOrder) {
// For now, just forward the call to the legacy implementation.
return this.orderServices.CancelOrder(cancelOrder.Id);
}
}
// This proxy forwards calls to the corresponding command handlers
public class OrderServicesProxy : IOrderServices {
private ICommandHandler<CancelOrder> cancelOrderHandler;
public OrderServicesProxy(
// other handlers here
ICommandHandler<CancelOrder> cancelOrderHandler) {
this.cancelOrderHandler = cancelOrderHandler;
}
public Task CancelOrder(Guid id) {
this.cancelOrderHandler.Handle(new CancelOrder { OrderId = id });
}
// Other IOrderService methods here.
} This code shows the move to a message based architecture, where each use case contains one single message. In our example the 'CancelOrder' class which is handled by the CancelOrderHandler. Since we don't want to change the controller and services class (for now), we let the CancelOrderHandler simply forward the call to the OrderServices and we create a proxy for the IOrderServices interface that forwards all the calls to the corresponding command handlers. This all can be registered in Simple Injector as follows: // Batch-registers all non-generic implementations of ICommandHandler<T>:
container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>),
AppDomain.CurrentDomain.GetAssemblies());
// Register the proxy class
container.Register<IOrderServices, OrderServicesProxy>();
// Removed: This registration can be removed
// container.Register<IOrderServices, OrderServices>(); After having created this very thin layer in between your presentation layer and your business layer, you are now able to apply cross-cutting concerns in a very flexible, elegant and maintainable way. For instance: container.RegisterDecorator(typeof(ICommandHandler<>), typeof(AuditTrailCommandDecorator<>));
container.RegisterDecorator(typeof(ICommandHandler<>), typeof(TransactionCommandDecorator<>));
container.RegisterDecorator(typeof(ICommandHandler<>), typeof(SecurityCommandDecorator<>));
container.RegisterDecorator(typeof(ICommandHandler<>), typeof(PermissionCommandDecorator<>)); Simple Injector will apply a generic decorator for you over all I know this is still quite some work, but probably still more feasible than trying to apply cross-cutting concerns using interceptors, ESPECIALLY when your system is inherently asynchronous. You already saw how painful it is to create an interceptor that applies a cross-cutting concern for an asynchronous operation. |
[This issue is migrated from Codeplex, by @fractalfractal, Thread #578166 | Message #1343762 | 2015-01-21] @steven, |
[This issue is migrated from Codeplex, by @fractalfractal, Thread #578166 | Message #1345155 | 2015-01-22] Back to the original question... plus a bit extra: How can I handle exceptions in Interception code snippet? after calling invocation.Proceed() And to further this along, how will I get this for async methods? If you have any further tips/tricks or sample code to help me get going, that would be greatly appreciated. We would really like to use your product as we've heard good reviews on its performance overall. Thanks. |
[This issue is migrated from Codeplex, by @dotnetjunkie, Thread #578166 | Message #1345186 | 2015-01-22] If you are going to implement a lot of interceptors, it might be better to use Castle DynamicProxy instead of the code snippets from the wiki. Castle DynamicProxy is a very mature framework for generating proxy classes and has an API that allows much more. And it contains a lot of documentation. The example from the website is a stripped down version of the DynamicProxy API. Here's an example of how to integrate DynamicProxy with Simple Injector. But either way, back to your questions:
By wrapping the call to invocation.Proceed() in a try-catch block. The same holds when you use Castle.
This is something you will have to Google; I can't help you with that. If you go with Castle, its documentation is full of examples.
We're very pleased to hear this :-) Cheers |
We are migrating away from Unity and would like to use Simple Injector as we've read it has great performance.
I am now looking at interception as described here.
But one thing I have a question on is: Does anyone have sample code to make this work for async methods? e.g. below is example usage -- where invocation.Proceed() is a synchronous call.
FYI: In Unity, we solved the problem as outlined here:
The basic approach adopted there was to wrap the call in a task.
http://msdn.microsoft.com/en-us/magazine/dn574805.aspx
Thanks!
The text was updated successfully, but these errors were encountered: