Comments 9
Банальные вещи которые всёже нужно повторять почаще.
Почитайте про курсорную пагинацию, она менее универсальна, но зато очень дружественна к базе.
Запихивать репозитории в TransactionManager через @property — это прямой путь к god-object. При росте приложения этот класс раздует, и его придется модифицировать при добавлении каждой новой таблички (явное нарушение OCP). Гораздо изящнее разруливать это через нормальный DI-контейнер (ту же Dishka). Провайдишь сессию куда надо, а управление транзакцией вешаешь декоратором прямо на юзкейс. И базовый класс чистый, и бойлерплейта меньше.
Спасибо за комментарий!
На god-object такой подход слабо тянет. Все-таки он всего лишь управляет транзакцией внутри репозириев, а не управляет всеми слоями приложения. А еще можно вручную менеджерить сессию в репозитории, без менеджера транзакций, этот подход остается открытым.
Нарушения OCP не вижу. Добавил табличку - добавил новый репозиторий и новый property. Было бы здорово аргументировать вашу позицию чуть более подробно.
Насчет DI контейнера очень крутая мысль, обязательно посмотрю. Может быть есть какие-то примеры/статьи?
"Нарушения OCP не вижу. Добавил табличку - добавил новый репозиторий и новый property". OCP - открыто для расширения, закрыто для модификации. Вы пишите, что нарушений нет, но в следующем предложении изменяете класс:) Попробуйте в ваше реализации заменить репозитории на другие имплементации и вам придётся в каждом property менять возращяемый класс. Эту проблему как раз решает DI-контейнер(как уже писали выше).
ПС хорошая статья, спасибо за труды
Нарушение OCP все же есть, вы правы. Но TransactionManager выступает еще и как такой composition object, который собирает репозитории, поэтому тут это не особо критично. Можно сделать код лучше с точки зрения OCP, но хуже с точки зрения типизации и явных контрактов. Наверное этот тот случай, когда я нарушаю его намеренно ради простоты и удобства, а то получится как в Django с его непонятными строками, обозначающими поля моделей:
class UserSerializer(serializers.ModelSerializer):
full_name = serializers.CharField(
source="profile.personal_data.full_name",
read_only=True,
)
company_name = serializers.CharField(
source="profile.company.name",
read_only=True,
)
manager_email = serializers.EmailField(
source="profile.company.manager.email",
read_only=True,
)
class Meta:
model = User
fields = [
"id",
"email",
"username",
"full_name",
"company_name",
"manager_email",
]PS. С DI контейнерами я все же поразбираюсь, вдруг поможет сделать мой подход лучше)
Травма от джанговских строк очень понятна)
Но фишка современных DI на питоне (той же Dishka) как раз в том, что там под капотом нет никакой строковой магии. Всё резолвится исключительно по type hints.
Вы просто указываете в init юзкейса зависимость вида repo: UserRepository. В итоге IDE, mypy и автокомплит работают идеально, потому что типы прописаны явно и строго. Так что компромиссов с типизацией тут не будет, реально советую потыкать.
В первый раз вижу включения репозиториев во внутрь TransactionManager. Репозиториев могут быть сотни. До каких размеров тогда вырастет TransactionManager? Из моего опыта если в юз кейсе нужна транзакция, то транзакционный механизм описывается, как обёртка для этого юз кейса. Репозитории используются в функционале юз кейса. Транзакция открывается перед выполнением кода юз кейса. Если функционал юз кейса отработал без ошибок, то транзакция коммитится. В противном случае откатывается.
Спасибо за комментарий!
В теории, можно сделать repository_registry внутри TransactionManager, и вызывать метод get_repository("users_repository"), но я намеренно избегаю этого, пока позволяют размеры проекта, так как это лишит меня удобной типизации в угоду архитектурной правильности, которая сейчас никак не раскрывается.
У меня в функционале юз кейса используются сервисы, сервисы использую репозитории, а открытие транзакции перед выполнением юз кейса считаю не всегда удачным решением, потому что:
Тогда логику открытия транзакции придется писать в эндпоинте, middleware или декораторе
Если выносить открытие из эндпоинта, то это лишний слой абстракции, без которого вполне можно обойтись
Снижается гибкость поведения системы при ошибках. Если мы захотим залогировать в БД то, что у нас упало в юз кейсе, то оно просто не закомитится
Скорее всего, вы в первый раз вы видите такую реализацию, так как я ее не тупо скопировал у кого-то другого. Я пришел к этому почти самостоятельно. Комментарии позволяют мне посмотреть на свои решения с разных сторон, это важно для меня)
Развитие FastAPI приложения в ходе разработки production-системы