Wednesday, November 11, 2015

Autofac Aggregate Service

There is a nice little feature of Autofac called Aggregate Service, that works very well with NSubstitute's recursive mock functionality to make tests much more maintainable and resilient to dependency modifications.

Let's take a useful if contrived example of dependency injection at work, a Greeter class that writes "Good Morning" if the hour is before 13:


{
  public class Greeter
  {
    private readonly IDateTime _dateTime;
    private readonly IWriter _writer;
 
    public Greeter(IDateTime dateTime, IWriter writer)
    {
      _dateTime = dateTime;
      _writer = writer;
    }
 
    public void WriteDate()
    {
      if (_dateTime.Now.Hour >= 13)
      {
        _writer.WriteLine("Good afternoon");
      }
      else
      {
        _writer.WriteLine("Good morning");
      }    
    }
  }

IDateTime is a simple wrapper for DateTime.Now, and IWriter for Console.WriteLine, so that the logic is testable:



    [Fact]
    public void Greeter_Hour0_WritesGoodMorning()
    {
      var fakeTime = Substitute.For<IDateTime>();
      fakeTime.Now.Returns(new DateTime(1, 1, 1, 0, 0, 0));
      var writer = Substitute.For<IWriter>();
      var sut = new Greeter(fakeTime, writer);
 
      sut.WriteDate();
 
      writer.Received().WriteLine("Good morning");
 
    }
 
    [Fact]
    public void Greeter_Hour13_WritesGoodAfternoon()
    {
      var fakeTime = Substitute.For<IDateTime>();
      fakeTime.Now.Returns(new DateTime(1, 1, 1, 13, 0, 0));
      var writer = Substitute.For<IWriter>();
      var sut = new Greeter(fakeTime, writer);
 
      sut.WriteDate();
 
      writer.Received().WriteLine("Good afternoon");
 
    }

The injection is wired up at application start:


using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autofac;

namespace DIDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      var builder = new ContainerBuilder();
      builder.RegisterType<DateTimeProvider>().As<IDateTime>();
      builder.RegisterType<ConsoleWriter>().As<IWriter>();
      builder.RegisterType<Greeter>().AsSelf();
      var container = builder.Build();

      using (var c = container.BeginLifetimeScope())
      {
        var obj = c.Resolve<Greeter>();
        obj.WriteDate();
      }
    }
  }
}

Okay, this works reasonably well. The class with the logic has no direct contact with the implementation of the operating system calls DateTime.Now and Console.WriteLine, so we can script and monitor the interactions with the operating system, and isolate our code. But what happens if we want to add another dependency to the constructor later on? We will need to modify all the places where Greeter is instantiated, which may be in a number of tests.

Autofac's AggregateService module can be helpful here. This is a separate NuGet download from the rest of Autofac, Autofac.Extras.AggregateService, and pulls in a dependency on Castle.Core, It lets you move your dependencies into properties of a single interface, which I like to nest inside the class it supports. After this change, Greeter.cs looks like this:


using System;
 
namespace DIDemo
{
  public class Greeter
  {
    private readonly IDepend _depend;
 
    public interface IDepend
    {
      IDateTime DateTime { get; set; }
      IWriter Writer { get; set; }
    }
 
    public Greeter(IDepend depend)
    {
      _depend = depend;
    }
 
    public void WriteDate()
    {
      if (_depend.DateTime.Now.Hour >= 13)
      {
        _depend.Writer.WriteLine("Good afternoon");
      }
      else
      {
        _depend.Writer.WriteLine("Good morning");
      }
 
    }
  }
}

The nice thing here is that constructor will not change if a new dependency is added, and most of your unit tests can ignore a new dependency, because NSubstitute's default recursive behavior will hydrate the properties with NSubstitute implementations. So you can script IDepend.DateTime.Now if you care about it, or you can leave it alone confident that NSubstitute will not return null. This keeps the tests loosely specified.

Here is what one of the tests looks like now with the IDepend construction. Note how the DateTime property gets filled in automagically by NSubstitute:


[Fact]
public void Greeter_HourIsZero_ReturnsGoodMorning()
{
  var d = Substitute.For<Greeter.IDepend>();
  d.DateTime.Now.Returns(new DateTime(1, 1, 1, 0, 0, 0));
  var sut = new Greeter(d);
 
  sut.WriteDate();
 
  d.Writer.Received().WriteLine("Good morning");
 
}

To wire this up, you need to add one line of code to register the aggregation interface at application start:

builder.RegisterAggregateService(typeof(Greeter.IDepend));

If you start employing this technique widely, you will probably want to standardize on convention, such as always using an IDepend nested interface. You can rewrite the above line to generalize it:


var assembly = Assembly.GetExecutingAssembly();
var dependencyInterfaces = assembly.GetTypes()
     .Where( t => t.IsInterface && t.Name == "IDepend" && t.IsNested);
  
dependencyInterfaces.ForEach(a => builder.RegisterAggregateService(a));

And if you have ReSharper, you can create a code template (Resharper->Tools->TemplateExplorer) to make adding the IDepend interface and constructor instantiation a one-word process. First,the template:


private IDepend _depend;
 
public interface IDepend
{
  $END$
}
 
 
public $CLASS$(IDepend depend)
{
  _depend = depend;
}

I've named this "depend", and configured $CLASS$ to take the name of the containing type. This makes adding the dependency a simple six-letter-plus-tab operation:

Thanks to Chris Smith of Velir for showing me the AggregateService feature. And for getting me started on DI in the first place.

3 comments:

  1. Interesting idea - but kinda feels like and anti-pattern to me. Wrapping services and exposing them through properties of another service doesn't really solve anything, it might seem like its solving the constructor over-injection issues, but really its just hiding them away. The root cause is that your class is still probably doing too much and violates the Single Responsibility Principle. You have just reduced the number of injected dependencies, you haven't reduced the number of dependencies the class has.

    Also, this pattern would tie you down to an IOC container that supports AggregationServices like autofac, adding another dependency into the mix.

    This blog by Mark Seemann has some good ways of aggregating services without adding in the extra dependency on the IOC container and also without hiding away the issue over over injection into the constructor.

    Interested in your thoughts on that

    ReplyDelete
    Replies
    1. Thanks for the comment, and a reasonable perspective. I am sympathetic to the hiding clutter argument, and don't like C# regions for exactly that reason. But I'm not sure it applies here. I'm not saying that the "IDepend" interface should contain many properties, just that a single change should not disrupt existing tests needlessly. I feel this structure allows tests to stay loosely specified, lowering the maintenance burden. And lowering that burden makes it easier to break functionality out of a class that is becoming too complex and into a new class, encouraging the Single Responsibility Principle. Finally, I'm personally not persuaded by the argument that you should avoid differentiating features of a Dependency Injection framework, unless swapability is a design goal (e.g. as it is for the DI hooks in ASP.NET MVC). I like using the power features of the frameworks I use. But that is obviously a point of personal and team preference, and the trade-off you are raising is a real one.

      Finally, I should mention you can create the same effect without using this feature by defining and registering classes that implement the dependency interface. For example:
      public class Depend: IDepend {
      public Depend (IDateTime dateTime, IWriter writer) { // normal property setter logic omitted }
      }
      I was in fact using this approach when Chris Smith told me about the Aggregate Service plug in. I think this approach would work with any DI framework, so you would have a fallback if you did want to change frameworks.

      The link to the Mark Seeman post did not come through in your comment, but I think it might have been this one: http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/ He makes the point that having more than 3 or so dependencies can feel like I violation of SRP, and that you can get closer to SRP by grouping related functions into a single class. I think that would still be applicable here; you would just be looking for too many properties in the IDepend class.

      Anyway, that's my current take. Will update here if that changes. :)

      Delete
  2. Hello guys,
    I agree with Richard and would not like to introduce the `IDepend` extra dependency. I would keep the original constructor with the datetime and writer parameters. Please take a look on the alternative solution written with AutoFixture: https://gist.github.com/sergeyshushlyapin/f692eddb1db277817545

    ReplyDelete