Какие альтернативные подходы к толстым моделям в Django существуют
Что проверяют
Понимаете ли вы, где в Django-проекте должна жить бизнес-логика, когда модели разрастаются до тысяч строк. Следом обычно дают сценарий, например оформление заказа со списанием баланса и письмом клиенту, и просят разложить его по слоям.
Как отвечать
Сначала признайте, что «толстые модели, тонкие вью» это нормальная стартовая точка. Проблемы начинаются, когда модель знает про платёжный шлюз, почту и соседние приложения. Дальше варианты:
- сервисный слой: функции или классы под сценарии, которые затрагивают несколько моделей и внешние системы. Здесь живут
transaction.atomic()иtransaction.on_commit(). Такой код тестируется без HTTP и без вью; - selectors: отдельный модуль для чтения, где собраны запросы с
select_related,prefetch_relatedи аннотациями. Вместе с сервисами это основа популярного Django Styleguide; - кастомные
QuerySetи менеджеры: логика выборок в цепочках вродеOrder.objects.paid().for_user(user), аQuerySet.as_manager()избавляет от дублирования методов в менеджере; - формы и сериализаторы DRF: валидация входных данных. Бизнес-процесс в них не тащат, иначе
serializer.save()с побочными эффектами становится второй толстой моделью; - доменный слой и репозитории: отдельные сущности, а ORM только как хранилище. Изоляция полная, но в Django это дорого, вы теряете часть удобств ORM и админки;
- сигналы: с оговоркой. Они прячут поток управления, их оставляют для развязки приложений, а не для основной логики.
Критерий, который стоит озвучить: модель отвечает за инварианты своих данных (свойства, clean(), методы над одной записью), а всё, что шире одной сущности, уходит в сервисы.
Где спотыкаются
Переносят логику из модели во вью или в сериализатор. Код не стал чище, он переехал туда, где его сложнее переиспользовать и тестировать.
Заводят сервисный класс со @staticmethod на каждую операцию, который просто повторяет objects.create. Слой ради слоя добавляет файлов, но не ясности.
Отправляют письма и задачи Celery внутри транзакции, а не через on_commit. При откате письмо уже ушло, а воркер может не найти запись, которую ещё не закоммитили.