Planteamiento y contexto
Diseñe un motor de flujos de trabajo para procesos definidos por el cliente de hasta 20 pasos. Un paso requiere aprobación humana y puede esperar durante días; las actividades pueden fallar, entregarse dos veces o tener éxito mientras se pierde la respuesta. El sistema debe recuperarse de fallas y admitir tiempos de espera (timeouts), cancelación, auditoría y actualizaciones de versión. Explique el modelo de estado, la programación (scheduling), la idempotencia, la seguridad de callbacks, la escala y las compensaciones (trade-offs).
Esta pregunta de diseño de sistemas es adecuada para roles de backend, plataforma e infraestructura. El enfoque se centra en la semántica de ejecución duradera, no en el dibujo de una cola simple. El límite de 20 pasos y la espera de varios días son suposiciones de entrevista; clarifique primero el rendimiento (throughput), la latencia, el aislamiento de inquilinos (tenants), la confidencialidad de los datos, los objetivos de recuperación y las obligaciones de retención.
Qué está evaluando el entrevistador
El entrevistador busca una separación entre el estado del flujo de trabajo, la ejecución de actividades y los efectos secundarios externos; un reconocimiento explícito de que las actividades pueden ejecutarse al menos una vez (at-least-once); y claves de idempotencia vinculadas a invariantes del negocio. La aprobación humana necesita una credencial no falsificable, que expire y sea de un solo uso. La respuesta también debe definir el historial de eventos, instantáneas (snapshots), temporizadores, cancelación, versiones y reparación operativa.
Preguntas para aclarar primero
- ¿Cuáles son la concurrencia por inquilino, la tasa de inicio de flujos de trabajo y el tiempo máximo de espera?
- ¿Las actividades son funciones, contenedores o servicios HTTP externos, y qué efectos secundarios son irreversibles?
- ¿Cómo se autentican, autorizan y reemplazan los aprobadores? ¿Se requiere aprobación por quórum?
- ¿Pueden los reintentos repetir una actividad, y puede el servicio de negocio aceptar una clave de idempotencia?
- ¿Cómo se publican, congelan y migran las definiciones? ¿Siguen las instancias en ejecución una nueva versión?
- ¿Qué campos de auditoría deben retenerse y quién puede leerlos o alterarlos?
- ¿Son la cancelación, la pausa, el reintento manual o la omisión de pasos operaciones normales del producto?
Estructura de respuesta en 30 segundos
“Modelaría un flujo de trabajo como una máquina de estados duradera: almacenar versiones de definición, ejecuciones, intentos de actividad, un registro de eventos y una instantánea actual por separado. El programador entrega tareas de actividad reintentables; los workers informan los resultados con un ID de actividad, intento y clave de idempotencia, y la máquina de estados los desduplica. Una aprobación humana obtiene un token de un solo uso y de corta duración vinculado a la ejecución, el paso, el inquilino y la versión de aprobación. Los temporizadores, los tiempos de espera y la cancelación son eventos duraderos. Escalaría con colas particionadas y cuotas por inquilino, y luego validaría la recuperación mediante reproducción (replay), auditoría e inyección de fallas.”
Respuesta paso a paso
Defina primero el modelo de ejecución. La definición de un flujo de trabajo es una versión inmutable que contiene tipos de pasos, mapeos de entrada, tiempos de espera, políticas de reintento y reglas de compensación. Una ejecución hace referencia a una versión congelada, por lo que publicar una nueva definición no reescribe silenciosamente el historial. Reconstruya el estado a partir de un registro de eventos de solo anexión (append-only); utilice instantáneas para acelerar las lecturas. Las anexiones de eventos necesitan una condición de secuencia o versión para que los escritores concurrentes no se sobrescriban entre sí.
No llame a un servicio externo dentro de la transacción de la base de datos. Confirme un evento ActivityScheduled, deje que un programador ponga en cola la tarea, haga que un worker adquiera un arrendamiento (lease) y llame al servicio, luego confirme ActivitySucceeded, ActivityFailed o ActivityTimedOut. Acepte resultados solo para la ejecución, el paso y el intento esperados. Los resultados tardíos o duplicados son evidencia de auditoría, no transiciones de estado.
Un modelo de estado mínimo es:
| Entity | Key fields | Purpose |
|---|---|---|
| DefinitionVersion | tenant, definition, version, digest | Freeze steps and policies |
| WorkflowRun | run, definitionVersion, status, sequence | Track an instance |
| Event | run, sequence, type, payload, createdAt | Facts and replay |
| ActivityAttempt | step, attempt, lease, idempotencyKey, status | Delivery, lease, result |
| Approval | step, tokenHash, approver, expiresAt, status | Human approval credential |
| Timer | run, step, fireAt, generation, status | Wakeups, delays, timeouts |
La entrega es al menos una vez (at-least-once); el acuse de recibo de una cola no es la finalización del negocio. Los mensajes llevan ID de ejecución, ID de paso, intento, versión de definición y una clave de idempotencia. Un arrendamiento evita que dos consumidores trabajen a la vez, mientras que un fencing token o una escritura condicional rechaza a un worker desactualizado. La expiración del arrendamiento permite la reentrega, pero los efectos externos duplicados dependen del contrato de la actividad.
Maneje la idempotencia en varias capas. Los eventos de la máquina de estados utilizan una clave única (run, sequence). Los resultados de la actividad utilizan (run, step, idempotencyKey). Los pagos, correos electrónicos o escrituras en un servicio de negocio deben aceptar esa misma clave o aplicar una restricción de unicidad del negocio. Si una llamada tiene éxito y la respuesta se pierde, un reintento debe devolver un resultado ya completado o un duplicado seguro. No afirme exactamente una vez (exactly-once) de extremo a extremo. Para efectos no idempotentes, utilice revisión humana, compensación o una política de no reintento.
Una URL de aprobación no es autorización. Genere material aleatorio de alta entropía, almacene solo su hash y vincule el token al inquilino, ejecución, paso, acción y expiración. En el callback, valide el token, la firma o la identidad de inicio de sesión, la protección CSRF, el estado de un solo uso y el paso actual del flujo de trabajo. Vuelva a verificar la autorización del aprobador al momento del envío. Las aprobaciones y rechazos se convierten en eventos de auditoría a prueba de manipulaciones; un callback expirado no es válido.
Persista los temporizadores. Almacene fireAt y una generación al crear un tiempo de espera o una espera. Un programador escanea un índice o bucket de tiempo, reclama un temporizador vencido con una actualización condicional y desduplica repeticiones con condiciones de generación y estado. Los reinicios se recuperan desde el almacenamiento. Completar la aprobación o cancelación marca un temporizador antiguo como obsoleto; un sleep en memoria no debe retener a un worker durante días.
La cancelación, la pausa y la reparación son comandos explícitos de la máquina de estados. La cancelación registra la intención y evita el trabajo que no ha comenzado; un efecto externo en curso no se puede deshacer mágicamente, así que espere su resultado o ejecute una compensación. La omisión por parte del administrador, el reintento y las ediciones de entrada requieren autorización, un motivo, el estado anterior, el estado nuevo y un evento de auditoría. Nunca edite una instantánea directamente, o la reproducción producirá un resultado diferente.
Aísle las versiones por ejecución. Una nueva definición obtiene un nuevo digest; las ejecuciones existentes conservan la versión antigua de forma predeterminada. Una migración debe definir un estado y entradas compatibles, obtener cualquier aprobación requerida y anexar un evento WorkflowMigrated con ambas versiones. Los workers aceptan solo la versión de definición que ejecutan, evitando que una tarea antigua avance incorrectamente una nueva máquina de estados.
Escale particionando por inquilino o ID de ejecución. Otorgue cuotas de concurrencia a inquilinos de alto tráfico (hot tenants) y aísle los grupos de workers por tipo de actividad y prioridad. Anexe eventos al almacenamiento particionado, mantenga instantáneas e índices en una base de datos en línea, cifre cargas útiles sensibles y aplique la retención. Agregue contrapresión (backpressure), colas de mensajes no entregados (dead letters), métricas de arrendamiento y límites de tasa para que un solo flujo de trabajo descontrolado no consuma la capacidad global.
La observabilidad debe servir tanto a los operadores del entorno de ejecución como a los del negocio. Registre el paso actual, el motivo de la espera, los intentos, el retraso de la cola, el retraso del temporizador, el tiempo de permanencia de la aprobación, la amplificación de reintentos, el resultado de la compensación y la versión. Las trazas pueden llevar identificadores de ejecución, paso e intento, mientras que las entradas sensibles permanecen en el almacenamiento de auditoría controlado. La vista de operaciones debe explicar la siguiente acción; "esperando" no es "fallido".
Inyecte fallas: caída tras la confirmación del evento, mensajes duplicados, expiración del arrendamiento, actividad exitosa con respuesta perdida, aprobación reproducida, temporizador duplicado, interrupción de la base de datos, acumulación en cola, despliegue de versión interrumpido y cuota de inquilino agotada. Defina resultados observables: el estado anterior no retrocede, un efecto secundario no se repite sin un contrato, un token no se puede consumir dos veces y la recuperación eventualmente continúa o entra en un estado humano explícito.
Respuesta de muestra de alta calidad
“Construiría una máquina de estados duradera en lugar de permitir que los workers recuerden el estado del flujo de trabajo. Las versiones de definición son inmutables y cada ejecución fija una versión; el registro de eventos es la fuente de la verdad y la instantánea acelera las lecturas. La máquina de estados confirma un evento de programación, la cola entrega al menos una vez y el worker informa con ejecución, paso, intento y una clave de idempotencia. Los resultados duplicados o desactualizados son entradas de auditoría, no transiciones inválidas.
Una aprobación crea un token de un solo uso vinculado al inquilino, ejecución, paso y acción; solo se almacena su hash y expira. El callback verifica la identidad, los permisos, CSRF, el estado del token y el paso actual, luego consume condicionalmente el token y anexa un evento de auditoría. La aprobación, el rechazo, el tiempo de espera y la cancelación son eventos, no ediciones directas de instantáneas.
Los temporizadores persisten fireAt y la generación. Un programador basado en buckets los reclama con escrituras condicionales; los reinicios los recuperan y las activaciones duplicadas se desduplican. Los arrendamientos evitan workers concurrentes y los fencing tokens rechazan escrituras desactualizadas. Los pagos o correos electrónicos sin un contrato de idempotencia aguas abajo demostrado no pueden ser exactamente una vez; use claves aguas abajo, unicidad, compensación o manejo humano.
Las ejecuciones no siguen silenciosamente las nuevas definiciones; la migración anexa un evento con ambas versiones. Particione por inquilino y ejecución, aísle cuotas para inquilinos de alto tráfico y use grupos de workers por tipo de actividad. Organice por capas eventos de solo anexión, instantáneas en línea, datos de auditoría cifrados y retención. La inyección de fallas debe cubrir caídas, reentregas, respuestas perdidas, reproducción de callbacks, temporizadores duplicados y despliegues de versión interrumpidos.”
Errores comunes
- Tratar el acuse de recibo de una cola como finalización → un worker puede fallar después del efecto y recibir el mensaje de nuevo → desduplique con estado duradero y resultados idempotentes.
- Afirmar exactamente una vez → las transacciones locales no pueden garantizar efectos secundarios distribuidos → establezca entrega al menos una vez e idempotencia aguas abajo, compensación o manejo humano.
- Tratar una URL de aprobación como autorización → la filtración o reproducción puede otorgar acceso → vincule identidad, inquilino, paso, acción, expiración y estado de un solo uso.
- Hacer sleep en memoria durante días → el reinicio y el escalado pierden la espera → persista temporizadores y despierte a través del programador.
- Editar instantáneas directamente → la reproducción produce un resultado diferente → utilice comandos de máquina de estados autorizados y eventos de auditoría.
- Compartir una cola entre inquilinos → un inquilino de alto tráfico deja sin recursos a los demás → particione, asigne cuotas, priorice y aplique contrapresión.
- Permitir que nuevas definiciones afecten a ejecuciones antiguas → los flujos de trabajo en ejecución se vuelven inexplicables → fije versiones y haga explícita la migración.
Preguntas de seguimiento y respuestas
Pregunta de seguimiento 1: La actividad tuvo éxito pero el worker falló antes de escribir el resultado. ¿Puede un reintento cobrar dos veces?
Si el servicio aguas abajo admite una clave de idempotencia, reintente con la misma clave y mapee el resultado completado. De lo contrario, no reintente a ciegas: consulte el estado aguas abajo, pase a gestión humana o compense. La máquina de estados puede hacer que sus propios eventos sean consistentes, pero no puede crear semántica de pago exactamente una vez por sí misma.
Pregunta de seguimiento 2: Un aprobador hace clic en aprobar dos veces. ¿Qué sucede?
Permita un consumo exitoso del token con una actualización condicional o restricción de unicidad de pending a approved. La segunda solicitud devuelve que ya ha sido procesada o expirada y no avanza el flujo de trabajo. Ambas solicitudes e identidades permanecen en el registro de auditoría.
Pregunta de seguimiento 3: ¿Cómo se admite "dos aprobadores cualesquiera"?
Persista una bandeja de entrada de aprobaciones y una versión de política. Registre una decisión desduplicada por aprobador, incluida la instantánea de autorización. Avance solo cuando se alcance el quórum; el rechazo, la revocación y el reemplazo del aprobador son comandos explícitos.
Pregunta de seguimiento 4: Un operador necesita omitir un paso con urgencia. ¿Cuál es el camino seguro?
Defina primero los roles autorizados, los pasos que se pueden omitir y las condiciones de seguridad. Emita un comando StepSkipped razonado con el estado anterior, el estado nuevo, el operador y la aprobación. Si la omisión viola una invariante de negocio, rechácela y ofrezca compensación o terminación en lugar de editar la base de datos.
Pregunta de seguimiento 5: ¿Cómo evita que un inquilino cree un flujo de trabajo ilimitado?
Limite pasos, profundidad de ramificación, concurrencia de actividades, tamaño del historial, recuento de temporizadores y tiempo de ejecución total por inquilino. Valide las definiciones estáticamente y mida la ejecución. Exceder una cuota pausa o rechaza el trabajo con un motivo visible para el operador en lugar de permitir un crecimiento sin límites.