Durable Task en .NET: reanudar workflows sin perder el control del efecto
Durable Task recuerda el progreso y reanuda workflows; puede reentregar actividades, así que tu backend necesita claves estables, ledger y reconciliación ante respuestas perdidas.
23 de octubre de 2026 · Leopoldo Benavente Cadena · 7 min de lectura
Un proceso que espera una aprobación, llama a varios servicios y puede durar días necesita recordar algo más que la pila de llamadas de un worker. Si el proceso reinicia mientras espera, una cola y una tabla de estado pueden bastar; si además hay timers, señales externas, fan-out/fan-in y recuperación de pasos, una orquestación durable puede hacer explícita esa secuencia.
La frontera importante está en otro sitio: que el runtime recuerde el workflow no significa que un proveedor externo recuerde el efecto. Una actividad puede llegar al proveedor, crear un recurso y perder la respuesta antes de registrar su resultado. Si el runtime entrega otra vez esa actividad, repetirla sin identidad estable puede duplicar el efecto; asumir que falló puede dejar el sistema desincronizado.
Qué persiste la orquestación
Durable Task es la implementación de durable execution de Microsoft. Sus workflows pueden durar horas, días o meses y recuperar el progreso después de un crash, reinicio o redeployment. El modelo separa cuatro piezas: el cliente inicia y consulta instancias, el orquestador decide la secuencia, las actividades ejecutan unidades de trabajo y las entidades mantienen pequeñas porciones de estado.
El orquestador debe ser determinista porque el runtime reconstruye su estado mediante event sourcing y replay. Su código puede programar una actividad, esperar un timer o esperar un evento externo; no debe hacer HTTP, leer directamente el reloj del sistema, crear un GUID aleatorio ni consultar una base de datos como si fuera una actividad. Esos efectos pertenecen a la actividad, donde se pueden observar y proteger.
El siguiente es un fragmento conceptual, no una aplicación compilable ni una prueba ejecutada: omite los tipos, el registro de tareas y el hosting. Solo muestra la frontera usando la API documentada de TaskOrchestrationContext; la combinación concreta de API, paquetes y hosting debe validarse contra la versión elegida:
public static Task<Receipt> RunAsync(
TaskOrchestrationContext context,
SubmitCommand command) =>
context.CallActivityAsync<Receipt>(
nameof(SubmitOperation), command);El fragmento solo coordina. SubmitOperation es el límite donde viven el cliente HTTP, la base de datos o el adaptador del proveedor. La llamada a la actividad puede volver a entregarse; el código de la actividad debe poder consultar el estado existente o marcarlo para reconciliación.
La actividad reentregable necesita una identidad
Usa una clave de operación estable, derivada del proceso de negocio y persistida antes de ejecutar el primer intento. Acompáñala con una huella del payload. Antes de cualquier I/O externo, el adaptador debe reclamar la clave con una operación atómica protegida por una restricción única: el primer worker inserta la fila con estado InProgress y solo ese ganador llama al proveedor. El worker que pierde la competencia consulta la fila y espera o reconcilia; no ejecuta otro efecto por haber observado una ausencia momentánea. Esa reclamación serializa la aplicación, pero no es una transacción con el proveedor: este debe aceptar la clave y deduplicar, o el adaptador debe resolver la ventana en la que el efecto fue aceptado y el recibo todavía no quedó persistido. Nada de esto constituye exactly-once.
| Situación del ledger | Respuesta del adaptador |
|---|---|
| La reclamación atómica gana | Inserta la clave única como InProgress antes del I/O; solo ese worker llama al proveedor. |
La clave está InProgress, Unknown, NeedsReconciliation o Failed sin recibo |
Espera, consulta o reconcilia según la política; nunca devuelve un recibo inexistente ni repite el efecto a ciegas. |
La clave está Succeeded, tiene el mismo payload y el recibo está persistido |
Devuelve el recibo existente; no crea otro efecto. |
Existe la clave con otra huella, incluso si está InProgress |
Rechaza el conflicto antes de esperar y pide una nueva clave. |
Imagina una actividad de aprovisionamiento: envía una solicitud con operationKey=tenant-42-onboarding-7, el proveedor confirma el recurso y el proceso cae antes de recibir la respuesta. En la siguiente entrega, el adaptador debe preguntar por esa identidad o consultar su ledger. Un retry puede ayudar a un error transitorio, pero no decide si el primer efecto llegó a existir.
La huella evita que una clave se reutilice silenciosamente para otro contenido. El ledger también debe distinguir, como mínimo, Unknown, Succeeded, Failed y NeedsReconciliation. Una compensación posterior puede ser útil, pero borrar un recurso no es un rollback universal de una operación ya aceptada.
Replay, reanudación y reconciliación no son lo mismo
El replay reconstruye la historia que Durable Task ya conoce. Durante un replay compatible, una actividad cuyo resultado ya está en la historia no se invoca automáticamente otra vez. La redelivery es posible cuando el runtime no registró el resultado de la entrega anterior; esa es la ventana que obliga al adaptador a consultar o deduplicar. La reanudación permite continuar el workflow después de una espera o un reinicio. La reconciliación compara esa historia con una autoridad externa: un ledger de pagos, el API de infraestructura o el destino que recibió un webhook. Solo la tercera puede resolver una respuesta perdida.
Esto también cambia la forma de modelar una aprobación. El orquestador puede esperar un evento humano y después llamar a una actividad que aplica la decisión. La identidad de la aprobación y la versión de la solicitud deben viajar con la actividad; el sistema externo debe rechazar una versión vieja o devolver el resultado que ya registró. En un flujo de webhooks, una entidad durable puede serializar su propio contador, pero el destino todavía necesita una valla de revisión o un único escritor.
Durable Task no ofrece una propiedad exactly-once para pagos, APIs, índices o recursos remotos. Puede coordinar el orden y conservar el progreso; la deduplicación y la autoridad del efecto siguen siendo responsabilidades del backend.
Estado del SDK, hosting y coste
Conviene separar las etiquetas que suelen mezclarse:
| Pieza | Estado o alcance consultado | Coste que debes considerar |
|---|---|---|
Paquetes base Microsoft.DurableTask.Worker y Microsoft.DurableTask.Client.Grpc |
1.27.0 es la versión actual consultada, publicada el 19-10-2026. 1.26.0 queda como referencia histórica del post del 12-10-2026, no como versión actual. |
El paquete no cobra por sí mismo; pagas el cómputo de tu aplicación y el backend elegido. |
| SDK .NET conectado a Azure Managed | La tabla actual de SDKs de Microsoft Learn lo marca GA; su catálogo de paquetes Azure Managed y su hosting se mantienen separados de los paquetes base anteriores. | El paquete no cobra por sí mismo; pagas el cómputo de tu aplicación y el backend elegido. |
| SDK JavaScript/TypeScript conectado al backend administrado | Preview, según la misma tabla. No extrapoles esa etiqueta al SDK .NET. | Además del cómputo, aplica el coste del backend administrado. |
| Durable Task Scheduler | Backend administrado con SKU Consumption o Dedicated. | Scheduler y cómputo se facturan por separado: Consumption cuenta acciones; Dedicated cobra capacidad provisionada. Los precios dependen de región y cambian. |
| SDK portable en tu proceso | Bibliotecas abiertas que pueden ejecutarse en Azure Container Apps, Kubernetes o máquinas virtuales y conectarse al backend compatible. | No hay una tarifa del SDK; sí costes de proceso, almacenamiento, red, observabilidad y del backend. |
La documentación de facturación calcula el consumo del Scheduler por acciones despachadas y describe retenciones y límites distintos por SKU. No confundas que una API .NET esté en GA con que una capacidad adicional, una región o un modo de hosting tenga el mismo estado. Para evaluar una instalación concreta, fija el paquete, el worker, el cliente, el backend y el hosting como una sola combinación.
Para desarrollo local, Microsoft documenta un emulador del Scheduler que se ejecuta en Docker. Eso permite validar la forma de registrar un worker y consultar una orquestación, pero no prueba la semántica de tu proveedor externo ni sus costes de producción. El SDK también puede alojarse en un proceso propio; en ambos casos la pregunta central sigue siendo quién confirma el efecto.
Cuándo compensa
Una tabla de estado, una cola y un outbox pueden ser la opción más pequeña para un job corto con pocos pasos. Durable Task empieza a compensar cuando el proceso debe esperar a una persona, recibir callbacks fuera de orden, coordinar ramas paralelas o continuar tras reinicios sin reconstruir manualmente toda la máquina de estados.
Antes de adoptarlo, dibuja la frontera de cada actividad y enumera sus estados inciertos. Si no puedes decir qué identidad consulta el adaptador después de una caída, todavía falta el diseño de idempotencia. Si el proveedor no permite consultar ni deduplicar, registra la incertidumbre y crea una ruta operativa de reconciliación; un segundo intento automático no sustituye esa decisión.
Comentarios