views:

505

answers:

5

Note: The examples below are C# but this problem should not be specific to any language in particular.

So I am building an object domain using a variant of the S# Architecture. For those unfamiliar with it, and to save you some reading time the idea is simply that you have a Data Access Object Interface for each of your domain objects that is responsible for loading to/from the persistence layer. Everything that might ever need to load/save a given object then accepts that object's data access interface as a dependency. So for example we can have the following where a product will lazy load the customer that purchased it as needed:

public class Product {
  private ICustomerDao _customerDao;
  private Customer _customer;
  public Product(ICustomerDao customerDao) {_customerDao = customerDao;}
  public int ProductId {get; set;}
  public int CustomerId {get; set;}
  public Customer Customer {
        get{
            if(_customer == null) _customer = _customerDao.GetById(CustomerId);
            return _customer;
        }
}
public interface ICustomerDao {
   public Customer GetById(int id);
}

This is all well and good until you reach a situation where two objects need to be able to load each other. For example a many-to-one relationship where, as above, a product needs to be able to lazy load its customer, but also customer needs to be able to get a list of his products.

public class Customer {
  private IProductDao _productDao;
  private Product[] _products;
  public Customer(IProductDao  productDao) {_productDao = productDao;}
  public int CustomerId {get; set;}
  public Product[] Products {
        get{
            if(_products == null) _products = _productDao. GetAllForCustomer(this);
            return _products;
        }
}


public interface IProductDao {
   public Product[] GetAllForCustomer(Customer customer);
}

I know that this is a really common situation but I am relatively new at this. My stumbling block is what to do when implementing the Data Access Objects. Because a Customer has a dependency on IProductDao, the CustomerDao implementation must also, however the vice versa is also true and ProductDao must take a dependency on ICustomerDao.

public class CustomerDao : ICustomerDao {
      private IProductDao _productDao;
      public CustomerDao(IProductDao  productDao) {_productDao = productDao;}
      public Customer GetById(int id) {
          Customer c = new Customer(_customerDao);
          // Query the database and fill out CustomerId
          return c;
      }
 }
public class ProductDao : IProductDao {
      private ICustomerDao _customerDao;
      public ProductDao (ICustomerDao customerDao) {_customerDao = customerDao;}
      public Product[] GetAllForCustomer(Customer customer) {
          // you get the idea
      }
 }

And here we have the problem. You cannot instantiate CustomerDao without an IProductDao and vice versa. My inversion of control container (Castle Windsor) hits the circular dependency and chokes.

I have come up with a for-the-time-being solution which involves lazy loading the DAO objects themselves (I will post this as an answer) but I don't like it. What are the time-tested solutions to this problem?

EDIT: The above is a simplification of the architecture I'm actually using and I am not recommending someone actually pass DAOs to an object. A better implementation closer to what I am actually doing is similar to the way NHibernate works where the actual objects are very simple and the above are actually proxy objects which inherit and override the appropriate fields.

+1  A: 

The lazy loading should be managed by your persistance layer, not your repositories.

Also your Customer and Product objects shouldn't have any references to the repositories, that just seems... wrong.

jonnii
This is a simplification. Technically speaking they don't. I was forbidden to use NHibernate but this works in a similar way where the actual objects are POCOs and these are proxies which override the methods necessary, never meant to be instantiated directly
George Mauer
A: 

You simply shouldn't have circular dependencies like this. It's just wrong.

If you just need to be wrong for a little while to get over the hump, then try using a service locator: a class that knows about the container and resolves dependencies when they are used instead of when your DAO class is being instantiated.

You might want to check out Oren Eini's service locator implementation in Rhino.Tools (it's a static class called IoC)

Chris Bilson
What do you mean not have a circular dependency like this? Can you just tell me in the context of my example where a customer references a product and vice versa?
George Mauer
A: 

My solution, and let me disclaim yet again that I am not a big fan of it is to have each of the data access objects lazy load the DAOs it depends on directly from the container. For simplicity's sake I make both the CustomerDao and ProductDao inherit from a BaseDao object and then implement it as follows:

public abstract class BaseDao() {
  private ICustomerDao _customerDao;
  protected ICustomerDao _CustomerDao {
    get {
      if(_customerDao == null) _customerDao = IoC.Container.Resolve<ICustomerDao>();
      return _customerDao;
    }
  private IProductDao _productDao;
  protected IProductDao _ProductDao {
    get {
      if(_productDao == null) _productDao = IoC.Container.Resolve< IProductDao  >();
      return _productDao;
    }

}

and then

public class CustomerDao : BaseDao, ICustomerDao {
      public Customer GetById(int id) {
          Customer c = new Customer(_CustomerDao);
          // Query the database and fill out CustomerId
          return c;
      }
 }
public class ProductDao : BaseDao, IProductDao {
      public Product[] GetAllForCustomer(Customer customer) {
          // use the base class's _ProductDao to instantiate Products
      }
 }

I don't like this because

  • a) Each DAO now has a dependency on the container
  • b) You can no longer tell from the constructor who each Dao depends on
  • c) Either each DAO needs at least an implicit dependency on each other Dao if you use such a base class or you could move the lazy loading out of the base class but have to duplicate a great deal of code.
  • d) Unit tests need to create and pre-fill the Container
George Mauer
+1  A: 

I just discovered a significantly better approach that works at least with Castle Windsor. By changing the data access object dependencies to properties rather than constructor dependencies Windsor will autofill them after instantiating each object fully.

So the following works just fine:

public class CustomerDao : ICustomerDao {

    private IProductDao _productDao;

    public IProductDao ProductDao {
        get { return _productDao; }
        set { _productDao = value; }
    }
    public CustomerDao() { }
    public Customer GetById(int id) {
        Customer c = new Customer(_productDao);
        // Query the database and fill out CustomerId
        return c;
    }
}
public class ProductDao : IProductDao {
    private ICustomerDao _customerDao;

    public ProductDao() { }

    public ICustomerDao CustomerDao {
        get { return _customerDao; }
        set { _customerDao = value; }
    }

    public Product[] GetAllForCustomer(Customer customer) {
        return null;
    }

}
George Mauer
+2  A: 

As the other posters have suggested, you might want to rethink your architecture - it looks like you are making things hard on yourself.

Also note:

By changing the data access object dependencies to properties rather than constructor dependencies Windsor will autofill them after instantiating each object fully.

Be careful. In doing this, you've basically told Windsor that these dependencies are optional (unlike dependencies injected via the constructor). This seems like a bad idea, unless these dependencies are truly optional. If Windsor can't fulfill a required dependency, you want it to puke.

remotefacade
This is true, the architecture is awkward, but writing framework code is worse and NHibernate is not an option. Currently I've actually moved the code to a completely separate lazy loading proxy layer that works similarly to NHibernate.
George Mauer