开发者

DDD and constructor explosion

I'm practicing DDD with ASP.NET MVC and come to a situation where my controllers have many dependencies on different services and repositories, and testing becomes very tedious.

In general, I have a service or repository for each aggregate root. Consider a page which will list a customer, along with it's orders and a dropdown of different packages and sellers. All of those types are aggregate roots. For this to work, I need a CustomerService, OrderService, PackageRepository and a UserRepository. Like this:

public class OrderController {
    pu开发者_如何学编程blic OrderController(Customerservice customerService, 
      OrderService orderService, Repository<Package> packageRepository, 
      Repository<User> userRepository) 
    {
        _customerService = customerService
        ..
    }
}

Imagine the number of dependencies and constructor parameters required to render a more complex view.

Maybe I'm approaching my service layer wrong; I could have a CustomerService which takes care of all this, but my service constructor will then explode. I think I'm violating SRP too much.


I think I'm violating SRP too much.

Bingo.

I find that using a command processing layer makes my applications architecture cleaner and more consistent.

Basically, each service method becomes a command handler class (and the method parameters become a command class), and every query is also its own class.

This won't actually reduce your dependencies - your query will likely still require those same couple of services and repositories to provide the correct data; however, when using an IoC framework like Ninject or Spring it won't matter because they will inject what is needed up the whole chain - and testing should be much easier as a dependency on a specific query is easier to fill and test than a dependency on a service class with many marginally related methods.

Also, now the relationship between the Controller and its dependencies is clear, logic has been removed from the Controller, and the query and command classes are more focused on their individual responsibilities.

Yes, this does cause a bit of an explosion of classes and files. Employing proper Object Oriented Programming will tend to do that. But, frankly, what's easier to find/organize/manage - a function in a file of dozens of other semi-related functions or a single file in a directory of dozens of semi-related files. I think that latter hands down.

Code Better had a blog post recently that nearly matches my preferred way of organizing controllers and commands in an MVC app.


Well you can solve this issue easily by using the RenderAction. Just create separate controllers or introduce child actions in those controllers. Now in the main view call render actions with the required parameters. This will give you a nice composite view.


Why not have a service for this scenario to return a view model for you? That way you only have one dependency in the controller although your service may have the separate dependencies


the book dependency injection in .net suggests introducing "facade services" where you'd group related services together then inject the facade instead if you feel like you have too many constructor parameters.


Update: I finally had some available time, so I ended up finally creating an implementation for what I was talking about in my post below. My implementation is:

public class WindsorServiceFactory : IServiceFactory
{
    protected IWindsorContainer _container;

    public WindsorServiceFactory(IWindsorContainer windsorContainer)
    {
        _container = windsorContainer;
    }

    public ServiceType GetService<ServiceType>() where ServiceType : class
    {
        // Use windsor to resolve the service class.  If the dependency can't be resolved throw an exception
        try { return _container.Resolve<ServiceType>(); }
        catch (ComponentNotFoundException) { throw new ServiceNotFoundException(typeof(ServiceType)); }
    }
}

All that is needed now is to pass my IServiceFactory into my controller constructors, and I am now able to keep my constructors clean while still allowing easy (and flexible) unit tests. More details can be found at my blog blog if you are interested.


I have noticed the same issue creeping up in my MVC app, and your question got me thinking of how I want to handle this. As I'm using a command and query approach (where each action or query is a separate service class) my controllers are already getting out of hand, and will probably be even worse later on.

After thinking about this I think the route I am going to look at going is to create a SerivceFactory class, which would look like:

public class ServiceFactory
{
    public ServiceFactory( UserService userService, CustomerService customerService, etc...)
    {
        // Code to set private service references here
    }

    public T GetService<T>(Type serviceType) where T : IService
    {
        // Determine if serviceType is a valid service type, 
        //  and return the instantiated version of that service class
        //   otherwise throw error
    }
}

Note that I wrote this up in Notepad++ off hand so I am pretty sure I got the generics part of the GetService method syntactically wrong , but that's the general idea. So then your controller will end up looking like this:

public class OrderController {
    public OrderController(ServiceFactory factory) {
        _factory = factory;
    }
}

You would then have IoC instantiate your ServiceFactory instance, and everything should work as expected.

The good part about this is that if you realize that you have to use the ProductService class in your controller, you don't have to mess with controller's constructor at all, you only have to just call _factory.GetService() for your intended service in the action method.

Finally, this approach allows you to still mock services out (one of the big reasons for using IoC and passing them straight into the controller's constructor) by just creating a new ServiceFactory in your test code with the mocked services passed in (the rest left as null).

I think this will keep a good balance out the best world of flexibility and testability, and keeps service instantiation in one spot.

After typing this all out I'm actually excited to go home and implement this in my app :)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜