views:

132

answers:

3

We are building an ASP.NET project, and encapsulating all of our business logic in service classes. Some is in the domain objects, but generally those are rather anemic (due to the ORM we are using, that won't change). To better enable unit testing, we define interfaces for each service and utilize D.I.. E.g. here are a couple of the interfaces:

IEmployeeService
IDepartmentService
IOrderService
...

All of the methods in these services are basically groups of tasks, and the classes contain no private member variables (other than references to the dependent services). Before we worried about Unit Testing, we'd just declare all these classes as static and have them call each other directly. Now we'll set up the class like this if the service depends on other services:

public EmployeeService : IEmployeeService
{
   private readonly IOrderService _orderSvc;
   private readonly IDepartmentService _deptSvc;
   private readonly IEmployeeRepository _empRep;

   public EmployeeService(IOrderService orderSvc
                        , IDepartmentService deptSvc
                        , IEmployeeRepository empRep)
   {
      _orderSvc = orderSvc;
      _deptSvc = deptSvc;
      _empRep = empRep;
   }
   //methods down here
}

This really isn't usually a problem, but I wonder why not set up a factory class that we pass around instead?

i.e.

public ServiceFactory
{
   virtual IEmployeeService GetEmployeeService();
   virtual IDepartmentService GetDepartmentService();
   virtual IOrderService GetOrderService();
}

Then instead of calling:

_orderSvc.CalcOrderTotal(orderId) 

we'd call

_svcFactory.GetOrderService.CalcOrderTotal(orderid)

What's the downfall of this method? It's still testable, it still allows us to use D.I. (and handle external dependencies like database contexts and e-mail senders via D.I. within and outside the factory), and it eliminates a lot of D.I. setup and consolidates dependencies more.

Thanks for your thoughts!

+4  A: 

One argument against this is that it doesn't make your dependencies clear. It shows that you depend on "some of the stuff in the service factory" but not which services. For refactoring purposes it can be helpful to know exactly what depends on what.

Dependency injection should make this kind of thing easy, if you're using an appropriate framework - it should just be a matter of creating the right constructor, defining what implements which interface, and letting it sort everything out.

Jon Skeet
Yep, we're using StructureMap, which *typically* handles everything fine. However, this relies on runtime error checking, and if someone accidentally forgot to mark a class as public we don't know until testing the program. There's also issues when services depend on each other - S/M gets stackoverflow exceptions and we need to sort things out manually instead of the auto dependency resolution. A factory method would seem to eliminate all these issues.
Andrew
1) With StructureMap, you can validate your container in a unit test or at app startup: container.AssertConfigurationIsValid()2) If you are getting stack overflow exceptions, you either have a circular dependency chain (bad), or have stumbled upon a bug we dont know about (tell us github.com/structuremap)3) The latest structuremap code (from github) has a StructureMap.AutoFactory assembly which probably does what you want. See discussion at http://groups.google.com/group/structuremap-users/browse_thread/thread/d1003ce9038403f5
Joshua Flanagan
+1  A: 

If most of your classes depend on this three interfaces you could pass an object around that wraps them together, BUT: if most of the classes just depend on one or two of them then it's not a good idea since those classes will have access to objects they don't need and they have no business with and some programmers will always call the code they are not supposed to call just because it's available.

Btw, it's not a factory unless you always create a new object in the Get[...]Service() methods and doing that just for passing a few methods around is bad. I'd just call it ServiceWrapper and turn them into the properties EmployeeService, DepartmentService and OrderService.

dbemerlin
"it's not a good idea since those classes will have access to objects they don't need and they have no business with and some programmers will always call the code they are not supposed to call just because it's available." - good point, marking this as the answer.
Andrew
+3  A: 

Such a factory is essentially a Service Locator, and I consider it an anti-pattern because it obscures your dependencies and make it very easy to violate the Single Responsibility Principle (SRP).

One of the many excellent benefits we derive from Constructor Injection is that it makes violations of the SRP so glaringly obvious.

Mark Seemann
I agree, but there are likely times where many injected dependencies are a GOOD thing. Consider an application we are writing right now that handles calculating a pay check. The calculation of a check is something that happens in one place. It utilizes common routines from many different services, but the 36 steps required to create the paycheck (calculate taxes, deductions, benefits, etc.) are not used elsewhere. No matter how you split it up, it's still 36 steps to create the paycheck. By keeping it in one place, it's very obvious how a check is calculated.
Andrew
Just because you only use it in one place doesn't mean that you can't split the calculation up into several smaller classes, each with their own dependencies, hidden behind Facades and Aggregate Services. Personally, I'd never want to maintain a single class with 36 steps. Even Visual Studio's very tolerant maintainability analyzer will very likely complain about a single class performing these steps. Such a calculation sounds more maintainable as an entire Sub-Domain than as a Transaction Srcipt.
Mark Seemann
But if it's a batch process with 36 steps, aren't you obfuscating the process by hiding it behind various other services? Taking a transcription script and breaking it up for the sake of breaking it up seems a bit silly, but maybe that's just me.
Andrew
The whole purpose of such coding principles as SOLID is to make the code clearer and cleaner. At a high level, you could express those 36 steps as 36 implementatations of an IStep interface, so that the applications entry point simply iterates through the list of clearly named ISteps. It doesn't get much clearer than that.
Mark Seemann