Let’s say I have some DDD service that requires some IEnumerable<Foo> to perform some calculations. I came up with two designs:
Abstract the data access with an
IFooRepositoryinterface, which is quite typicalpublic class FooService { private readonly IFooRepository _fooRepository; public FooService(IFooRepository fooRepository) => _fooRepository = fooRepository; public int Calculate() { var fooModels = _fooRepository.GetAll(); return fooModels.Sum(f => f.Bar); } }Do not rely on the
IFooRepositoryabstraction and injectIEnumerable<Foo>directlypublic class FooService { private readonly IEnumerable<Foo> _foos; public FooService(IEnumerable<Foo> foos) => _foos = foos; public int Calculate() => _foos.Sum(f => f.Bar); }
This second design seems better in my opinion as FooService now does not care where the data is coming from and Calculate becomes pure domain logic (ignoring the fact that IEnumerable may come from an impure source).
Another argument for using the second design is that when IFooRepository performs asynchronous IO over the network, usually it will be desirable to use async-await like:
public class AsyncDbFooRepository : IFooRepository
{
public async Task<IEnumerable<Foo>> GetAll()
{
// Asynchronously fetch results from database
}
}
But as you need to async all the way down, FooService is now forced to change its signature to async Task<int> Calculate(). This seems to violate the dependency inversion principle.
However, there are also issues with the second design. First of all, you have to rely on the DI container (using Simple Injector as an example here) or the composition root to resolve the data access code like:
public class CompositionRoot
{
public void ComposeDependencies()
{
container.Register<IFooRepository, AsyncDbFooRepository>(Lifestyle.Scoped);
// Not sure if the syntax is right, but it demonstrates the concept
container.Register<FooService>(async () => new FooService(await GetFoos(container)));
}
private async Task<IEnumerable<Foo>> GetFoos(Container container)
{
var fooRepository = container.GetInstance<IFooRepository>();
return await fooRepository.GetAll();
}
}
Also in my specific scenario, AsyncDbFooRepository requires some sort of runtime parameter to construct, and that means you need an abstract factory to construct AsyncDbFooRepository.
With the abstract factory, now I have to manage the life cycles of all dependencies under AsyncDbFooRepository (the object graph under AsyncDbFooRepository is not trivial). I have a hunch that I am using DI incorrectly if I opt for the second design.
In summary, my questions are:
- Am I using DI incorrectly in my second design?
- How can I compose my dependencies satisfactorily for my second design?