Pages

Tuesday 20 April 2010

Enhancing the Aggregate Pattern in Domain Driven Design with Weak References

Note: This post makes use of the generic WeakReference class illustrated in this post.

I'm working my way through Eric Evan's wonderful book Domain Driven Design and, already, it has a place on my "books-every-developer-should-read" shelf. One of the 3 main design patterns covered in chapter 6 is Aggregate. One of the tenets of this pattern is that external objects can request a reference to an internal object of the Aggregate on the assumption that they use it fleetingly and do not hold a permanent reference to it. The reason for this is that the Aggregate needs to be disposable as a whole and this will not happen if some internal objects are still being referenced.

Now, the problem with this is that, certainly in .Net, there is nothing in the language to enforce this requirement. As a developer you have to write your Aggregates and hope that the next developer to consume them understands the pattern requirements. A simple recipe for disaster!

Enter the genreric WeakReference class.

I'll extend Eric's example using his idea of a Car class (omitting any Factory or Repository complications)...

public enum WheelLocation
{
  FrontLeft,
  FrontRight,
  RearLeft,
  RearRight
}

public class Wheel : IDisposable
{
  public WheelLocation Location
  {
    get;
    private set;
  }
   
  public Wheel(WheelLocation location)
  {
    Location = location;
  }

  public void Dispose()
  {
  }
}

public class Car : IDisposable
{
  public Guid ID
  {
    get;
    private set;
  }

  private IList<Wheel> _wheels;

  public Car(Guid id)
  {
    ID = id;

    _wheels = new List<Wheel>(4);

    _wheels.Add(new Wheel(WheelLocation.FrontLeft));
    _wheels.Add(new Wheel(WheelLocation.FrontRight));
    _wheels.Add(new Wheel(WheelLocation.RearLeft));
    _wheels.Add(new Wheel(WheelLocation.RearRight));
  }

  public void Dispose()
  {
    foreach (IDisposable obj in _wheels)
      obj.Dispose();
  }

  public Wheel GetWheel(WheelLocation location)
  {
    return _wheels.Where(w => w.Location == location).Single();
  }
}

So, we now have a nicely encapsulated Car Aggregate class that can also, indirectly, supply any consumers with references to its internal objects (Wheels). Here's a (kinda dumb!) usage example...

public class WheelEventArgs : EventArgs
{
  public Wheel Wheel { get; private set; }

  public CarEventArgs(Wheel wheel)
    : base()
  {
    Wheel = wheel;
  }
}

public class CarConsumer
{
  public void CreateCar(Guid id)
  {
    using (var car = new Car(id))
    {
      OnCarCreate(car);
    }
  }

  protected virtual void OnCarCreated(Car car)
  {
    if (CarCreated != null)
    {
      CarCreated(this, new EventArgs(car.GetWheel(WheelLocation.FrontLeft));
      CarCreated(this, new EventArgs(car.GetWheel(WheelLocation.FrontRight));
      CarCreated(this, new EventArgs(car.GetWheel(WheelLocation.RearLeft));
      CarCreated(this, new EventArgs(car.GetWheel(WheelLocation.RearRight));
    }
  }

  public event CarCreated;
}

The problem here is that we have no way of knowing whether the classes that registered to the CarCreated event still have references to the Wheel objects when we come to dispose our Car class.

So, what we can do is change the GetWheel method to return weak references to the internal wheels...

public WeakReference<Wheel> GetWheel(WheelLocation location)
{
  return new WeakReference<Wheel>(_wheels.Where(w => w.Location == location).Single());
}

Now, this doesn't prevent the external objects from securing a strong reference to the wheels and it doesn't stop dumb developers doing dumb things; but it does indicate our intention with the pattern. "Hey, look, I'm returning a WeakReference to this object - there's a reason for it!"

1 comment:

  1. Ah great.

    I've spent years building a big database client, that has 'memory leaks' (i.e. garbage collection blocks) all over the place, and I never knew about the WeakReference class.

    Although I must say the generic version looks waaay more useful.

    ReplyDelete