Stack: Django/DRF · Proyecto: TicketFlow Estado: Publicada — apertura del módulo de arquitectura Prerrequisito: Lección 23 — Secretos y RGPD
Objetivos
- Explicar cada principio SOLID con un ejemplo de TU proyecto (no con Shape/Circle de tutorial).
- Separar capas: vista (HTTP) → servicio (dominio) → modelos (persistencia), y saber qué va en cada una.
- Aplicar inyección de dependencias en Python sin ceremonia (duck typing + protocolos).
1. SOLID en TicketFlow (cada letra, un ejemplo tuyo)
S — Single Responsibility: reservar() (10) reserva; no envía emails ni calcula impuestos. La vista traduce HTTP; el servicio ejecuta dominio; el modelo persiste y mantiene invariantes. Cuando una función necesita "y además", es la señal.
O — Open/Closed: añadir un nuevo método de pago (tarjeta regalo de la 08, pasarela B) sin tocar el código existente: un PaymentMethod con interfaz común (charge(amount, intent)) y registro de implementaciones, en vez de un if method == "card" elif... que crece para siempre. Extender = añadir clase; modificar = no.
L — Liskov: toda implementación de PaymentGateway respeta el contrato: charge devuelve o lanza, nunca devuelve None silencioso "a veces". Si una pasarela simulada devuelve {"ok": True} y la real {"status": "ok"}, el código que las usa se rompe — la sustituibilidad es el contrato exacto, incluidas las excepciones.
I — Interface Segregation: la vista de disponibilidad no necesita un "repository que también envía emails". Interfaces pequeñas por consumidor: AvailabilityReader (lee), ReservationWriter (escribe) — los clientes no dependen de métodos que no usan.
D — Dependency Inversion: el servicio de reserva no importa psycopg ni Redis directamente: recibe clock, payment_gateway, cache por parámetro/constructor. El dominio define qué necesita; la infraestructura lo cumple.
2. Las capas de TicketFlow
views/serializers → HTTP: parse, auth, status codes (13/26)
services/ → DOMINIO: reglas, transacciones (10), invariantes
models/ → PERSISTENCIA: tablas, constraints, máquinas de estado (00b)Reglas de frontera: la vista NO consulta el ORM en loops (eso es lógica de dominio); el servicio NO construye Response ni lee request (eso es HTTP); el modelo NO importa Redis ni llama APIs (eso es infraestructura). La prueba del algodón: ¿puedes llamar reservar(user, seats) desde un comando de management, un worker y un test sin Django request? Si sí, las capas están bien.
3. Inyección en Python (sin contenedores de gore)
Python no necesita Spring: las dependencias entran por parámetro con defaults, y el duck typing + typing.Protocol documentan el contrato:
class Clock(Protocol):
def now(self) -> datetime: ...
class SystemClock:
def now(self) -> datetime:
return timezone.now()
def reservar(user, seats, *, clock: Clock = SystemClock(), expires_in: timedelta = timedelta(minutes=10)):
expires_at = clock.now() + expires_in # el test inyecta FakeClock y controla el tiempoEl test de la 10 con expires_at fijo deja de depender de timezone.now(); el test de expiración del job (29) avanza el reloj virtual. La DI aquí es: constructor + default de producción + fake en test. Un contenedor solo cuando el grafo crece de verdad (no es este proyecto).
4. Señales de deuda de arquitectura (las cuelas que lees en review)
- Un
utils.pyde 900 líneas (el cajón de sastre): dividir por dominio. - Import de
requests/redisdentro deservices/reservations.py: dependencia de infraestructura dura → inyectar. if user.role == "STAFF"repetido en 12 lugares: la regla viva en un lugar (permissions.py/servicios).- Vista de 150 líneas: mezcla HTTP+dominio: extraer servicio.
- Modelos sin comportamiento (anémicos) con toda la lógica en servicios: válido, pero las invariantes de esquema (00b) deben seguir en el modelo — el equilibrio del proyecto: máquinas de estado en el modelo, flujos en servicios.
Autoevaluación
- Tu vista de reserva tiene 150 líneas con ORM en loops: ¿qué extraes y a qué capa lleva cada pieza?
- ¿Qué problema concreto resuelve la L en
PaymentGatewayy cómo se manifiesta su violación en tests? - ¿Por qué
Clockes la primera dependencia que conviene inyectar en un sistema de reservas? - ¿Cuándo un modelo "anémico" es señal de problema y cuándo es elección legítima en Django?
- ¿Qué prueba del algodón aplicas para saber si tus capas están bien separadas?
Continúa con los ejercicios. Las solutions.md solo tras intentarlo.