Какие паттерны используются в 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, модели — за данные, а сервисы — за бизнес-процессы.
  • Читаемость: Код представлений становится лаконичным и понятным.

Ответ 18+ 🔞

А, слушай, вот эта вся история с Django и бизнес-логикой — это просто пиздец какой-то цирк, если её не организовать. Смотри, чуваки часто делают так: вся их логика либо в views.py сидит, как толстый жлоб на диване, либо в models.py размазана, как говно по стенке. А потом орут: «Почему тесты писать — это ад? Почему ничего переиспользовать нельзя?».

Так вот, чтобы не быть таким мудаком, есть паттерн Service Layer. Слой сервисов, блядь. Выносишь всю свою бизнес-хуйню — создание заказов, расчёты, проверки — в отдельный файлик, например services.py. И живёшь потом, как сыр в масле катаешься.

Смотри, как это выглядит, на примере

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

Тут у нас вся магия, вся соль. Views и модели — это просто обёртки, а тут — настоящая работа.

# 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):

А вот view становится лёгким и пушистым. Его задача — принять запрос, вызвать нужный сервис и отдать ответ. Всё, ебушки-воробушки!

# 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:
        # Вся сложность спрятана тут. View даже не парится, что там внутри.
        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)

Ну и зачем это всё, спросишь? А вот зачем, ёпта:

  • Тестируемость — овердохуищная. Ты можешь тестировать функцию create_order в полной изоляции, без всяких там HTTP-запросов и фреймворков. Просто подал данные и смотришь, что вышло.
  • Переиспользование на уровне бога. Эту же сервисную функцию можно вызвать из обычной view, из Celery-задачи, из management-команды, да хоть из телеграм-бота! Всё в одном месте.
  • Принцип единственной ответственности (SRP). View отвечает за HTTP-хуйню (принял запрос, отдал ответ). Модели — за работу с базой. А сервисы — за бизнес-процессы, за настоящую логику приложения. Всё по полочкам, ни одна сущность не пытается быть умнее другой.
  • Читаемость. Открываешь views.py — и сразу видишь, что происходит, а не ломаешь глаза, пытаясь разобраться в трёхэтажных условиях и циклах.

Короче, не будь мудаком — выноси логику в сервисы. А то потом сам же будешь плакать, когда придётся что-то менять или тестировать.