Допустим, у меня есть служба DDD, которая требует некоторого IEnumerable<Foo>
для выполнения некоторых вычислений. Я придумал две схемы:
Абстрагируйте доступ к данным с помощью интерфейса
IFooRepository
, что довольно типично.public 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); } }
Не полагайтесь на абстракцию
IFooRepository
и внедряйтеIEnumerable<Foo>
напрямую.public class FooService { private readonly IEnumerable<Foo> _foos; public FooService(IEnumerable<Foo> foos) => _foos = foos; public int Calculate() => _foos.Sum(f => f.Bar); }
На мой взгляд, этот второй дизайн кажется лучше, так как FooService
теперь не важно, откуда поступают данные, а Calculate
становится чистой логикой предметной области (игнорируя тот факт, что IEnumerable
может поступать из нечистого источника).
Еще один аргумент в пользу использования второго варианта заключается в том, что когда IFooRepository
выполняет асинхронный ввод-вывод по сети, обычно желательно использовать async-await
, например:
public class AsyncDbFooRepository : IFooRepository
{
public async Task<IEnumerable<Foo>> GetAll()
{
// Asynchronously fetch results from database
}
}
Но так как вам нужно асинхронизировать до конца, FooService
теперь вынужден изменить свою подпись на async Task<int> Calculate()
. Похоже, это нарушает принцип инверсии зависимостей.
Однако есть проблемы и со вторым дизайном. Прежде всего, вы должны полагаться на контейнер DI (используя Simple Injector в качестве примера здесь) или корень композиции для разрешения кода доступа к данным, например:
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();
}
}
Кроме того, в моем конкретном сценарии AsyncDbFooRepository
требуется какой-то параметр времени выполнения для создания, а это означает, что вам нужна абстрактная фабрика для создания AsyncDbFooRepository
.
С абстрактной фабрикой теперь мне приходится управлять жизненными циклами всех зависимостей под AsyncDbFooRepository
(граф объекта под AsyncDbFooRepository
не тривиален). У меня есть подозрение, что я неправильно использую DI, если выбираю второй дизайн.
Подводя итог, мои вопросы таковы:
- Я неправильно использую DI во втором дизайне?
- Как я могу удовлетворительно составить свои зависимости для моего второго проекта?
AsyncDbFooRepository
(первый дизайн), но не будет ли это также нарушением принципа инверсии зависимостей (как описано в вопросе)? - person rexcfnghk   schedule 30.06.2017IFooRepository
/IEnumerable<Foo>
. Во-вторых, сервис не должен зависеть от абстракции (добавлять данные обратно в репозиторий), которую он не использует. - person rexcfnghk   schedule 07.07.2017