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) yprefetch_relatedpara evitar problemas N+1. - Proyecciones: Uso de
only()ydefer()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
- No mezclar Dominio con HTTP: Si tu lógica de negocio recibe
request, estás acoplado al framework web. - No mezclar Dominio con Serialización:
to_representationen un Serializer no es el lugar para cálculos de negocio. - 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).