Tishka17 @Tishka17
Пользователь
Information
- Rating
- 3,887-th
- Location
- Москва, Москва и Московская обл., Россия
- Date of birth
- Registered
- Activity
Specialization
Backend Developer, Mobile Application Developer
Lead
Python
Docker
Linux
SQL
Git
Golang
Android SDK
это не интерфейс, это невнятный огрызок. Интерфейс нужен чтобы зафиксировать требования к реализации, а у вас тут kwargs торчит.
Так базовые вещи или детали реализации, определитесь. Базовая модель данных - звучит как кусок "домена"/слоя "entites" в то время как клиент redis - кусок конкретной части инфраструктуры, а настройки логгерыов это вообще мейн
CRUD операции существуют на всех слоях приложения, с точки зрения архитектуры бессмысленный набор слов. Хотите выделить слой - выделите слой по зоне ответсвенности/уровню абстракции, а не по наличию там 4 типичных операций. CRUD может быть в DAO/Репозитории, может быть в HTTP клиете, могут быть интеракторы обслуживающие этот круд, а могут быть хэндлеры слоя представления. Если у вас нет бизнес логики - не пытайтесь выдавить из себя её, сделайте один слой presentation (назовите api, views или ещё как) и расслабьтесь. Как только появится что-то сложнее одного вызова шлюза БД - выделяйте уже сервисный слой по всем правилам.
это какие такие утилиты? почти в каждом проекте где такой пакет есть - это свлака кусков с совершенно разной зонов ответственности. Первое время такое делать точно не надо, выделяйте общие куски внутри слоев и называйте их нормально, utils сам по себе родится из хаоса, но старайтесь как можно дольше обходиться без этого
Возьмём вкбсокет. У вас есть цикл обработки сообщений в нем. Есть объект который должен быть доступен все время доступности вкбсокет (не знаю, адаптер для отправки в него структурированных данных), а вот на каждое сообщение мы хотим временный скоуп иметь, даже если они в одной таске последовательно обрабатываются.
Раз я тут в этом треде уже несколько раз говорил про скоупы, приложу своё видео где я про это немного говорил http://www.youtube.com/watch?v=gWOBaZ3I4gc
От того что C стала контекстным менеджером, A и B им не станет сам и фабрика А тоже. Это надо написать. В подходе с dishka у вас топ левел всегда контекст, а внутри он уже разрулит где юзать конеткст, а где просто создавать. Небольшая унификация, можно сделать самому.
Нет, в случае с контейнером вы напишете другую фабрику для конкретной зависимости и всё остальное оставите как было. А контейнер сам соберет ваш composition root из того что в данной конфигурации сделано.
Есть проблема когда объекту А нужен объект Б, которому нужен объект С, который контекстный менеджер, а вам нужен именно А. Приходится через всю ирерахию протаскивать
__enter__
/__exit__
Я не предлагаю втаскивать контейнер туда где не надо. Я предлагаю посмотреть, на проект где есть хотя бы десятка три фабрик и сравнить с тем что получится с контейнером. Если помогает - берем, если не помогает, значит не надо. В любом случае хороший контейнер существует виден только на границе скоупа, там где мы и так что-то дергаем.
Контейнер имеет смысл только в интеграционных тестах (то есть там где уже всё сложно), в обычных тестах я пишу так как вы сказали - просто передаю мок/фейк.
Уточню, что я не считаю IoC-контейнер must-have инструментом, без которого приложение жить не может. Это вспомогательная штука для создания вот таких фабрик. Его прелесть раскрывается когда фабрик становится много и среди них начинают встречаться контекстные менеджеры или какой-нибудь объект меняет скоуп. Унифицированный подход упрощает работу с этим. Но опять же, можно сделать вручную. Я много лет так и жил и до сих пор некоторые проекты так вполне комфортно работают.
Конечно же полностью отказаться от ручного написания фабрик нельзя. Идея в том, чтобы не писать их там, где не требуется. Если мы можем связать большую часть приложения на основе типов и явных деклараций "протокола-используемая реализация" (а ещё до использования проверить что нет противоречий в графе) - это уже неплохо. Если надо написать несколько фабрик вручную - напишем только их, это не противоречит концепции.
Более того, моя позиция в том, что почти всё приложение не должно знать о существовании контейнера. Да и контейнер у меня сделан максимально тупо, чтобы с минимальными изменениями можно было переехать на ручные фабрики и обратно.
Context vars - не лучшее, что можно придумать. Во-первых, они неявно копируются, во-вторых, скоуп не всегда совпадает с асинк таской. Я предпочитаю явное использование контекстного менеджера.
Да, стало. Теперь чтобы создать 10 объектов и прокинуть куда надо какие надо с переисползованием мне нужно писать 20+ строк фабрик и потом ещё неизвестно сколько чтобы в тестах переопределять. Я просто регистрирую классы, в простом случае по одной строке на класс. Если где-то появляется контекстный менеджер, мне снова не надо тащить его по всей иерархии. Меняется только 3 строки, а его использующие классы больше не должны волноваться о пробросе exit.
Такая проблема действительно есть. Частично она решается тем, что контейнер не используется напрямую, а везде используетсч
inject
и конкретные типы завимостей. Это работает только на обработчиках фреймворков, так что риск что "любая часть кода" полезет куда не надо - сильно меньше. Мы хотим тут тоже проверять доступность, но пока нет действительно хороших идейВ норме вся механика DI используется за счёт constructor injection, а контейнер - лишь способ получить временный объект в обработчике, на границе его жизни.
Ну вот поэтому у меня в dishka всё полагается на типы и есть достаточно строгая валидация графа
Делают. По классике есть зависимости типа "singletone", а есть"scoped". Если у вашего объекта есть Стейт, который не должен шариться между обработчиками запроса (банально, Коннект к бд) - это scoped зависимость и её надо бы пересоздавать (оптимизации приемлемы, но концептуально, один инстанс не должен одновременно использоваться)
Моя позиция: можно и нужно. А вот когда зависимостей станет много и следить за "скоупами" станет лень, можно брать контейнер. Такой, который по минимуму влезает в существующий код
Я не знаю стандартного определения, это моё описание того что подразумевают под такими инструментами и мой опыт. Когда я пилил dishka я опирался на свой опыт использования "плохих ioc-контейнеров" и у меня вышло то что мне нравится. Если не углубляться в детали, то для использования надо всего 3-4 вызова функций, просто не таких как у вас и тоже не таких как у аналогов.
Уже после релиза dishka мне дали почитать книгу "Внедрение зависимостей в .Net" Марка Симана. Могу порекомендовать для ознакомления, она во многом передает мои представления, хотя по сравнению с ней, я несколько переосмыслил, например, работу со скоупами.
если глобалов 2 - это всё ещё глобалы
Раз уж зашла речь о контейнерахв питоне, я обязан высказаться.
Dependency injection это полезный паттерн, который говорит что мы не должны сами брать зависимости ни из глобальной переменной, ни создавать по месту. Если мы просто делаем глобальный сторадж, который предоставляет доступ к куче разных объектов - это паттерн SericeLocator, к DI он не имеет отношения. DI-фреймворк (IoC-контейнер) может быть похож на него если юзать через глобал, но это не обязательно делать так.
Что же делает Ioc-container? Во-первых, умеет создавать сложную иерархию объектов, передавая одни в другие - собственно реализуя DI. Во-вторых, понимает где надо создать новый объект, а где переиспользовать старый, то есть имеет некоторые скоупы. Хороший контейнер умеет чуть больше: требует минимального вмешательства в сами объекты, умеет в финализацию, изолированные куски графа зависимостей, позднее связывание зависимостей. Я для себя формулировал такой список требований: https://dishka.readthedocs.io/en/stable/requirements/technical.html
Как обращаться к контейнеру? Тут могут быть варианты. В общем случае, он не должен быть фиксированно в одном экземпляре. У нас так же должна быть возможность подменить граф зависимостей для тестов. Я считаю самым удачным решением - использовать возможности фреймворков для передачи инстанса контейнера в конченые обработчики, но это не всегда возможно.
Какие могут быть подводные камни при разработке? Во-первых, сделать удобное api. Для обозначения разных объектов достаточно нативно используют типы. Использовать названия для всех сущностей достаточно сложно, так как среди них могут быть одноимённые и вообще следить за уникальностью имен по большомц проекту нереально. Во-вторых, скоупы можно реализовать по разному, но в любом случае нужен их контроль и иерархия. Я считаю что число скоупов должно быть произвольным, но некоторые разработчики начинают с классических 3, упуская, что уже в классических же либах их стало больше. В-третьих, мне кажется важным чтобы граф зависимостей можно было отлаживать. То есть чтобы ошибки были понятные, чтобы был полный контроль за ним.
Могу предложить посмотреть свой проект dishka, про него уже было несколько статей на Хабре. Там мы постарались сделать очень простое API, реаилзововав всё выше названное и даже больше.
Это не просто похоже на глобалы. Это и есть глобальный контейнер. Это будет работать пока не понадобится в тестах или модульном монолите иметь несколько контейнеров
Я думаю эта ссылка обязана быть в контексте такой статьи: https://github.com/ag2ai/faststream/
Наследование - полезный инструмент, просто не надо его тащить везде.
Полиморфизм подтипов реализуется через подтипы. Это не обязательно наследование. Интерфейс - это тоже тип, но в некоторых языках для реализации интерфейса надо отнаследоваться от него, в некоторых - указать что вы ег ореализовали, а в третьих просто реализовать и механика языка сама проверит соответствие безо всяких наследований
Ерунда, авторы изначально говорят о том что композиция лучше наследования в большинстве случаев. На наследовании, если мне не изменяет память, основаны только шаблонный метод и парный к нему фабричный метод. Всё остальное - про реализацию интерфейсов и вообще говоря применимо не только к ООП
А ещё есть отличные полезные проверки типа удаления неиспользуемых имортов, которые ломают сгенрированный grpc код, но это уже камень в сторону grpc
Есть очень сомнительные правила вроде требования включать "if TYPE_CHECKING" которые могут сломать рантайм, хотя тайпчекер будет думать что всё ок.