Django Guía

11. Patrones de Diseño Aplicados a Django

Este documento no es un glosario teórico. Es un análisis estratégico de cómo aplicar los patrones de diseño clásicos y modernos dentro del ecosistema opinionado de Django, sin pelear contra el framework.

1. Patrones Clásicos (GoF) en Django

Strategy Pattern

Problema: Múltiples algoritmos para una tarea (ej: cálculo de precios con descuentos variados).

Solución Django: Definir una interfaz base (Clase Abstracta o Protocol) y pasar la estrategia concreta al servicio.

# strategies.py
class PricingStrategy(Protocol):
    def calculate(self, order) -> Decimal: ...

class BlackFridayPricing:
    def calculate(self, order): return order.base_total * 0.8

# service.py
def checkout(order, pricing_strategy: PricingStrategy):
    total = pricing_strategy.calculate(order)
    # ...

Factory Pattern

Problema: Crear objetos complejos cuya configuración depende de inputs.

Aplicación: Útil para crear diferentes tipos de usuarios o configuraciones de reportes.

Adapter Pattern

Problema: Integrar librerías de terceros con interfaces incompatibles.

Aplicación: Estandarizar pagos. Tu sistema espera pay(amount), pero Stripe pide create_charge() y PayPal execute_payment(). Creas un adaptador para cada uno.

Facade Pattern

Problema: Subsistema complejo (ej: generar PDF, subirlo a S3, notificar usuario).

Aplicación: Una clase ReportGeneratorFacade que exponga un método simple generate_and_send(), ocultando la complejidad.

Observer Pattern

Implementación Nativa: Django Signals (post_save, pre_delete).

Advertencia: Las señales son síncronas por defecto y ocultan el flujo de ejecución. Úsalas con cautela o prefiere eventos de dominio explícitos.

2. Patrones Orientados a Dominio

Aggregate Root

Un modelo principal que controla la consistencia de modelos hijos. Ej: Order controla sus OrderItems. Nunca modificas un Item directamente, lo haces a través de métodos en Order (ej: order.add_item()) para recalcular totales.

Specification Pattern

Encapsula reglas de negocio booleanas complejas que pueden combinarse.

class IsEligibleForPremium:
    def is_satisfied_by(self, user) -> bool:
        return user.age > 18 and user.score > 500

Se puede adaptar para generar Q objects del ORM: PremiumUserSpec().to_q().

3. Patrones de Infraestructura

Gateway Pattern

Encapsula el acceso a sistemas externos (APIs REST, SOAP). Si la API externa cambia, solo modificas el Gateway, no tu lógica de negocio.

Repository Pattern (¿Cuándo sí?)

Cuándo NO: Para el 90% de los casos. El ORM de Django ya es un Repositorio.

Cuándo SÍ: Cuando necesitas cambiar la fuente de datos dinámicamente (ej: leer de DB en local, pero de una API externa en producción) o para testeo unitario puro (in-memory repo).

Circuit Breaker

Protege tu sistema de fallos en cascada. Si un servicio externo (ej: servicio de SMS) falla repetidamente, el Circuit Breaker "abre el circuito" y falla rápido sin esperar timeout, recuperándose después de un tiempo.

4. Patrones Avanzados de API

DTOs (Data Transfer Objects)

Objetos planos para transferir datos entre capas, evitando pasar instancias de Modelos a serializadores o plantillas en arquitecturas complejas.

Validación en 3 Capas

  1. Entrada: Serializers/Forms (Tipos de datos, formatos).
  2. Dominio: Services/Models (Reglas de negocio, estado actual).
  3. Persistencia: Constraints de DB (Integridad referencial, Uniqueness).

5. Patrones Arquitectónicos Internos

Command Pattern

Encapsula una solicitud como un objeto. Útil para colas de tareas, deshacer operaciones (undo) o logging transaccional.

Query Object Pattern

Clases especializadas en construir consultas complejas, similar a los Selectores pero orientados a objetos con métodos encadenables (Builder).

6. Patrones que NO deben implementarse en Django

Anti-patrones Comunes

  • Repositorios Genéricos sobre ORM: Repo(Model).find(id) es redundante sobre Model.objects.get(id). Añade capas sin valor.
  • Pure Domain Models aislados: Intentar ignorar que Django usa Active Record lleva a escribir miles de líneas de código "pegamento" innecesario.
  • Service Locator: Oculta dependencias. Prefiere Inyección de Dependencias (aunque sea manual en Python).

7. Conclusión

Los patrones son herramientas, no dogmas. Implementar un patrón Factory para algo que se resuelve con un if es sobre-ingeniería.

Regla del Arquitecto: Aplica un patrón solo cuando el dolor de NO tenerlo sea mayor que la complejidad de implementarlo.