Pull to refresh

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"), но я намеренно избегаю этого, пока позволяют размеры проекта, так как это лишит меня удобной типизации в угоду архитектурной правильности, которая сейчас никак не раскрывается.

У меня в функционале юз кейса используются сервисы, сервисы использую репозитории, а открытие транзакции перед выполнением юз кейса считаю не всегда удачным решением, потому что:

  1. Тогда логику открытия транзакции придется писать в эндпоинте, middleware или декораторе

  2. Если выносить открытие из эндпоинта, то это лишний слой абстракции, без которого вполне можно обойтись

  3. Снижается гибкость поведения системы при ошибках. Если мы захотим залогировать в БД то, что у нас упало в юз кейсе, то оно просто не закомитится

Скорее всего, вы в первый раз вы видите такую реализацию, так как я ее не тупо скопировал у кого-то другого. Я пришел к этому почти самостоятельно. Комментарии позволяют мне посмотреть на свои решения с разных сторон, это важно для меня)

В моём комментарии указано что вся работа с транзакцией идёт в юз кейсе. Так было сделано во многих проектах.

Sign up to leave a comment.

Articles