Comments 14
Вообще-то, обычно в Unity регистрируют не экземпляры, а типы или фабрики — как раз чтобы тяжелые сервисы инициализировались по месту.
Ваш подход плох тем, что (а) вы сразу оперируете экземплярами и (б) вы выносите часть логики наружу компонента. А вдруг инициализация компонента перестала быть потокобезопасной? А вдруг операции инициализации, на самом деле, не приносят выигрыша при параллельном запуске? Или у вас появилась дополнительная зависимость, усложняющая граф?
Ваш подход плох тем, что (а) вы сразу оперируете экземплярами и (б) вы выносите часть логики наружу компонента. А вдруг инициализация компонента перестала быть потокобезопасной? А вдруг операции инициализации, на самом деле, не приносят выигрыша при параллельном запуске? Или у вас появилась дополнительная зависимость, усложняющая граф?
0
Как я упоминал в самом начале, для моих задач (бекенд за балансировщиком) мне выгоднее провести инициализацию всего тяжелого именно на старте. Я не хочу увеличивать время отклика первых пришедших пользователей. Поэтому стараюсь на старте загрузить по-максимуму, а отложенную инициализацию использую очень осторожно.
Это также помогает диагностике. Потому что приложение в случае проблем упадет прямо здесь и сейчас в момент запуска, когда администратор занят непосредственно запуском. А не через несколько часов, когда кто-то из клиентов вызовет крайне редкоиспользуемый метод.
Наверное, следовало все-таки вынести в статью, почему я предпочитаю явную инициализацию экземпляров вместо регистрации типов.
Главная причина — это понятность и читаемость кода. Когда открываешь сравнительно простой чужой проект с точки зрения бизнес-логики и видишь в инициализации IoC-контейнера адскую мешанину из многоэтажных конструкций RegisterType, Resolve и InjectionConstructor, то… тоска и желание закрыть это и больше никогда не открывать. Очень тяжело разобраться сходу какая реализация интерфейса будет использоваться в качестве зависимости (реализаций часто несколько), какой конструктор вызван и т.д. Совсем другое дело, когда зависимости компонентов прописаны явно, а не «размазаны» по коду.
Несомненно, бывают случаи, когда нам нужен каждый раз новый экземпляр компонента. Я в этом случае предпочитаю регистрировать фабрику компонентов (как инстанс) с кастомной логикой (которую всё-равно писать вручную), и использовать получение компонента через эту фабрику.
В случае необходимости отложенной инициализации (как я уже упоминал, для моих задач это редкость) можно попробовать использовать Lazy<>, причем в функторе инициализации придется использовать heavyComponentXTask.Result вместо await heavyComponentXTask
Потокобезопасность при инициализацией экземпляров компонентов в общем не должна нас волновать, т.к. у нас регистрируется либо единственный экземпляр либо фабрика, которая при необходимости возьмет задачи по синхронизации контекста на себя.
В случае, если операции инициализации не приносят выигрыша при параллельном запуске (видимо инициализация происходит практически мгновенно), то инициализация не принесет и значительного проигрыша. Пара миллисекунд на создание обертки в виде задачи над инициализацией компонента — это совсем небольшой оверхед.
А с зависимостями, усложняющими граф самое вкусное. Достаточно описать задачу инициализации зависимости выше по коду чем задачи, где эта зависимость упоминается. Дальше задачи выстроятся в очередь планировщиком за счет использования await «автоматически».
Циклические зависимости компонентов друг от друга я не рассматриваю, т.к. это явные архитектурные проблемы.
Это также помогает диагностике. Потому что приложение в случае проблем упадет прямо здесь и сейчас в момент запуска, когда администратор занят непосредственно запуском. А не через несколько часов, когда кто-то из клиентов вызовет крайне редкоиспользуемый метод.
Наверное, следовало все-таки вынести в статью, почему я предпочитаю явную инициализацию экземпляров вместо регистрации типов.
Главная причина — это понятность и читаемость кода. Когда открываешь сравнительно простой чужой проект с точки зрения бизнес-логики и видишь в инициализации IoC-контейнера адскую мешанину из многоэтажных конструкций RegisterType, Resolve и InjectionConstructor, то… тоска и желание закрыть это и больше никогда не открывать. Очень тяжело разобраться сходу какая реализация интерфейса будет использоваться в качестве зависимости (реализаций часто несколько), какой конструктор вызван и т.д. Совсем другое дело, когда зависимости компонентов прописаны явно, а не «размазаны» по коду.
Несомненно, бывают случаи, когда нам нужен каждый раз новый экземпляр компонента. Я в этом случае предпочитаю регистрировать фабрику компонентов (как инстанс) с кастомной логикой (которую всё-равно писать вручную), и использовать получение компонента через эту фабрику.
В случае необходимости отложенной инициализации (как я уже упоминал, для моих задач это редкость) можно попробовать использовать Lazy<>, причем в функторе инициализации придется использовать heavyComponentXTask.Result вместо await heavyComponentXTask
Потокобезопасность при инициализацией экземпляров компонентов в общем не должна нас волновать, т.к. у нас регистрируется либо единственный экземпляр либо фабрика, которая при необходимости возьмет задачи по синхронизации контекста на себя.
В случае, если операции инициализации не приносят выигрыша при параллельном запуске (видимо инициализация происходит практически мгновенно), то инициализация не принесет и значительного проигрыша. Пара миллисекунд на создание обертки в виде задачи над инициализацией компонента — это совсем небольшой оверхед.
А с зависимостями, усложняющими граф самое вкусное. Достаточно описать задачу инициализации зависимости выше по коду чем задачи, где эта зависимость упоминается. Дальше задачи выстроятся в очередь планировщиком за счет использования await «автоматически».
Циклические зависимости компонентов друг от друга я не рассматриваю, т.к. это явные архитектурные проблемы.
0
Я вот только не понимаю, зачем вам вообще IoC-контейнер?
(отсутствие выигрыша при параллельном запуске легко может объясняться, например, тем, что у вас все обращения идут к одной БД, и она не справляется)
Что касается графа зависимостей: в вашем случае *нужно* знать порядок инициализации, в то время как при нормальном использовании контейнера он определяет его сам.
(отсутствие выигрыша при параллельном запуске легко может объясняться, например, тем, что у вас все обращения идут к одной БД, и она не справляется)
Что касается графа зависимостей: в вашем случае *нужно* знать порядок инициализации, в то время как при нормальном использовании контейнера он определяет его сам.
0
отсутствие выигрыша при параллельном запуске легко может объясняться, например, тем, что у вас все обращения идут к одной БД, и она не справляетсяЕсли асинхронная инициализация отрицательно влияет на производительность, никто ведь не мешает часть компонентов проинициализировать по классической, синхронной схеме. Еще в рамках тюнинга конкретной ситуации можно внутри задачи someTask в самом начале явно прописать await someOtherTask — заставляя someOtherTask завершится до инициализации someTask, что в вашем случае положительно повлияет на производительность.
Я старался решить проблему ускорения загрузки приложений «в общем». Но вышло это ценой расположения регистраций по-порядку в коде (почему многие считают, что это минус?) и регистрации готовых инициализированных инстансов вместо типов (что в моем случае явный плюс, но тоже многие коллеги не согласны).
0
Регистрация готовых инстансов вместо типов приводит к тому, что у вас из всех вариантов жизненного цикла остается синглтон.
Регистрация «выше по коду» приводит к тому, что вы управляете графом зависимостей вручную.
Сочетание этих факторов приводит к тому, что по факту вы не используете практически ничего из возможностей из контейнера, превращая вашу реализацию в poor man's DI.
Регистрация «выше по коду» приводит к тому, что вы управляете графом зависимостей вручную.
Сочетание этих факторов приводит к тому, что по факту вы не используете практически ничего из возможностей из контейнера, превращая вашу реализацию в poor man's DI.
0
Для тяжелых компонентов — синглтон практически единственный сценарий использования. Для всего остального ничто не мешает использовать IoC классически.
0
del.
+1
Я бы убрал добавление экземпляров в контейнер из метода инициализации и перенес его в метод FinishRegistrationTasks, значительно упростив логику и убрав синхронизацию.
0
Асинхронная регистрация — это довольно странный подход. Особенно, если решается задача асинхронной инициализации компонент. Почему бы не зарегистрировать компоненты отдельно, а уже затем в подходящий момент срезолвить все нужные компоненты и инициализировать их? В этом случае метод Initialize будет возвращать Task, компоненты можно срезолвить при старте приложения в нужном порядке, и вызвать соответствующие методы.
Плюсы:
1) мухи и котлеты будут отдельно — регистрация зависимостей занимается только своим делом.
2) инициализация будет асинхронной и за асинхронность отвечает сам компонент. Это чуть более правильно, так как Task — это не обязательно поток, вполне возможно, что внутри компонента происходят IO операции, которые можно запустить асинхронно.
Это факт, что Unity имеет чрезмерно усложненный API, но здравомыслящие программисты обычно оборачивают его в нечто более понятное. Более того, если регистрация компонент сложна, состоит из каких-то хитро-закрученных кастомных регистраций, и компоненты имеют по двум-трем конструкторам, то это вероятнее всего сигнализирует о проблемах в архитектуре. Я работал с очень сложными проектами, где вся регистрация сводится по сути к чему-то вроде container.RegisterSingleton<SomeService, ISomeService>() или container.RegisterPerRequest<SomeService, ISomeService>().
Плюсы:
1) мухи и котлеты будут отдельно — регистрация зависимостей занимается только своим делом.
2) инициализация будет асинхронной и за асинхронность отвечает сам компонент. Это чуть более правильно, так как Task — это не обязательно поток, вполне возможно, что внутри компонента происходят IO операции, которые можно запустить асинхронно.
Когда открываешь сравнительно простой чужой проект с точки зрения бизнес-логики и видишь в инициализации IoC-контейнера адскую мешанину из многоэтажных конструкций RegisterType, Resolve и InjectionConstructor, то… тоска и желание закрыть это и больше никогда не открывать. Очень тяжело разобраться сходу какая реализация интерфейса будет использоваться в качестве зависимости (реализаций часто несколько), какой конструктор вызван и т.д.
Это факт, что Unity имеет чрезмерно усложненный API, но здравомыслящие программисты обычно оборачивают его в нечто более понятное. Более того, если регистрация компонент сложна, состоит из каких-то хитро-закрученных кастомных регистраций, и компоненты имеют по двум-трем конструкторам, то это вероятнее всего сигнализирует о проблемах в архитектуре. Я работал с очень сложными проектами, где вся регистрация сводится по сути к чему-то вроде container.RegisterSingleton<SomeService, ISomeService>() или container.RegisterPerRequest<SomeService, ISomeService>().
+1
Это хороший подход, мне уже говорили некоторые коллеги, что он более правилен архитектурно. Но он и рассчитан на более хорошую кодовую базу, которая имеется не всегда. Зачастую имеется куча кода (например легаси), который, в общем, не знает об асинхронности, потому что писался 5 лет назад, у которого вся инициализация в конструкторе, который выполняется секунд эдак 30, причем в качестве параметра ему предварительно надо вычислить текущую фазу луны. К нему надо писать дополнительный слой абстракции. Это всё, конечно, крайние случаи, но они показывают что в моем коде разобраться будет, в общем, проще, т.к. вся инициализация от создания до регистрации в контейнере — в одном месте.
Кроме того, непроинициализированный компонент в IoC-контейнере — потенциально опасное место.
Кроме того, непроинициализированный компонент в IoC-контейнере — потенциально опасное место.
0
до того, как нода попадет в балансировщик нагрузки
Если это как-то контроллируется, т.е. если нода не попадает в балансировщик, пока всё окончательно не проинициализируется, то ради чего усложнять? Какая разница, когда она попадёт в балансировщик (если, конечно, речь не о десятках минут инициализации)?
0
Sign up to leave a comment.
Асинхронная инициализация компонентов