3

I have the following in QUERY side of Project1 which primarily contains interfaces

public interface IQueryResult {}
public interface IQuery<TResult> where TResult : IQueryResult {}
public interface IQueryHandler<TQuery, TResult> 
    where TQuery : IQuery<TResult> 
    where TResult : IQueryResult
{
    Task<TResult> HandleAsync(TQuery query);
}

public class PersonQueryResult : IQueryResult
{
    public string Name { get; set; }
}
public class GetPersonDetailsQuery : IQuery<PersonQueryResult>
{
    public int Id { get; set; }
}
public interface IQueryDispatcher
{
    Task<TResult> DispatchAsync<TQuery, TResult>(TQuery query)
        where TQuery : IQuery<TResult>
        where TResult : IQueryResult;
}

In the Second Project2 which references Project 1, I have

public class GetPersonDetailsQueryHandler : 
IQueryHandler<GetPersonDetailsQuery, PersonQueryResult>
{
    public Task<PersonQueryResult> HandleAsync(GetPersonDetailsQuery query)
    {
        return Task.FromResult( new PersonQueryResult {Name = "Bamboo"});
    }
}

The last Project 3 is a Web API project which only references Project 1 but NOT project 2. So it knows the interfaces and the commands and queries only. I need to configure autofac in a way that i can easily do something like this

var query = new GetPersonDetailsQuery { Id = 1 };
var magicHappensHere = new QueryDispatcher(); //any better way?
PersonQueryResult result = magicHappensHere.Dispatch(query); 

Also the IQueryDispatcher I have in Project 1 does not seem fit for the job above. A sample implementation for that interface which is open for suggestions is

public class QueryDispatcher : IQueryDispatcher
{
    private readonly IComponentContext _context;

    public QueryDispatcher(IComponentContext context)
    {
        this._context = context;
    }

    public Task<TResult> DispatchAsync<TQuery, TResult>(TQuery query) where TQuery : IQuery<TResult> where TResult : IQueryResult
    {
        var handler = _context.Resolve<IQueryHandler<TQuery, TResult>>();

        return handler.HandleAsync(query);
    }
}

Possible solutions that I dont know how to implement (A) Define an Autofac module in Project 2 and then Scan the Project 2 assembly in Web API?.. (B) http://docs.autofac.org/en/latest/register/registration.html#open-generic-components (C) Scanning assembly and trying to map automatically. Need help with code to be inserted here

private static void ConfigureAutofac(HttpConfiguration config) 
{
    var builder = new ContainerBuilder();
    //*************//
    //what to do here?
    //**************//
    builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
    builder.RegisterWebApiFilterProvider(config);

    var container = builder.Build();
    config.DependencyResolver = new   AutofacWebApiDependencyResolver(container);
}    
CodeReaper
  • 775
  • 2
  • 6
  • 21
  • 1
    Why don't you want to reference project 2 from project 3? – Yacoub Massad Aug 24 '16 at 11:19
  • Because different handlers can be defined in different project 2's. If all project 2's provide modules to register the handlers they define, then Project 3 will automatically configure and map to the correct handlers. No matter which type of 'Project 2' they come form – CodeReaper Aug 24 '16 at 11:25
  • How many projects do you have handlers defined in? – tomliversidge Aug 24 '16 at 11:45
  • The project needs to pluggable based on the client environment. and also during test. – CodeReaper Aug 24 '16 at 12:01
  • this question http://stackoverflow.com/questions/26595829/autofac-resolve-dependency-in-cqrs-commanddispatcher can be helpful – Artyom Pranovich Aug 25 '16 at 16:12

2 Answers2

2

Finally got proposed solution (B) http://docs.autofac.org/en/latest/register/registration.html#open-generic-components to work. Also I can confirm that no references to the dll's have been added to the solution

However I still need a nice way to dispatch queries from the API project. Something more intuitive

private static void ConfigureAutofac(HttpConfiguration config) 
{
    var builder = new ContainerBuilder();
    //*************//
    //heres what i did
    //*************//
    var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>();

    foreach (var assembly in assemblies)
    {
        builder.RegisterAssemblyTypes(assembly).AssignableTo<IQueryResult>().AsImplementedInterfaces();

        builder.RegisterAssemblyTypes(assembly).AsClosedTypesOf(typeof(IQuery<>)).AsImplementedInterfaces();

        builder.RegisterAssemblyTypes(assembly).AsClosedTypesOf(typeof(IQueryHandler<,>)).AsImplementedInterfaces();

    } 
    //rest of the code
}   
CodeReaper
  • 775
  • 2
  • 6
  • 21
  • Why are you registering your `IQueryResult` and `IQuery` messages in Autofac? They are not components (but DTOs) and should not be registered. Do note that adding them to your configuration will cause problems once you start to verify your container configuration. By the way, the `IQueryResult` abstraction seems quity useles to me. I would ditch it. – Steven Aug 24 '16 at 16:42
  • Thanks. I agree, it can be omitted. It's still a WIP. :) – CodeReaper Aug 24 '16 at 23:31
1

Your Web API project contains the application's Composition Root and the Composition Root by definition references all other assemblies in your application. Not referencing it makes no sense and just complicates things.

Please read this q/a for a more detailed discussion:

Many developers don’t want their [Web API] assembly to depend on the DAL assembly, but that's not really a problem. Don't forget that assemblies are a deployment artifact; you split code into multiple assemblies to allow code to be deployed separately. An architectural layer on the other hand is a logical artifact. It's very well possible (and common) to have multiple layers in the same assembly. In this case we'll end up having the Composition Root (layer) and the Presentation Layer in the same web application project (thus in the same assembly). And even though that assembly references the assembly containing the DAL, the Presentation Layer still does not reference the Data Access Layer. This is a big distinction. Of course, when we do this, we lose the ability for the compiler to check this architectural rule at compile time, but this shouldn't be a problem. Most architectural rules actually can't be checked by the compiler and there's always something like common sense. And if there's no common sense in your team, you can always use code reviews (which every team should IMO always do btw). You can also use a tool such as NDepend (which is commercial), which helps you verifying your architectural rules.

In general, hard references from your Composition Root to other assemblies only have to be prevented in case you have a plug-in model, where assemblies aren't known at compile-time, and can be added during or after deployment. These types of scenarios however are not very common for LOB applications.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • When I say not want to reference I mean that I don't want to add as a dll reference within a visual studio project. I don't want to do this as I don't want the developers to be able to directly use a concrete class from that assembly. Instead, I can have my IoC container 'reference' the assembly. Quite clearly the Web API will need concrete implementations of the interfaces in order to function which will be provided by Autofac. The assembly will be in the bin folder for the Web API, but it wont be a reference. That job is for autofac. – CodeReaper Aug 24 '16 at 15:21
  • @CodeReaper: Again, read [this](https://stackoverflow.com/questions/9501604/ioc-di-why-do-i-have-to-reference-all-layers-assemblies-in-entry-application) and both Mark's and my answer. – Steven Aug 24 '16 at 15:24
  • Since you insisted, here is an extract from that answer "However, all that said, with many DI Containers, you don't have to add hard references to all required libraries. Instead, you can use late binding either in the form of convention-based assembly-scanning (preferred) or XML configuration." . Now Isn't this what I just said that Autofac will load the assembly for me instead of me adding a 'hard' reference? – CodeReaper Aug 24 '16 at 15:40
  • 1
    'I don't want developers to be able to directly use a concrete class' sounds like a communication / education issue not a technical one. – tomliversidge Aug 24 '16 at 17:47
  • @tomliversidge I completely agree with you. However from my experience, no amount of training works. When you have people working joining/leaving across multiple continents, some rule have to be forced instead of trying to keep everyone upto date with rules and practices. – CodeReaper Aug 24 '16 at 23:25
  • @Steven A bit more insight. As I earlier mentioned that the module need to be plugged in based on client and also on test environment. I have TFS managing deployments and it is configured to deploy different set of packages based on where the project is being deployed. I don't know at compile time what implementation I will get. Hence I can't have a the dll being referenced by the project directly. – CodeReaper Aug 24 '16 at 23:30
  • 1
    @CodeReaper in that case, that's the exception. You are applying a plug-in model and need dynamic loading. But still be careful don't to enforce atchitecture rules without good code reviews. Without code reviews and education, Developers will always find ways around your 'restrictions'. It's just too easy to add a project reference. In Resharper its really just one single key stroke. – Steven Aug 25 '16 at 05:57
  • We have NDepend rules to ensure no wild reference changes are checked in to source control. – CodeReaper Aug 25 '16 at 07:45