Software Architect · Módulo 09
La mayoría de los bugs difíciles no vienen de los algoritmos. Vienen de estados implícitos y de las transiciones entre ellos.
State machine · lifecycle · concurrency · consistency
Si un objeto tiene ciclo de vida, diséñalo como state machine — no como un montón de flags booleanos.
El estado tiene que ser explícito
Un semáforo se entiende porque tiene un conjunto limitado de estados y transiciones permitidas. Si todas las luces se prendieran a la vez, los conductores discutirían en el cruce.
Order, payment, delivery, subscription, ticket, deployment — son entidades con lifecycle. Tienen estados y transiciones: draft, pending_payment, paid, cancelled, refunded. El trabajo arquitectónico es definir las transiciones permitidas y el owner de cada una.
Cuando aparecen flags como isPaid, isCancelled, isRefunded, isArchived en lugar de una state machine, el sistema llega rápido a combinaciones imposibles: pagado y cancelado a la vez, entregado sin pago, refund sin capture.
La concurrency es parte del modelo
Si dos personas editan un documento al mismo tiempo sin reglas, no gana quien tenga razón — gana el último que guardó.
En un sistema real, varios procesos pueden cambiar el mismo estado: el usuario, un worker, un webhook, un admin, un retry. Hacen falta optimistic locking, un version field, transactions, unique constraints u operaciones compare-and-swap.
El arquitecto está obligado a preguntar: quién puede cambiar este estado, cómo se resuelven las races, qué operaciones son idempotentes y qué cuenta como estado final.
Un buen estado se explica con un diagrama. Un mal estado se explica con una lista de excepciones.
Ejemplo: payment lifecycle
Una operación bancaria pasa por etapas. No puedes devolver el dinero primero y decidir después si fue cobrado.
Un payment puede pasar de created a authorized, y luego a captured o voided. Un refund solo es posible después de captured. El webhook del proveedor no escribe campos arbitrarios — dispara una transición que valida el estado actual y el version.
Ese enfoque convierte a los edge cases en parte del modelo, no en un montón de if regados por todo el código.
Antiejemplo: flags en lugar de ciclo de vida
Un formulario con diez checkboxes parece flexible — hasta que dos de ellos se contradicen.
El order tiene paid_at, cancelled_at, failed_at, refunded_at, completed_at, pero ningún status claro. Distintas partes del código interpretan las combinaciones de manera distinta. Soporte ve una cosa, contabilidad otra, el API una tercera.
Eso no es flexibilidad. Es una state machine implícita sin reglas.
- ¿Qué estados tiene esta entidad? - ¿Qué transiciones están prohibidas? - ¿Quién es el owner de la transición: el usuario, el sistema o un proveedor externo? - ¿Cómo nos protegemos contra retries y races?