Módulo 5 · Arquitectura y código mantenible

Lección 24 — SOLID e inyección de dependencias

Separación por capas y dependencias que apuntan en la dirección correcta.

Publicada
En esta lección
  1. Ejercicio 1 — La vista gorda, operada
  2. Ejercicio 2 — Clock inyectado
  3. Ejercicio 3 — L de Liskov con pasarelas
  4. Ejercicio 4 — O con registro de métodos
  5. Ejercicio 5 — I en el acceso a datos
  6. Entrega

Refactors sobre tu TicketFlow. Cada uno con tests que lo respaldan (la 28 formaliza el método). No mires solutions.md hasta entregar.

Ejercicio 1 — La vista gorda, operada

  1. Toma tu vista de reserva (o la más larga que tengas) y lista cada línea con su capa: HTTP (parse/status), dominio (reglas/transacciones), persistencia (ORM directo).
  2. Extrae services/reservations.py::reservar() con la firma reservar(user, event_uuid, seat_refs, *, clock, gateway) -> Reservation. La vista queda: serializer + llamada + respuesta. Mide: ¿cuántas líneas tiene la vista ahora?
  3. Verifica la prueba del algodón: llámala desde manage.py shell sin request. ¿Funcionó?

Ejercicio 2 — Clock inyectado

  1. Crea el Clock Protocol + SystemClock + FakeClock(fixed=...) y úsalo en reservar() (el default de producción intacto).
  2. Escribe el test "una reserva con FakeClock a las 23:59 expira a las 00:09" sin sleeps ni freezegun: solo el fake.
  3. Bonus: el job de expiración (cuando llegue la 29) consumirá el mismo Clock — anota cómo le ayuda.

Ejercicio 3 — L de Liskov con pasarelas

  1. Define el Protocol PaymentGateway.charge(amount, intent) -> ChargeResult. Implementa FakeGateway (aprueba si el importe < 1000) y SimulatedGateway (para producción-staging).
  2. Rompe a propósito: una implementación que devuelve None en error en vez de lanzar. Escribe el test que la caza (¿qué assertion falló?).
  3. Documenta el contrato exacto: qué devuelve, qué lanza, qué garantiza de idempotencia (14). ¿Tu docstring basta o hace falta type strict?

Ejercicio 4 — O con registro de métodos

  1. Implementa PAYMENT_METHODS: dict[str, type[PaymentMethod]] con registro @register("gift_card"). Añade "cash_on_delivery" (simulado) SIN tocar el servicio de pagos: solo un nuevo módulo + registro.
  2. ¿Qué pasa en tu código viejo si mañana añaden "crypto"? ¿Cuántas líneas cambian? (respuesta esperada: 1 archivo nuevo).

Ejercicio 5 — I en el acceso a datos

  1. Separa AvailabilityReader (lo que usa el endpoint público) de ReservationWriter (lo que usa el checkout). ¿Qué métodos sobraban en la interfaz única que imaginaste primero?
  2. El utils.py (si lo tienes): reorganicéalo por dominio (utils/time.py, utils/money.py...) o confiesa que aún no lo tienes y escribe cómo lo evitarías.

Entrega

Pega el before/after de la vista, los tests con FakeClock y el registro de métodos. Después: Lección 25 — Patrones de servicio.