Decorating a generic interface with StructureMap

While at The Day Job, I noticed we had some services that all shared the same cross-cutting concerns. In particular, validating a request object. You may think of some other ones, like logging, auditing, standard error-handling, etc. Due to other architectural concerns of our project, it made sense to decorate a common interface we had as a way to achieve the validation. First, here’s our generic interface that we want to decorate:

public interface IServiceOperation<in TRequest, out TResponse> where TResponse : ServiceResult, new()
{
    TResponse PerformService(TRequest validatedRequest);
}

Simply put, it just says given a request object, I’ll return a standard response object. To show it “in action”, here’s a sample implementation. Note that the interface has already assumed at this point that the request object has been validated.

public class SignUpService : IServiceOperation<SignUpRequest, SignUpResult>
{
    private readonly IUserRepository _userRepo;

    public SignUpService(IUserRepository userRepo)
    {
        _userRepo = userRepo;
    }

    public SignUpResult PerformService(SignUpRequest validatedRequest)
    {
        var user = Mapper.Map<User>(validatedRequest);

        user.MarkAsLoggedIn();
        user.ChangePassword(validatedRequest.UnhashedPassword);

        using(var transaction = _userRepo.BeginTransaction())
        {
            _userRepo.Save(user);
            transaction.Commit();
        }

        return new SignUpResult();
    }
}

Pretty standard stuff, nothing special. Just a domain service that takes in a request, performs some domain operations, then saves changes to a repository.

Now for our decorator. If you’re unfamiliar with the decorator pattern, here’s the simple version – a decorator is a class that implements an interface, and also consumes an instance of that interface in it’s constructor. This allows you to “intercept” any calls against the passed-in instance to add extra behavior, while wrapping the consumed instance. Your consuming application doesn’t have to change it’s code at all – your IoC container will do the magic for you. You just code against the interface like normal, and everything is happy. You can even have multiple decorators (each one consuming another!).

public class ValidateServiceDecorator<TRequest, TResponse> : IServiceOperation<TRequest, TResponse> where TResponse : ServiceResult, new()
{
    private readonly IServiceOperation<TRequest, TResponse> _serviceOperation;
    private readonly IValidationService _validationService;

    public ValidateServiceDecorator(IServiceOperation<TRequest, TResponse> serviceOperation,
        IValidationService validationService)
    {
        _serviceOperation = serviceOperation;
        _validationService = validationService;
    }

    public TResponse PerformService(TRequest request)
    {
        var response = new TResponse();
        var validationResult = _validationService.Validate(request);

        if (!validationResult.IsValid)
        {
            response.ValidationErrors = validationResult.ValidationErrors;
            return response;
        }

        return _serviceOperation.PerformService(request);
    }
}

Easy peasy! It takes in IService<A,B>, and implements it as well. So, again – the magic needs to be handled by our IoC Container, in our case, StructureMap. StructureMap already has some great support for auto-registering open generic types with the “ConnectImplementationsToTypesClosing” function, and decorator support with the “EnrichWith” function. However, combining the two can be prolematic, as EnrichWith except your type, if generic, to be a closed generic type (meaning you know the type parameters explicitly). Obviously, we don’t know that – well, we could, but that’d mean every time we added a new request/response pairing for our IServiceOperation, we’d have to modify our container. This would lead to a lot of copy/pasted code in our container, be something everyone would forget to do, and another spot to be affected when renaming objects. Not exactly ideal.

It took quite a bit of digging, even posted the question to StackOverflow, but did not get a response. Eventually, I was able to find a solution – a custom IRegistrationConvention! If you’re not familiar with this (as I wasn’t), this is a hook StructureMap provides. By implementing this interface from Structuremap, you’ll get a hook in the form of a function that is called for every type StructureMap finds when starting up (depending on what assemblies you tell it to scan). From here, I was able to inspect each type, and see what interfaces it implemented. If the implemented interface was IServiceOperation, then through reflection I could get what two types (the response and request) were used to close the implementation, then tell StructureMap that for that closed type, to return my decorator using those same two types (with Activator.CreateInstance).

Here’s my final StructureMap code, showing it all hooked together. I hope this helps other people!

public class StructureMapServiceScanner : Registry
{
    public StructureMapServiceScanner()
    {
        Scan(scanner =>
                {
                    //Tells StructureMap to scan all types in the assmebly where the interface is defined
                    scanner.AssemblyContainingType(typeof (IServiceOperation<,>));
                    //Tells StructureMap that for all requests of IServiceOperation to automatically return
                    //the concrete type that closes the interface with the same as the requested parameters
                    scanner.ConnectImplementationsToTypesClosing(typeof (IServiceOperation<,>));
                    //And finally, we add our new registration convention
                    scanner.Convention<ServiceRegistrationConvention>());
                });
    }
}
public class ServiceRegistrationConvention : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
        var interfacesImplemented = type.GetInterfaces();

        foreach (var interfaceImplemented in interfacesImplemented)
        {
            if (interfaceImplemented.IsGenericType && interfaceImplemented.GetGenericTypeDefinition() == typeof(IServiceOperation<,>))
            {
                var genericParameters = interfaceImplemented.GetGenericArguments();
                var closedValidatorType = typeof(ValidateServiceDecorator<,>).MakeGenericType(genericParameters);

                registry.For(interfaceImplemented)
                    .EnrichWith((context, original) => Activator.CreateInstance(closedValidatorType, original,
                                                                                context.GetInstance<IValidationService>()));
            }
        }
    }
}

And of course, if you know of another/better way to do this, please respond!

1 comments:

Unknown said...

With 3.0+, look at this sample:

https://github.com/structuremap/structuremap/blob/master/src/StructureMap.Testing/Acceptance/interception_acceptance_tests.cs#L221-L238

Designed by Posicionamiento Web | Bloggerized by GosuBlogger