> Какие инструменты DRF и Django использовать для реализации бизнес-логики сохранения заказа? (Python)

Уровень: senior · Роль: backend · Язык: Python · Категория: Технические вопросы

Компании: ФедяИСамат

Стек: Python

> Пример ответа

Короткий ответ

Для реализации бизнес-логики сохранения заказа в DRF используйте сериализаторы с методом create()/update() для валидации и атомарности, транзакции transaction.atomic() для целостности данных, сигналы или явные сервисные слои для побочных эффектов. Для сложной логики - отдельный service layer (например, OrderService), вызываемый из perform_create(). Для конкурентных изменений - select_for_update() и версионирование. Для фоновых задач - Celery или transaction.on_commit().

Подробное объяснение

Бизнес-логика сохранения заказа обычно включает: проверку прав, валидацию данных, вычисление итоговой суммы, применение скидок, резервирование товаров, создание связанных сущностей (платежи, доставка), отправку уведомлений.

Основные инструменты:

  • Сериализаторы DRF - точка входа для валидации и создания/обновления. Методы validate(), create(), update() - место для бизнес-правил, связанных с данными запроса.
  • Транзакции - transaction.atomic() гарантирует атомарность: если любой шаг падает, откатываются все изменения. Для вложенных транзакций используйте savepoint.
  • Service layer - отдельный класс (например, OrderService) инкапсулирует бизнес-логику, не привязанную к HTTP. Это улучшает тестируемость и переиспользование (например, из management commands или Celery).
  • Сигналы - для побочных эффектов (отправка email, обновление кэша), но не для критичной логики - сложно отлаживать и тестировать.
  • select_for_update() - блокировка строк при конкурентном доступе (например, при резервировании товара).
  • transaction.on_commit() - выполнение действий после успешного коммита (например, отправка уведомлений, запуск фоновых задач).
  • Celery - для асинхронных операций (расчет доставки, интеграции с внешними сервисами).
  • F() expressions - для атомарных обновлений счетчиков (например, остатков на складе).

На практике

Для senior-уровня важно показать понимание trade-off между подходами:

  • Сериализатор vs service layer: для простых случаев достаточно сериализатора. Если логика разрастается, выносите в сервис - иначе сериализатор превращается в god object.
  • Сигналы vs явный вызов: сигналы удобны для cross-cutting concerns, но неявные вызовы усложняют отладку. Предпочитайте явные вызовы в сервисе.
  • Транзакции: не держите транзакцию открытой во время внешних HTTP-запросов - это блокирует БД. Используйте on_commit для действий после коммита.
  • Конкурентность: всегда проверяйте остатки товара внутри транзакции с select_for_update(), иначе возможен overselling.

Пример кода

PYTHON
# services/order_service.py
from django.db import transaction
from django.db.models import F
class OrderService:
@staticmethod
@transaction.atomic
def create_order(user, cart_items, address_id):
# Блокируем товары для предотвращения гонок
products = Product.objects.select_for_update().filter(
id__in=[item['product_id'] for item in cart_items]
)
# Проверка остатков и резервирование
for product in products:
quantity = next(
item['quantity'] for item in cart_items
if item['product_id'] == product.id
)
if product.stock < quantity:
raise ValidationError(f"Недостаточно товара: {product.name}")
product.stock = F('stock') - quantity
product.save(update_fields=['stock'])
# Создание заказа
order = Order.objects.create(
user=user,
address_id=address_id,
total=sum(
p.price * q for p, q in zip(products, [i['quantity'] for i in cart_items])
)
)
OrderItem.objects.bulk_create([
OrderItem(order=order, product=p, quantity=q, price=p.price)
for p, q in zip(products, [i['quantity'] for i in cart_items])
])
# Действие после коммита
transaction.on_commit(lambda: notify_order_created(order.id))
return order
PYTHON
# views.py
class OrderCreateView(APIView):
def post(self, request):
serializer = OrderSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
order = OrderService.create_order(
user=request.user,
cart_items=serializer.validated_data['items'],
address_id=serializer.validated_data['address_id']
)
return Response(OrderDetailSerializer(order).data, status=201)

Как отвечать на собеседовании

Начните с краткого перечисления инструментов, затем углубитесь в конкретный сценарий. Покажите, что понимаете, когда какой инструмент применять. Обязательно упомяните про транзакции и конкурентность - это ключевой момент для senior. Приведите пример из реального проекта, где вы решали проблему целостности данных. Если спросят про альтернативы - сравните сигналы и сервисный слой, объясните, почему выбрали один из них.

Что проверяет интервьюер

  • Понимание жизненного цикла запроса в DRF: от URL до сериализатора и модели.
  • Умение проектировать атомарные операции с БД.
  • Знание подводных камней: гонки, deadlocks, N+1 запросы.
  • Способность отделять бизнес-логику от транспортного уровня.
  • Практический опыт с реальными кейсами (резервирование, инвентаризация, платежи).

Типичные ошибки

  • Вся логика в perform_create() без транзакции - при ошибке данные частично сохраняются.
  • Использование сигналов для критичных операций - сложно тестировать и отлаживать.
  • Игнорирование конкурентного доступа - overselling товаров.
  • Держать транзакцию открытой при внешних вызовах (HTTP, email) - блокировки БД.
  • Не использовать select_for_update() при изменении счетчиков - потеря обновлений.
  • Смешивать валидацию и бизнес-логику в сериализаторе до состояния god object.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью