Каков сценарий использования on_commit в Django?
Чтение этой документации https://docs.djangoproject.com/en/4.0/topics/db/transactions/#django.db.transaction.on_commit
Это случай использования on_commit
with transaction.atomic(): # Outer atomic, start a new transaction
transaction.on_commit(foo)
# Do things...
with transaction.atomic(): # Inner atomic block, create a savepoint
transaction.on_commit(bar)
# Do more things...
# foo() and then bar() will be called when leaving the outermost block
Но почему бы просто не написать код как обычно, без on_commit
хуков? Например, так:
with transaction.atomic(): # Outer atomic, start a new transaction
# Do things...
with transaction.atomic(): # Inner atomic block, create a savepoint
# Do more things...
foo()
bar()
# foo() and then bar() will be called when leaving the outermost block
Его легче читать, поскольку он не требует дополнительных знаний об API Django, а утверждения расположены в порядке их выполнения. Его легче тестировать, так как вам не нужно использовать специальные тестовые классы для Django.
Итак, для чего нужен крючок on_commit
?
Документация Django:
Django предоставляет функцию on_commit() для регистрации функций обратного вызова, которые должны быть выполнены после того, как транзакция будет успешно зафиксирована
.
Это основная цель. Транзакция - это единица работы, которую вы хотите обрабатывать атомарно. Она либо выполняется полностью, либо не выполняется вообще. То же самое относится и к вашему коду. Если что-то пошло не так во время операций с БД, вам может не понадобиться делать некоторые вещи.
Рассмотрим некоторый поток бизнес-логики:
- Пользователь отправляет свои регистрационные данные на нашу конечную точку, мы проверяем их и т.д.
- Мы сохраняем нового пользователя в нашей БД.
- Мы отправляем ему письмо "привет" на электронную почту со ссылкой для подтверждения аккаунта.
Если что-то пойдет не так во время шага 2, мы не должны переходить к шагу 3.
Мы можем подумать, что, ну, я получу исключение и не выполню этот код. Зачем он нам еще нужен?
Иногда вы выполняете действия в коде, основываясь на предположении об успешности транзакции перед потенциально опасными операциями с БД. Например, вы хотите сначала проверить, можно ли отправить письмо пользователю, потому что вы знаете, что ваш сторонний email часто выдает 500. В этом случае вы хотите поднять 500 для пользователя и попросить его зарегистрироваться позже (очень плохая идея, кстати, но это просто синтетический пример).
Когда ваша функция (например, с декоратором @atomic
) содержит много операций с БД, вы, конечно, не хотите запоминать все состояния переменных, чтобы использовать их после всего кода, связанного с БД. Например, так:
- Валидация заказа пользователя.
- Проверка в БД, может ли он быть выполнен.
- Если он может быть выполнен, нам нужно отправить запрос в стороннюю CRM с деталями заказа.
- Если это не удалось, то нужно создать тикет поддержки в другой сторонней системе.
- Сохранение заказа пользователя в БД, обновление модели пользователя.
- Отправка уведомления в мессенджере сотруднику, ответственному за заказ.
- Сохранение информации о том, что уведомление для сотрудника было успешно отправлено в БД.
Вы можете себе представить, какой бы у нас был беспорядок, если бы мы не on_commit
оказались в этой ситуации, и у нас была бы действительно большая try-catch
на это