Я — Михаил Гурбанов, тимлид и разработчик.
Здесь я пишу о мире разработки: от Python и инструментов для бэкэнда и ИИ до фронта с DevOps.
Делюсь опытом из практики, и заметками со спикерских выступлений
Tg: @mygurbanov
Github: https://github.com/insani7y
Post #22
535
В Python долго считалось, что DI - штука чужая.
Не нужен нам контейнер, не нужны провайдеры: «создам объект в конструкторе - и поехали».
Это действительно работает… пока у тебя не появляется десяток сервисов, клиенты, кеши, пулы и фоновые задачи.
И вот уже каждый новый объект тянет за собой ещё пару зависимостей - и слой, который должен быть простым, превращается в хаос.
Чтобы этот хаос остановить, и существуют DI-библиотеки.
Мы у себя используем that-depends - она ближе всего нам по синтаксису и ощущению.
Проект, по сути, вырос после того, как популярный dependency-injector не стал поддерживать Python 3.12 (сейчас они поддержали, но уже поздно!!), и мы внезапно остались без удобного DI под современный стек.
Самое приятное в that-depends - он не ломает привычный стиль разработки и не требует изучать «новый мир».
Он просто позволяет декларативно описать, как создаются зависимости, и хранить всё это в одном месте.
Сервис-слой перестаёт расползаться, а тесты перестают превращаться в мококарнавал.
🔹 Пример - асинхронный ресурс
Жизненный цикл ресурса определён контейнером.
Бизнес-логика пользуется пулом, не думая о его устройстве.
В тестах Container.db можно заменить одной строкой - без танцев.
🔹 Пример из реальной жизни
Не нужно протаскивать зависимости через пять уровней конструкторов.
Не нужно хранить singletons в модулях.
Не нужно вручную собирать граф объектов - контейнер делает это сам.
И вот за это подход и нравится:
все зависимости лежат в одном месте, ими удобно управлять, легко подменять и переиспользовать.
Код становится чище, архитектура - прозрачнее, тесты - проще.
that-depends, конечно, не единственный DI в экосистеме.
Но это тот вариант, который ощущается по-питоновски: лёгкий, понятный, без магии и лишнего шума - и при этом действительно полезный в живых проектах.
GitHub GitHub - modern-python/that-depends: Simple, typed dependency-injection framework for Python Simple, typed dependency-injection framework for Python - modern-python/that-depends Не нужен нам контейнер, не нужны провайдеры: «создам объект в конструкторе - и поехали».
Это действительно работает… пока у тебя не появляется десяток сервисов, клиенты, кеши, пулы и фоновые задачи.
И вот уже каждый новый объект тянет за собой ещё пару зависимостей - и слой, который должен быть простым, превращается в хаос.
Чтобы этот хаос остановить, и существуют DI-библиотеки.
Мы у себя используем that-depends - она ближе всего нам по синтаксису и ощущению.
Проект, по сути, вырос после того, как популярный dependency-injector не стал поддерживать Python 3.12 (сейчас они поддержали, но уже поздно!!), и мы внезапно остались без удобного DI под современный стек.
Самое приятное в that-depends - он не ломает привычный стиль разработки и не требует изучать «новый мир».
Он просто позволяет декларативно описать, как создаются зависимости, и хранить всё это в одном месте.
Сервис-слой перестаёт расползаться, а тесты перестают превращаться в мококарнавал.
🔹 Пример - асинхронный ресурс
from that_depends import BaseContainer, providers, inject, Provide
async def create_pool():
pool = await make_pool()
try:
yield pool
finally:
await pool.close()
class Container(BaseContainer):
db = providers.Resource(create_pool)
@inject
async def handle_user(pool = Provide[Container.db]):
async with pool.acquire() as conn:
return await conn.fetch("SELECT 1")
Жизненный цикл ресурса определён контейнером.
Бизнес-логика пользуется пулом, не думая о его устройстве.
В тестах Container.db можно заменить одной строкой - без танцев.
🔹 Пример из реальной жизни
class Config:
url = "https://api.example.com"
class Client:
def __init__(self, config: Config):
self.config = config
class Service:
def __init__(self, client: Client):
self.client = client
class Container(BaseContainer):
config = providers.Singleton(Config)
client = providers.Factory(Client, config.cast)
service = providers.Factory(Service, client.cast)
# готовый сервис со всеми зависимостями
svc = Container.service.resolve_sync()
Не нужно протаскивать зависимости через пять уровней конструкторов.
Не нужно хранить singletons в модулях.
Не нужно вручную собирать граф объектов - контейнер делает это сам.
И вот за это подход и нравится:
все зависимости лежат в одном месте, ими удобно управлять, легко подменять и переиспользовать.
Код становится чище, архитектура - прозрачнее, тесты - проще.
that-depends, конечно, не единственный DI в экосистеме.
Но это тот вариант, который ощущается по-питоновски: лёгкий, понятный, без магии и лишнего шума - и при этом действительно полезный в живых проектах.
- 👍 12
- 🔥 5
- 🤯 1

