Какие паттерны используются в Django для отделения бизнес-логики от представлений и моделей

Ответ

В Django для разделения бизнес-логики от представлений (views) и моделей (models) принято использовать паттерн Service Layer (слой сервисов).

Это помогает избежать анти-паттернов Fat Models (толстые модели) и Fat Views (толстые представления), делая код более тестируемым, переиспользуемым и поддерживаемым. Логику выносят в отдельные файлы, например, services.py внутри Django-приложения.

Пример

1. Файл с бизнес-логикой (services.py):

# myapp/services.py
from typing import List, Dict
from django.db import transaction
from .models import Order, OrderItem, Product

class OrderCreationError(Exception):
    pass

@transaction.atomic
def create_order(user, items_data: List[Dict]) -> Order:
    """
    Создает заказ и связанные с ним позиции в одной транзакции.
    Инкапсулирует логику валидации, расчета и сохранения.
    """
    order = Order.objects.create(user=user, status=Order.Status.PENDING)

    for item_data in items_data:
        product_id = item_data.get('product_id')
        quantity = item_data.get('quantity')

        if not Product.objects.filter(id=product_id, is_active=True).exists():
            raise OrderCreationError(f"Товар с ID {product_id} не найден или неактивен.")

        OrderItem.objects.create(order=order, product_id=product_id, quantity=quantity)

    # Здесь может быть дополнительная логика: расчет итоговой суммы, отправка уведомлений и т.д.
    order.calculate_total_price()
    order.save()

    return order

2. Представление, которое вызывает сервис (views.py):

# myapp/views.py
from django.http import JsonResponse
from . import services

def order_create_api(request):
    # Данные обычно приходят из request.body в формате JSON
    items_data = [{'product_id': 1, 'quantity': 2}]
    user = request.user

    try:
        new_order = services.create_order(user=user, items_data=items_data)
        return JsonResponse({"status": "ok", "order_id": new_order.id}, status=201)
    except services.OrderCreationError as e:
        return JsonResponse({"status": "error", "message": str(e)}, status=400)

Преимущества подхода:

  • Тестируемость: Бизнес-логику в services.py можно тестировать изолированно, без HTTP-запросов.
  • Переиспользование: Сервисную функцию create_order можно вызвать из view, Celery-задачи или management-команды.
  • Принцип единственной ответственности (SRP): Views отвечают за HTTP, модели — за данные, а сервисы — за бизнес-процессы.
  • Читаемость: Код представлений становится лаконичным и понятным.