Budda
Budda

Reputation: 18343

Dependency Container: how to instantiate object instances

I'm using Unity as dependency injection engine. It contains interfaces/classes for 'repository'- and 'manager'- type of classes. These repositories are responsible for getting/saving/updating data from/into DB and managers 'know' about object relationships with other components/classes.

There is factory implemented to get instance of dependency container (1 instance per request). When any repository/manager class is instantiated it receives dependency container as constructor parameter and can create all other managers/repositories when needed.

There are 2 ways to create business entities:

  1. When data are extracted from DB repository creates instances of objects and initialize them with proper DB data, among other the "Container" field is initialized.
  2. When object is created in code to be put into DB among other it accepts "IUnityContainer" parameter.

As a result, in both cases any business object has dependency container inside and if he needs to get any data he could get instance of appropriate manager and request required data.

The problem statement: last week I read some questions/answers on SO that state something like this:

  1. object instances should not have access to dependency container;
  2. instead they should receive all required interfaces in the constructor.

Guess, the same is applied for manager/repository classes: I shouldn't pass container instance to their constructor, instead I should put interfaces to other required components.

For me it seems like reasonable, but:

Question 1: Is it really good approach to "hide" container? Why? (actually, I feel I know why, but if you have good answer, please advise).

Question 2: What is good practice to address such issue?

Question 3: In my particular implementation when my objects are extracted from DB (I use Linq2Sql, and thinking about switching to EF) I can't create objects by DependencyContainer, I should do that myself (because objects are created by using expression that is executed on SQL-site, a parameterless constructor will be called with following data assigned to public properties; and dependency container is not available on the SQL side). Is there any workaround for that?

Technical details of my implementation:

Example of Manager base class constructor:

public abstract class ManagerBase : IManager
{
    protected ManagerBase(IUnityContainer container)
    {
        DependancyContainer = container;
    }

    protected readonly IUnityContainer DependancyContainer;

    ...
}

Example of Repository base class:

public abstract class RepositoryBase<T, TDb> : IRepository<T>
    where T : IEntity
    where TDb : class, IDbEntity, new()
{
    protected abstract ITable<TDb> GetTable();

    public IQueryable<T> GetAll()
    {
        return GetTable().Select(GetConverter());
    }

Example of how data are extracted from DB: repository creates instances of objects and initialize them with proper DB data, among other the "Container" field is initialized:

public class CountryRepository
    : RepositoryBase<ICountry, DbData.Country>, ICountryRepository
{   
    protected override Expression<Func<DbData.Country, ICountry>> GetConverter()
    {
        return dbEntity => new Country
        {
            DependancyContainer = DependancyContainer,

            Code = dbEntity.CountryCode,
            Name = dbEntity.CountryName,
        };
    }

Following is called when data are required to be get from DB:

public class CountryManager : ManagerBase
{
    ICountry GetCountryById(int countryId)
    {
        ICountryRepository repository = DependancyContainer.Resolve<ICountryRepository>();
        return repository.GetAll()
            .Where(country=>country.Id==countryId)
            .SingleOrDefault()
            ;
    }
}

Upvotes: 3

Views: 1956

Answers (1)

James Webster
James Webster

Reputation: 4104

Q1: Others may disagree, but generally you shouldn't provide your DI container to your domain model/entity classes. I see there being two main reasons for this:

  1. Giving the container to the entity class obfuscates the dependencies of the entity class. If you rely upon constructor injection you can see what its dependencies are by looking at the constructor. If you rely upon property injection you can see what its dependencies are by looking at the properties, although this is less clear than constructor injection (i.e. some of the properties are the data of the entity class, some of the properties are the interfaces for other services that the entity class; there is some cognitive load in separating the two categories). If you provide the container to the entity then the dependencies will be scattered throughout the class.
  2. Simpler to test. If you pass your container to the entity class, then in order to test it you will have to mock/stub the container as well as the interfaces that it retrieves from the container. You could always set up a lightweight container but this is still more work than just providing the mocked/stubbed interfaces directly.

Q2: Some would say that good practice (under DDD where the repository pattern/strong domain model pattern comes from) is not exposing your entity class to interfaces in the first place. Rather they should contain whatever business logic can be achieved without relying on other interfaces, and the more complicated logic should be in a Service class (in DDD parlance).

Q3: You don't say which object-relational mapping tool you are using, but it may be possible to take some control over the entity object instantiation process via some sort of interceptor/aspect-oriented programming pattern. This does then tie your codebase closer to relying on this particular O/R mapper and its capabilities however (i.e. harder to change away from it down the track although I generally tend to avoid worrying about that when making architectural choices).

A few more observations about your sample code that may or may not be indicative of the rest of your codebase:

  • Have you looked at AutoMapper? You may find that a better alternative to the manual mapping that you are doing in the CountryRepository.GetConverter() class.
  • It looks like you are building up parallel entity classes for each class in the DbData namespace, presumably because you cannot insert the services you want to into the DbData namespace classes. Perhaps my answer to Q2 will direct you in a better direction (rely on services and extend the DbData classes if possible).
  • I'm not really sure what role the Manager is playing... ideally all your query methods should be on the repository such that they can actually execute a SELECT ... FROM Country where Id = ? query against the database rather than returning the whole table and doing the query in memory via LINQ, after all that is what databases are good at! (notwithstanding intentional caching optimisations for the performance characteristics of your applicaton). The GetCountryById method should be on CountryRepository itself I believe.

Hope that helps!

Upvotes: 2

Related Questions