Django Guía

10. Arquitectura Moderna (Nivel Profesional)

Este capítulo es el blueprint para equipos medianos que buscan construir sistemas Django mantenibles y escalables. Aquí transformamos Django de un framework "rápido para prototipos" a una herramienta de ingeniería robusta.

1. MTV a Fondo y su Relación con MVC

Para construir sistemas complejos, debemos diseccionar el patrón Model-Template-View (MTV) con precisión quirúrgica.

Precisión Técnica

  • View (Vista) como Orquestador: En arquitectura moderna, la Vista NO contiene lógica de negocio. Su única responsabilidad es traducir HTTP: recibir una petición, extraer datos, llamar a la capa correcta y devolver una respuesta (HTML/JSON). Es un adaptador de entrada.
  • Model (Modelo) como Híbrido: Los modelos de Django mezclan el patrón Active Record (persistencia y queries) con Domain Model (reglas de negocio). Esto es poderoso pero peligroso.

Riesgos del Acoplamiento Natural: Django incentiva poner lógica en save() o en métodos del modelo. Esto acopla tu lógica de negocio a la base de datos, dificultando el testeo unitario puro y la migración a arquitecturas distribuidas.

2. Anti-patrones: Fat Views y Fat Models

Corregir estos vicios es el primer paso hacia una arquitectura profesional.

Fat Views (Vistas Obesas)

Definición: Vistas que validan formularios, calculan precios, envían correos y llaman a APIs externas.

  • Rompe SRP: La vista cambia por razones de UI y por razones de negocio.
  • Bloqueo: Testing lento (requiere RequestFactory) y refactor imposible.

Fat Models (Modelos Obesos)

Definición: Mover toda la lógica de la vista al modelo (model.calcular_impuesto()).

  • Consecuencias Operativas: Los modelos se vuelven "God Objects" de miles de líneas.
  • Violación de Capas: Un modelo no debería saber sobre envío de correos o serialización JSON.

3. Service Layer Totalmente Formalizado

La Capa de Servicios es la solución canónica para la lógica de negocio en Django.

Principios Rectores

  • Son funciones simples o clases sin estado (Stateless).
  • Reciben tipos de dominio o primitivos, NUNCA objetos request.
  • Declaran explícitamente las transacciones de base de datos.

Ejemplo: Trabajo Transaccional Real

# services/orders.py
from django.db import transaction
from .models import Order, Payment
from .tasks import send_confirmation_email

def create_order(*, user, items: list[dict]) -> Order:
    """
    Caso de Uso: Crear Orden.
    Maneja consistencia, errores y side-effects.
    """
    with transaction.atomic():
        order = Order.objects.create(user=user)

        # Lógica de dominio compleja
        total = calculate_total(items)
        if user.balance < total:
            raise InsufficientFundsError()

        for item in items:
            order.items.add(item['product'], quantity=item['qty'])

        # Side effects asíncronos (Consistencia Eventual)
        transaction.on_commit(lambda: send_confirmation_email.delay(order.id))

    return order

Por qué la Vista debe ser un "Thin Controller": Al delegar todo a create_order, la vista se reduce a 3 líneas. Si mañana cambias de API REST a GraphQL o CLI, reutilizas el servicio intacto.

4. Selector Layer (Query Layer) Profesional

Separar las lecturas (Queries) de las escrituras (Commands) es vital. Los Selectores encapsulan consultas complejas.

Diseño Sistemático

  • Optimización Centralizada: Uso estricto de select_related (JOINs) y prefetch_related para evitar problemas N+1.
  • Proyecciones: Uso de only() y defer() para no cargar campos pesados innecesarios.
  • Desacoplamiento: La vista pide datos a un selector, no sabe si vienen de una tabla SQL, una vista materializada o cache.
# selectors/products.py
def get_catalog_products() -> list[dict]:
    """
    Selector optimizado por proyección.
    Retorna diccionarios, no modelos (opcional, para mayor rendimiento).
    """
    return Product.objects.filter(active=True)\
        .select_related('category')\
        .only('id', 'name', 'price', 'category__name')\
        .values('id', 'name', 'price', 'category_name')

5. Bounded Contexts usando Apps Django

Las Apps de Django no son carpetas para organizar archivos; son Contextos Delimitados del dominio.

  • Mapeo de Dominios Reales: users, payments, logistics, reporting.
  • Interfaz Pública: Una App debe exponer una interfaz clara (ej: services.py, selectors.py). Otras apps NO deberían importar modelos internos directamente.

6. Configuración Modular de Settings

Un solo settings.py es inmanejable. La estructura estándar profesional es:

src/curso_django/settings/
├── __init__.py
├── base.py       # Configuración común (Apps, Middleware)
├── local.py      # Entorno desarrollo (Debug=True, Console Email)
└── production.py # Entorno prod (S3, Redis, Sentry, Strict DB)

Aislamiento de Infraestructura: Usar variables de entorno (python-decouple o django-environ) para credenciales. Nunca commitear SECRET_KEY.

7. Reglas de Oro para Evitar Deuda Técnica

  1. No mezclar Dominio con HTTP: Si tu lógica de negocio recibe request, estás acoplado al framework web.
  2. No mezclar Dominio con Serialización: to_representation en un Serializer no es el lugar para cálculos de negocio.
  3. No introducir Complejidad Accidental: No crees Repositorios Genéricos sobre el ORM si no los necesitas. El ORM de Django ya es una implementación del patrón Repository/UnitOfWork.

8. Cierre del Capítulo

Matriz de Decisión para Proyectos Medianos

Característica Enfoque Moderno
Lógica de Negocio Services Layer (Funciones puras o Clases de Servicio).
Consultas Complejas Selector Layer (Funciones especializadas).
Tareas Lentas Asíncronas obligatorias (Celery/RQ) invocadas desde Servicios.

Criterio de Ascenso: Cuando la complejidad del dominio supera la capacidad de organización de las Apps y Servicios, es momento de pasar a la Arquitectura Avanzada (Cap. 12).