Можно ли использовать dependency injection для подключения к базе данных и как это реализовать в Python?
Что проверяют
Понимаете ли вы, что DI в Python это прежде всего передача зависимости снаружи, а не обязательный контейнер. И умеете ли развести время жизни: пул соединений один на приложение, сессия одна на запрос. Следом обычно просят показать, как подменить базу в тестах.
Как отвечать
Да, это классический пример DI: класс или функция не создаёт соединение сама, а получает его параметром:
- конструктор:
UserRepository(session)вместоpsycopg.connect()внутри метода. Контракт удобно описать черезtyping.Protocol; - корень композиции: объекты собираются в одном месте, в
main()или фабрике приложения; - FastAPI
Depends: зависимость-генератор открывает сессию, отдаёт её черезyieldи закрывает после ответа. Эндпоинт объявляетsession: Annotated[AsyncSession, Depends(get_session)], а в тестах подмена делается черезapp.dependency_overrides[get_session]; - контейнеры:
dependency-injectorс провайдерамиSingletonиFactory,dishkaсо скоупами приложения и запроса,punq. Они окупаются на большом графе зависимостей, маленькому сервису хватает ручной передачи.
Отдельно проговорите время жизни. create_engine() или create_async_engine() в SQLAlchemy создаёт пул, и он должен быть один на процесс. Session или соединение из пула берётся на единицу работы и возвращается после неё. Если провайдер каждый раз отдаёт новый engine, вы получите по пулу на запрос и быстро упрётесь в лимит соединений базы.
Выигрыш в тестах: репозиторий подменяется фейком, а интеграционные тесты идут на отдельной базе через testcontainers.
Где спотыкаются
Делают соединение глобальной переменной модуля и называют это синглтоном. Такую зависимость не подменить без monkeypatch, а при форке воркеров gunicorn или работе из нескольких потоков соединение оказывается общим.
Путают скоупы: кладут Session в провайдер Singleton. Одна сессия на все запросы копит объекты в identity map и ломает транзакции соседних запросов.
Забывают про закрытие. Зависимость без yield с finally или контекстного менеджера оставляет соединения висеть, и под нагрузкой пул заканчивается.