Интересно знает ли автор о двух других DI-IoC имплементациях на сорс генераторах:
Да я случайно наткнулся на них когда делал генерацию кода :) Но идея немного другая но похожая.
По поводу резолва графа только один раз в реальных приложениях и бесполезности бенчмарка миллиона резолвов, это наверное не так, если вы резолвите для unit-of-work, например для веб реквеста, сессии, транзакции и т.д., что похоже на реальный мир и может потребовать миллионов резолвов.
В этом случае не нужен резолв каждый раз. Просто внедрите «фабрику». Вызов Resolve много раз в раных местах — это сервис локатор
А это тогда что: public static void Main() => Glue.Resolve().Run();?
Что делает Resolve? Если судить по коду выше — похоже, что принимает параметр-тип Program и создает объект, реализующий этот тип.
Метод Resolve() создает композицию с рутом типа T.
который передан параметром-типом (а конструктор — сгенерить на этапе компиляции) но интерфейс-то так не создашь
И почему интерфес не создашь?… это же генератор кода
Я не очень понимаю ваши сомнения. Классическихе IoC библиотеки = рантайм логика + рефлекшн. Библиотеки как Pure.DI = синтаксические деревья вашего кода + семантическая модель вашего кода и всех его зависимостей, со всей инфой как в рефлекшене + рантайм логика, которую можно легко добавить в ваш проект + рефлекшн если нужно, но зачем, и все что угодно.
Представьте, что у вас нет контейнеров, а есть просто набор типов в DI стиле. Как вы поступите в этой ситуации? Pure DI сейчас сделает за вас рутинную работу, проверит граф и напишет код композиции, на этом, пока, вся его зона ответственности.
Но если рассматривать эту ситуацию как идею для новой фичи, то можно добавить API чтобы Pure DI используя, например, полные имена типов создавал эти объекты этих типов рефлекшеном и лямбда функциями:
.Bind<IFacade>().To(“Abc.Façade, myassembly”)
Ну и да, я все-таки не понимаю: так кто же поставляет эти метаданные, автор компонента или потребитель?
Ну вот вы пользуетесь библиотекой что бы решить этот вопрос, кто и что ей поставляет? Здесь тоже самое
Как вариант на этапе или до компиляции получил бы метаданные для построения графа зависимостей стороннего компонента. Как? Можно скажем написать .NET tool который это сделает и сложит все в JSON заранее, ведь эти данные не изменятся. Генератор может это и сам сделать – это же обычный .NET процесс, но нужно ли? Собственно и хотелось собрать ваши идеи. А далее все как обычно, добавил или переопределил привязки.
Мне понравилась идея с <ТТ> — в отличие от Bind(typeof(IBox< >)).To(typeof(MyBox<>)) здесь компилятор сможет проверить, что MyBox реализует IBox, если я правильно понял.
Да все верно.
Но что касается производительности, мне действительно трудно представить кейс, когда resolve ощутимо сказывается на перфомансе. Коллега пару лет назад тестировал несколько библиотек (Ninject, Autofac, MS DI, если правильно помню) и пришёл к выводу, что разрешение даже огромного графа зависимостей занимает довольно ничтожное время, сравнение по этому критерию не имеет смысла. Если у вас есть данные в подтверждение противоположной точки зрения, будет любопытно взглянуть.
Во многом я соглашусь с вами.
Новые объекты в приложении могут появляется постоянно на протяжении всего времени его работы и их может быть очень много, они появляются и исчезают непрерывно с большой скоростью. Приложение может работать месяцами.
Скорость зависит от способа, которым граф резолвится. Продвинутые библиотеки внедрения компилируют лямда функции для того, чтобы его собрать и тогда сам процесс композиции действительно быстрый, но, чтобы скомпилировать лямбду тоже нужно время. Я бы не сказал, что Ninject, Autofac относятся к продвинутым в плане производительности, а в MS DI все хорошо. Вот тут я делал замеры. Понятно эти все замеры плохо отражают реальность, но какое-то представление о потерях дают.
Под «мощью» DI я имел ввиду количество фич. Под сложностью, в этом конкретном случае, имелась ввиду сложность конструирования. Композиция, дженерики, иньекции не только в конструктор итд.
На мой взгляд в реальном приложении все бывает гораздо сложнее, чем можно вообразить: пятиэтажные дженерики, внедрения куда попало, например, чтобы избавиться от циклических зависимостей и другие странные вещи. Не все же люди гении и сразу пишут простой код “тот который не написан”.
Программирование это не только поиск и применение «клевых» решений, попытки попасть в практичность продукта, но и работа со сложностью в общем ее смысле. Если за ней не следить, то потом никто не сможет понять проект (и назовет его легаси, который невозможно поддерживать). Работать стоит надо не над «мощью» инструментов, а над уменьшения сложности кода. Это экономит на разработку «мощных» инструментов.
Идеальный код — тот который не написан.
Я с вами согласен. Я думаю, что кодинг — это итеративный процесс. Код усложняется, когда добавляются новый фичи, потом упрощается, когда его рефакторят. Вот такой «маятник».
… вот как раз на то, как Решарпер на таких проектах справляется, я и любуюсь, да.
Когда слишком много проектов и кода может быть имеет смысл использовать бинарные зависимости.
Вот только «вручную» я не могу (точнее могу, но код будет уродлив) объявить в одном проекте реализацию зависимости, которую использовать в другом проекте динамически (подложили сборку в бин — используется, не подложили — не используется). А еще я не могу предложить потребителям зависимость, реализация которой им при этом недоступна (интерфейс — public, реализация — internal). Значит и ваш «помощник» не сможет.
Я смутно понимаю задачу. Это что то похожее на плагины, которые автоматом подтягиваются из папки и internal типы загружаются рефлекшеном? И это ответсвенность IoC/DI? То-есть контейнер ходит по файловой системе? Я не хочу углублятся в этот вопрос, но да Pure DI не будет ходить по файловой системе, так как это слишком частный случай
Нет, реального приложения, которое бы использовало такой DI.
В случае Pure.DI нет библиотеки. Что мерить не очень понятно.
Я и пишу инфраструктуру. Достаточно сложно самому про себя не знать.
Под инфпаструкторой я имел ввиду инфраструктурный код, например код, хостинга приложения, логирование, метрики… и композиция
Может быть, и просто. Но пока у вас нет поддержки хотя бы сценария использования в ASP.NET Core / Generic Host, это выглядит академическим исследованием, а не прикладным кодом.
И это не потому, что я такой привередливый, а потому что я начинаю очередной реальный проект — ну не знаю там, хочу маленькую AWS Lambda с небольшим количеством компонентов — и мне нужно как минимум логирование и настройки. Я бы с удовольствием оперся на Microsoft.Extensions.Logging и Microsoft.Extensions.Options — но им для нормальной работы нужен Microsoft.Extensions.DependencyInjection. Подтащить его, очевидно, просто, но в этот момент возникает, все-таки, вопрос: что я выиграю от использования вашего (еще не написанного) решения по сравнению с базовой имплементацией в M.E.DI? Там даже скоупы есть — которых у вас, заметим, нет.
Ну еще и две недели не прошло как я подумал про этот проект и треть из этого времени проболел, так что все впереди :)
Насколько я понял из статьи, это все compile-time зависимости и в runtime они не поедут.
Один пакет кодогенерации это анализатор, который не добавляет зависимость
Пакет API обычный — там нет кода и он позволяет использовать свои атрибуты разметки из модулей предметной области что не добавить в них ни каких зависимостей
Ну если скажем добавить JSON с подсказками к построению графа зависимостей то и контракты будут не нужны
По мне так третий пункт наиболее оптимальный.
Единственное что, я бы хотел видеть сгенерированный код вживую, чтобы можно было подебажить и все такое. Насколько я понял, кодогенерация рослином, используемая тут, этого не даст. Поправьте, если не прав.
Можно увидеть результаты. Я вот тут же гифку прицепил, где редактирую код и одновременно с этим меняется код построения композиции
Хуже, потому что не имеет такого количества интеграций. Так зачем мне это использовать, если оно при этом не лучше?
Так я же не навязываю, не хотите не используйте.
… по сотне проектов с неявными зависимостями?
Ну Resharper как-то справляется, пока есть идея, и какая-то простенькая реализация
подложили сборку в бин — используется, не подложили — не используется
Уверен, что это не задача библиотек контейнеров. Например, некоторые люди хранят данные в формате ключ значение в Dictionary не потому, что когда-либо им понадобится быстрый поиск значения по ключу, а потому что … не знаю почему, лень было написать свой тип, или хотя бы тупл использовать и положить его в лист может быть
А еще я не могу предложить потребителям зависимость, реализация которой им при этом недоступна (интерфейс — public, реализация — internal).
Ну используйте InternalsVisibleTo или напишите код, который это делает и полагайтесь на него. Это как бы не задача контейнера
Бенчмарки в студию, на реальном проекте, чтобы было видно вклад в общую производительность. У нас вот в реальной системе накладные расходы на Autofac начинают быть заметными только тогда, когда мы ошибаемся (например, начинаем создавать зависимость тысячи раз за запрос).
Бенчмарки чего? Этого кода?
new CardboardBox<ICat>(
new ShroedingersCat(
new Lazy<State>(
new Func<State>(
(State)Indeterminacy.Next(2))))));
Если вы вызываете много раз Resolve из кода, на мой взгляд это не совсем DI. В идеале ваш код не должен знать про инфраструктуру
А как вы, если у вас нет контейнера, поддержите IServiceProvider?
Вариантов как сделать множество. Ведь там все достаточно просто. Хотелось бы выбрать правильный. Сейчас же поддерживаются BCL типы
Я как раз вчера добавил сюда github.com/danielpalme/IocPerformance :)
Но эти тесты не особо показательны, так как они по сути тестируют метод Resolve(), который создает композиции по 3 — 15 объектов, вызывая его миллионы раз. Тестируется работа той или иной реализации Dictionary<,>. А за без контейнерный вариает просто выдается еще какая то одна реализация Dictionary<,> :)
Я предполагаю, что создание композиции в идеальном случае выполняется один раз в приложении в самом начале его работы и она состоит из тысяч объектов. Иначе это сервис локатор какой то.
Еще плохо, что этот достаточно популярный репозиторий не использует github.com/dotnet/BenchmarkDotNet и собирает статистику весьма примитивными способами.
Не лучше и не хуже. Предполагается что это не библиотека внедрения зависимостей, там нет классического контейнера.
Представьте, что вы работаете над абстракциями и их реализациями некоторой предметной области. Например, вы добавляете новый параметр в конструктор какой-то реализации. А невидимый помощник тут же пытается построить граф зависимостей с учетом этого последнего изменения. Мгновенно проверяет валиден ли он, подсказывает что нужно поправить и подсвечивает код с проблемами и дает рекомендации.
Параллельно с вами это помощник создает код для композиции объектов вашей предметной области так, как если бы вы это делали сами вручную. Нет контейнеров, нет зависимостей на библиотеки кода, нет рефлексии типов. Просто код, который вы можете сами написать руками. Резюмируя:
1. Это банально сильно быстрее :)
2. Пока нет, но если будет интерес людей, то почему бы и нет
Да я случайно наткнулся на них когда делал генерацию кода :) Но идея немного другая но похожая.
В этом случае не нужен резолв каждый раз. Просто внедрите «фабрику». Вызов Resolve много раз в раных местах — это сервис локатор
Метод Resolve() создает композицию с рутом типа T.
И почему интерфес не создашь?… это же генератор кода
Я не очень понимаю ваши сомнения. Классическихе IoC библиотеки = рантайм логика + рефлекшн. Библиотеки как Pure.DI = синтаксические деревья вашего кода + семантическая модель вашего кода и всех его зависимостей, со всей инфой как в рефлекшене + рантайм логика, которую можно легко добавить в ваш проект + рефлекшн если нужно, но зачем, и все что угодно.
и мне кажется это неплохая мысль, но не все приложения web
Но если рассматривать эту ситуацию как идею для новой фичи, то можно добавить API чтобы Pure DI используя, например, полные имена типов создавал эти объекты этих типов рефлекшеном и лямбда функциями:
Ну вот вы пользуетесь библиотекой что бы решить этот вопрос, кто и что ей поставляет? Здесь тоже самое
На мой взгляд в реальном приложении все бывает гораздо сложнее, чем можно вообразить: пятиэтажные дженерики, внедрения куда попало, например, чтобы избавиться от циклических зависимостей и другие странные вещи. Не все же люди гении и сразу пишут простой код “тот который не написан”.
Я с вами согласен. Я думаю, что кодинг — это итеративный процесс. Код усложняется, когда добавляются новый фичи, потом упрощается, когда его рефакторят. Вот такой «маятник».
Когда слишком много проектов и кода может быть имеет смысл использовать бинарные зависимости.
Я смутно понимаю задачу. Это что то похожее на плагины, которые автоматом подтягиваются из папки и internal типы загружаются рефлекшеном? И это ответсвенность IoC/DI? То-есть контейнер ходит по файловой системе? Я не хочу углублятся в этот вопрос, но да Pure DI не будет ходить по файловой системе, так как это слишком частный случай
В случае Pure.DI нет библиотеки. Что мерить не очень понятно.
Под инфпаструкторой я имел ввиду инфраструктурный код, например код, хостинга приложения, логирование, метрики… и композиция
Ну еще и две недели не прошло как я подумал про этот проект и треть из этого времени проболел, так что все впереди :)
Один пакет кодогенерации это анализатор, который не добавляет зависимость
Пакет API обычный — там нет кода и он позволяет использовать свои атрибуты разметки из модулей предметной области что не добавить в них ни каких зависимостей
Ну если скажем добавить JSON с подсказками к построению графа зависимостей то и контракты будут не нужны
Можно увидеть результаты. Я вот тут же гифку прицепил, где редактирую код и одновременно с этим меняется код построения композиции
Так я же не навязываю, не хотите не используйте.
Ну Resharper как-то справляется, пока есть идея, и какая-то простенькая реализация
Уверен, что это не задача библиотек контейнеров. Например, некоторые люди хранят данные в формате ключ значение в Dictionary не потому, что когда-либо им понадобится быстрый поиск значения по ключу, а потому что … не знаю почему, лень было написать свой тип, или хотя бы тупл использовать и положить его в лист может быть
Ну используйте InternalsVisibleTo или напишите код, который это делает и полагайтесь на него. Это как бы не задача контейнера
Бенчмарки чего? Этого кода?
Если вы вызываете много раз Resolve из кода, на мой взгляд это не совсем DI. В идеале ваш код не должен знать про инфраструктуру
Вариантов как сделать множество. Ведь там все достаточно просто. Хотелось бы выбрать правильный. Сейчас же поддерживаются BCL типы
Но эти тесты не особо показательны, так как они по сути тестируют метод Resolve(), который создает композиции по 3 — 15 объектов, вызывая его миллионы раз. Тестируется работа той или иной реализации Dictionary<,>. А за без контейнерный вариает просто выдается еще какая то одна реализация Dictionary<,> :)
Я предполагаю, что создание композиции в идеальном случае выполняется один раз в приложении в самом начале его работы и она состоит из тысяч объектов. Иначе это сервис локатор какой то.
Еще плохо, что этот достаточно популярный репозиторий не использует github.com/dotnet/BenchmarkDotNet и собирает статистику весьма примитивными способами.
Представьте, что вы работаете над абстракциями и их реализациями некоторой предметной области. Например, вы добавляете новый параметр в конструктор какой-то реализации. А невидимый помощник тут же пытается построить граф зависимостей с учетом этого последнего изменения. Мгновенно проверяет валиден ли он, подсказывает что нужно поправить и подсвечивает код с проблемами и дает рекомендации.
Параллельно с вами это помощник создает код для композиции объектов вашей предметной области так, как если бы вы это делали сами вручную. Нет контейнеров, нет зависимостей на библиотеки кода, нет рефлексии типов. Просто код, который вы можете сами написать руками. Резюмируя:
1. Это банально сильно быстрее :)
2. Пока нет, но если будет интерес людей, то почему бы и нет
Вы предполагаете поймать в using и try/finally только OperationCanceledException?