Skip to content
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

Closed
dotnetjunkie opened this issue Feb 27, 2018 · 8 comments
Labels

Comments

@dotnetjunkie
Copy link
Collaborator

dotnetjunkie commented Feb 27, 2018

This question was migrated from CodePlex discussion #578166
Post date: 2015-01-20 10:24:30

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.

public void Intercept(IInvocation invocation) {
    var watch = Stopwatch.StartNew();

    // Calls the decorated instance.
    invocation.Proceed();

    var decoratedType = invocation.InvocationTarget.GetType();

    this.logger.Log(string.Format("{0} executed in {1} ms.",
        decoratedType.Name, watch.ElapsedMiliseconds));
}

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!

@dotnetjunkie
Copy link
Collaborator Author

[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?

@dotnetjunkie
Copy link
Collaborator Author

dotnetjunkie commented Feb 27, 2018

[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 LoggingAsynchronousOperationInterceptionBehavior from the linked article:

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.

@dotnetjunkie
Copy link
Collaborator Author

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

@dotnetjunkie
Copy link
Collaborator Author

[This issue is migrated from Codeplex, Thread #578166 | Message #1343654 | 2015-01-20]

Thank you for the replies.
To answer your question about why we wish to use Interception for AOP cross-cutting concerns is two fold, namely

  1. We have a lot of code... getting to the point you might call it legacy code. Refactoring and re-testing is not viable at this stage. (I know, I know, we should never say this, because every project should have 100% code coverage with unit tests, integration tests, and automated UI tests too right? But that's not reality, and we are a pretty good shop with lots of tests as it is).

  2. Our application is starting to scale and we need a lot more AOP cross-cutting features... namely, logging, auditing, and performance/performance counters/profiling. Using interception seems like the perfect fit, as one developer can work on this without impacting any other developers or domains, and it can be fully tested in isolation.

@dotnetjunkie
Copy link
Collaborator Author

[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:

  • Restrict access based on permissions and roles.
  • Restrict access based on row level
  • Record audit trails
  • Validate user input
  • Execute logic in a transaction
  • Implement a deadlock-retry mechanism
  • Performance monitoring
  • Ensure isolation of executed operations (run in their own isolated 'request' within a web request)
  • Apply concurrency checks for user CRUD operations.
  • Ensure synchronization of executed operations to prevent concurrency bugs.
  • Caching of data to improve performance of queries in the system.

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 ICommandHandler<TCommand> implementations in the system. You can apply generic type constraints to the decorators and Simple Injector will apply your decorators conditionally automatically. This gives you complete compile-time support when writing decorators. Something that is highly valuable, especially when applying cross-cutting concerns to asynchronous operations.

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.

@dotnetjunkie
Copy link
Collaborator Author

[This issue is migrated from Codeplex, by @fractalfractal, Thread #578166 | Message #1343762 | 2015-01-21]

@steven,
Your reply is (IMO) an excellent example which shows how to introduce the command-handler pattern in an existing app. Probably a fair amount of work but the ability to introduce those cross-cutting concerns in a generic way is worth it.

@dotnetjunkie
Copy link
Collaborator Author

[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()
if that target method throws an exception, am I suppose to wrap this in a try/catch block?
does invocation.ReturnValue give me the Exception?

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.

@dotnetjunkie
Copy link
Collaborator Author

[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:

How can I handle exceptions in Interception code snippet?

By wrapping the call to invocation.Proceed() in a try-catch block. The same holds when you use Castle.

And to further this along, how will I get this for async methods?

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 would really like to use your product as we've heard good reviews on its performance overall.

We're very pleased to hear this :-)

Cheers

Repository owner locked and limited conversation to collaborators Feb 27, 2018
@dotnetjunkie dotnetjunkie changed the title Using Interception with support for async methods? [2015-01-20] Codeplex #578166 Using Interception with support for async methods? Jul 19, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

1 participant