Недавно в этом посте вы познакомились с библиотекой Pure.DI. Этот пакет с анализатором/генератором кода .NET 5 задумывался как помощник, который пишет простой код для композиции объектов в стиле чистого DI, используя подсказки для построения графа зависимостей. Он следит за изменениями, анализирует типы и зависимости между ними, подсвечивает проблемы и предлагает пути решения. Важно отметить, что библиотека Pure.DI - это не контейнер внедрения зависимостей, в её задачи входит:
анализ графа зависимостей
определение в нем проблем и путей их решения
создание эффективного кода для композиции объектов
По обсуждениям в предыдущем посте у меня сложилось впечатление, что необходимо решить следующие вопросы:
добавить возможность использовать Pure.DI в инфраструктуре ASP.NET
убрать бинарную зависимость на API из пакета Pure.DI.Contracts
увеличить производительность для случаев, когда операция
Resolve()
выполняется многократно
Сейчас, после небольших доработок анализатор кода автоматически определяет, является ли проект ASP.NET проектом, и генерирует код специального метода расширения, который обеспечивает интеграцию с ASP.NET. Для демонстрации возможности выбрано серверное Blazor приложение:
Описание графа зависимостей находится в этом классе:
DI.Setup()
.Bind<IDispatcher>().As(Singleton).To<Dispatcher>()
.Bind<IClockViewModel>().To<ClockViewModel>()
.Bind<ITimer>().As(Scoped).To(_ => new Timer(TimeSpan.FromSeconds(1)))
.Bind<IClock>().As(ContainerSingleton).To<SystemClock>();
Для того чтобы связать эти DI типы с инфраструктурой ASP.NET нужно добавить всего лишь одну строку вызова метода расширения:
services.AddClockDomain();
Как видно из примера, его название зависит от имени класса-владельца, чтобы не путаться если их несколько. Помимо метода расширения, добавлены два времени жизни:
ContainerSingleton - чтобы использовать один объект типа на ASP.NET контейнер
Scoped - чтобы использовать по одному объекту типа на каждый ASP.NET scope
Сейчас их нельзя использовать вне контекста ASP.NET, иначе появится ошибка компиляции с информацией об этом.
Для решения вопроса о нежелательной бинарной зависимости на API я “удалил” пакет Pure.DI.Contracts. Теперь весь API для описания графа зависимостей генерируются “на месте” и является частью инфраструктурного кода проекта, где этот API и используется. Как итог, в проекты не добавляется ни одной бинарной зависимости, а единственная зависимость типа analyzers на пакет Pure.DI будет использована только во время компиляции и забыта сразу после. И, конечно, ее можно использовать без ограничения в любых проектах, не опасаясь “зависеть от чего-то лишнего”.
ASP.NET инфраструктура вызывает метод Resolve()
для каждого запроса. Чтобы уменьшить накладные расходы на этот вызов, был оптимизирован код, ответственный за сопоставление типа корневого элемента композиции объектов к методу создания этой композиции. С результатами сравнительных тестов можно ознакомиться здесь. Хотелось бы подчеркнуть, что в этом сравнении используется спорный способ получения показателей производительности. Поэтому, эти результаты, дают приблизительную оценку накладных расходов на многократный вызов метода Resolve()
.
Как обычно, любые конструктивные замечания и идеи очень приветствуются.
lair
Покажите, пожалуйста, какой код генерится, особенно в случае, когда добавленная вами зависимость хочет что-то из сервисов, зарегистрированных в контейнере.
NikolayPyanikov Автор
lair
Ээээ.
Можете случайно получить много смешных багов. Да, очень маловероятно, прямо очень. Но не надо так все равно.
Ну и не очень понятно, зачем вы что-то делаете с MVC-шными сервисами, будем честными. Это невежливо по отношению к пользователю контейнера.
Короче говоря, как я и боялся: получившийся код очень тяжело читать. Понять, что в нем пошло не так… более чем рискованно.
lair
Я, причем, не очень понимаю, что вам мешает сделать "резолвер" для каждого "вашего" сервиса в виде
Func<IServiceProvider,T>
, и просто передавать сервис-провайдер вниз по цепочке. Грубо говоря, вот так:Я, конечно, из головы писал, но не очень сходу вижу, какие у такого подхода проблемы.
lair
Тут, на самом деле, должно быть хитрее, и если зависимости
Scoped
илиSingleton
, то надо тоже вызыватьsp.GetRequiredService<T>
, аFactory
вызывать только если временем жизни зависимости управляет не контейнер. Но это вполне очевидные (и просто реализуемые) мелочи.