Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to break circular dependencies between repositories

To begin with, no I'm not using an ORM, nor am I allowed to. I have to hand-roll my repositories using ADO.NET.

I have two objects:

public class Firm
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public virtual IEnumerable<User> Users { get; set; }
}

public class User
{
    public Guid Id { get; set; }
    public string Username { get; set; }
    public Firm Firm { get; set; }
}

note the references to each other, a Firm has a list of Users, each User has only one Firm.

Now I want to design my repositories:

public interface IFirmRepository
{
    IEnumerable<Firm> FindAll();
    Firm FindById(Guid id);
}

public interface IUserRepository
{
    IEnumerable<User> FindAll();
    IEnumerable<User> FindByFirmId(Guid firmId);
    User FindById(Guid id);
}

So far, so good. I want to load the Firm for each User from my UserRepository. The FirmRepository knows how to create a Firm from persistence, so I'd like ot keep that knowledge with the FirmRepository.

public class UserRepository : IUserRepository
{
    private IFirmRepository _firmRepository;

    public UserRepository(IFirmRepository firmRepository)
    {
        _firmRepository = firmRepository;
    }

    public User FindById(Guid id)
    {
        User user = null;
        using (SqlConnection connection = new SqlConnection(_connectionString))
        {
            SqlCommand command = connection.CreateCommand();
            command.CommandType = CommandType.Text;
            command.CommandText = "select id, username, firm_id from users where u.id = @ID";
            SqlParameter userIDParam = new SqlParameter("@ID", id);
            command.Parameters.Add(userIDParam);
            connection.Open();
            using (SqlDataReader reader = command.ExecuteReader())
            {
                if (reader.HasRows)
                {
                    user = CreateListOfUsersFrom(reader)[0];
                }
            }
        }
        return user;
    }

    private IList<User> CreateListOfUsersFrom(SqlDataReader dr)
    {
       IList<User> users = new List<User>();
       while (dr.Read())
       {
           User user = new User();
           user.Id = (Guid)dr["id"];
           user.Username = (string)dr["username"];
           //use the injected FirmRepository to create the Firm for each instance of a User being created
           user.Firm = _firmRepository.FindById((Guid)dr["firm_id"]);
       }
       dr.Close();
       return users;
    }

}

now when I go to load any User via the UserRepository, I can ask the FirmRepository to build the User's Firm for me. So far, nothing too crazy here.

Now the problem.

I want to load a list of Users from my FirmRepository. The UserRepository knows how to create a User from persistence, so I'd like to keep that knowledge with the UserRepository. So, I pass in a reference to IUserRepository to the FirmRepository:

public class FirmRepository : IFirmRepository
{
    private IUserRepository
    public FirmRepository(IUserRepository userRepository)
    {

    }
}

But now we have a problem. The FirmRepository depends on an instance of IUserRepository, and the UserRepository now depends on an instance of IFirmRepository. So one Repository cannot be created without an instance of the other.

If I keep IoC containers OUT of this equation (and I should, b/c Unit Tests should not use IoC containers), there is no way for me to accomplish what I'm trying to do.

No problem, though, I'll just create a FirmProxy to lazy-load the Users collection from Firm! That's a better idea, b/c I don't want to load ALL the Users all the time when I go to get a Firm or a list of Firms.

public class FirmProxy : Firm
{
    private IUserRepository _userRepository;
    private bool _haveLoadedUsers = false;
    private IEnumerable<User> _users = new List<User>();

    public FirmProxy(IUserRepository userRepository)
        : base()
    {
        _userRepository = userRepository;
    }

    public bool HaveLoadedUser()
    {
        return _haveLoadedUsers;
    }

    public override IEnumerable<User> Users
    {
        get
        {
            if (!HaveLoadedUser())
            {
                _users = _userRepository.FindByFirmId(base.Id);
                _haveLoadedUsers = true;
            }
            return _users;
        }
    }

}

So, now I have a nice proxy object to facilitate lazy-loading. So, when I go to create a Firm in the FirmRepository from persistence, I instead return a FirmProxy.

public class FirmRepository : IFirmRepository
{

    public Firm FindById(Guid id)
    {
        Firm firm = null;
        using (SqlConnection connection = new SqlConnection(_connectionString))
        {
            SqlCommand command = connection.CreateCommand();
            command.CommandType = CommandType.Text;
            command.CommandText = "select id, name from firm where id = @ID";
            SqlParameter firmIDParam = new SqlParameter("@ID", id);
            command.Parameters.Add(firmIDParam);
            connection.Open();
            using (SqlDataReader reader = command.ExecuteReader())
            {
                if (reader.HasRows)
                {
                    firm = CreateListOfFirmsFrom(reader)[0];
                }
            }
        }
        return firm;
    }

private IList<Firm> CreateListOfFirmsFrom(SqlDataReader dr)
{
    IList<FirmProxy> firms = new List<FirmProxy>([need an IUserRepository instance here!!!!]);
    while (dr.Read())
    {

    }
    dr.Close();
    return firms;
}

But this is still not working!!!

In order to return a FirmProxy instead of a Firm, I need to be able to new up a FirmProxy in my FirmRepository class. Well, the FirmProxy takes an IUserRepository instance b/c the UserRepository contains the knowledge of how to create a User object from persistence. As a result of the FirmProxy needing an IUserRepository, my FirmRepository now needs an IUserRepository as well, and I'm right back to square one!

So, given this long winded-explanation/source code, how can I go about being able to create an instance of a User from the FirmRepository and an instance of Firm from the UserRepository without:

  1. put the User creation code in the FirmRepository. I don't like this. Why should the FirmRepository know anything about creating an instance of a User? This to me is a violation of SoC.
  2. Not using the Service Locator pattern. If I go this route, I feel this is notoriously hard to test. Besides, constructors of objects that take explicit dependencies make those dependencies obvious.
  3. property injection instead of constructor injection. This doesn't fix a thing, I still need an instance of IUserRepository when new'ing up a FirmProxy no matter how the dependency is injected into FirmProxy.
  4. Having to "dumb down" either the Firm or the User object and expose a FirmID on User, for example instead of a Firm. If I'm just doing id's, then the requirement to load a Firm from the UserRepository goes away, but with it goes the richness of being able to ask the Firm object for anything withing the context of a given User instance.
  5. Resorting to an ORM. Again, I want to do it, but I can't. No ORM's. That's the rule (and yes, it's a crappy rule)
  6. keep all my inject-able dependencies as dependencies injected from the lowest level of the application, which is the UI (in my case, a .NET web project). No cheating and using IoC code in the FirmProxy to new up the appropriate dependency for me. That's basically using the Service Locator pattern anyhow.

I think about NHiberante and Enitity Framework, and it seems they have no problem figuring out how to generate sql for a simple example that I've presented.

Does anyone else have any other ideas/methods/etc... that will help me achieve what I want to do without an ORM?

Or maybe there is a different/better way to approach this? Want I don't want to lose is the ability to access a Firm from a User, or to get a list of Users for a given Firm

like image 767
Michael McCarthy Avatar asked Mar 09 '12 16:03

Michael McCarthy


People also ask

How do you break a circular dependency?

But circular dependencies in software are solvable because the dependencies are always self-imposed by the developers. To break the circle, all you have to do is break one of the links. One option might simply be to come up with another way to produce one of the dependencies, in order to bootstrap the process.

How do you fix a circular dependency problem?

To resolve circular dependencies: Then there are three strategies you can use: Look for small pieces of code that can be moved from one project to the other. Look for code that both libraries depend on and move that code into a new shared library. Combine projectA and projectB into one library.

How do I break a circular dependency spring?

A simple way to break the cycle is by telling Spring to initialize one of the beans lazily. So, instead of fully initializing the bean, it will create a proxy to inject it into the other bean. The injected bean will only be fully created when it's first needed.

How do I remove circular dependency in DAX?

Therefore, you experience circular dependency only once you have created the second column. The correct solution to avoid this is to restrict the list of columns that the calculated column depends on, by using ALLEXCEPT or REMOVEFILTERS and keeping only the table's primary key.


1 Answers

You need to think more clearly about your aggregate root objects, i.e. the focus of each repository. You don't create a repository for each table in your database, that's not the objective. The intent is to identify the aggregate root object that you need to work with, include the child tables/records, i.e. User(Firm, Address, Access Rights). That's what your repository will return to your app.

The difficulty you are having is showing you that your repositories are not correctly structured. Anytime I find my code becoming too difficult it raises an alert with me that I'm probably doing it wrong, I live my life by that ;)

like image 104
Lazarus Avatar answered Oct 20 '22 01:10

Lazarus