Каков сценарий использования 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() для регистрации функций обратного вызова, которые должны быть выполнены после того, как транзакция будет успешно зафиксирована

.

Это основная цель. Транзакция - это единица работы, которую вы хотите обрабатывать атомарно. Она либо выполняется полностью, либо не выполняется вообще. То же самое относится и к вашему коду. Если что-то пошло не так во время операций с БД, вам может не понадобиться делать некоторые вещи.

Рассмотрим некоторый поток бизнес-логики:

  1. Пользователь отправляет свои регистрационные данные на нашу конечную точку, мы проверяем их и т.д.
  2. Мы сохраняем нового пользователя в нашей БД.
  3. Мы отправляем ему письмо "привет" на электронную почту со ссылкой для подтверждения аккаунта.

Если что-то пойдет не так во время шага 2, мы не должны переходить к шагу 3.

Мы можем подумать, что, ну, я получу исключение и не выполню этот код. Зачем он нам еще нужен?

Иногда вы выполняете действия в коде, основываясь на предположении об успешности транзакции перед потенциально опасными операциями с БД. Например, вы хотите сначала проверить, можно ли отправить письмо пользователю, потому что вы знаете, что ваш сторонний email часто выдает 500. В этом случае вы хотите поднять 500 для пользователя и попросить его зарегистрироваться позже (очень плохая идея, кстати, но это просто синтетический пример).

Когда ваша функция (например, с декоратором @atomic) содержит много операций с БД, вы, конечно, не хотите запоминать все состояния переменных, чтобы использовать их после всего кода, связанного с БД. Например, так:

  • Валидация заказа пользователя.
  • Проверка в БД, может ли он быть выполнен.
  • Если он может быть выполнен, нам нужно отправить запрос в стороннюю CRM с деталями заказа.
  • Если это не удалось, то нужно создать тикет поддержки в другой сторонней системе.
  • Сохранение заказа пользователя в БД, обновление модели пользователя.
  • Отправка уведомления в мессенджере сотруднику, ответственному за заказ.
  • Сохранение информации о том, что уведомление для сотрудника было успешно отправлено в БД.

Вы можете себе представить, какой бы у нас был беспорядок, если бы мы не on_commit оказались в этой ситуации, и у нас была бы действительно большая try-catch на это

Вернуться на верх