Reputation: 18343
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:
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:
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:
I will need for any object to provide ALL interfaces that he needs (usually, each manager/repository/entity needs 3-5 of them);
There are a lot of cases when only 1-2 interfaces are required (so I don't want to create a lot of other);
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
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:
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:
CountryRepository.GetConverter()
class.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).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