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
- Entrada: Serializers/Forms (Tipos de datos, formatos).
- Dominio: Services/Models (Reglas de negocio, estado actual).
- 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 sobreModel.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.