Temporal es una herramienta open source que permite modelar procesos de larga duración directamente en código, con garantías de fiabilidad y resiliencia ante fallos. En este artículo explico sus conceptos fundamentales, workflows, actividades y señales, y los pongo en práctica implementando en Java un proceso de compra de entradas con múltiples microservicios implicados.
Desde hace tiempo tenía pendiente investigar y entender Cadence, tras un tiempo leyendo su documentación no tenía claro cómo funcionaba, qué funcionalidad cubría y como funcionaba. Sin embargo, al preguntarle a Claude me indicó, como ocurre ocurre en muchos casos, los desarrolladores originales han creado un fork, en el caso de Cadence el fork es Temporal.
Tras unos días y siguiendo la buena documentación ofrecida por temporal tengo un ejemplo con el que entender, practicar y usar un pequeño proceso. Con la documentación y el ejemplo pensar en posibles casos de uso al dar a solución a necesidades.
En la web dan algunos ejemplos de casos de uso de Temporal y en su página de inicio una descripción introductoria de la herramienta.
Procesos con Temporal
De forma muy resumida Temporal es una herramienta que permite modelar procesos que pueden durar minutos, días, meses o años e integrar interacciones humanas. Además, la implementación de los procesos se realiza con garantías de fiabilidad y resiliencia al considerar que cualquier paso del proceso puede fallar e implementando políticas de reintentos.
Temporal tiene otras características diferenciadoras. Las herramientas tradicionales para modelar procesos se basan en la definición de un proceso en una notación textual como xml y su entorno de ejecución es el servidor de procesos. En Temporal por el contrario el proceso se define en código en alguno de los diferentes lenguajes de programación soportados con mayores capacidades que un archivo descriptor.
Sus casos de uso son los siguientes:
- Máquinas de estados complejas: Modelar entidades de negocio con ciclos de vida largos y múltiples transiciones de estado, como un pedido que pasa por creado, reservado, pagado, enviado y entregado a lo largo de días.
- Procesos de larga duración con esperas humanas: Flujos de aprobación, onboarding de usuarios o procesos de KYC donde el workflow queda en espera de una acción humana durante horas, días, semanas, meses o años y luego continúa.
- Sagas y transacciones distribuidas: Implementar el patrón saga para gestionar la consistencia eventual entre microservicios con compensaciones automáticas si algún paso falla, evitando inconsistencias de datos.
- Tareas programadas y recurrentes: Con la funcionalidad de schedule, sustituir cronjobs frágiles por procesos planificados con reintentos, historial y observabilidad.
Casos de no uso son los siguientes:
- Procesos simples y de corta duración: si el proceso se resuelve en una sola llamada HTTP o una transacción de base de datos, Temporal añade una complejidad innecesaria. Para esos casos un simple servicio REST o una cola de mensajes como Kafka es suficiente.
- Alta frecuencia y baja latencia: Temporal no está diseñado para procesos que requieren respuesta en milisegundos o millones de ejecuciones por segundo, como procesamiento de eventos en tiempo real o sistemas de trading de alta frecuencia.
- Equipos pequeños sin necesidad de resiliencia distribuida: Si la aplicación es un monolito o un sistema sencillo, introducir Temporal implica operar un servidor adicional con su base de datos, lo que puede no justificarse.
- Lógica puramente reactiva a eventos: Si el sistema ya está bien modelado con event sourcing y CQRS y los procesos son stateless, herramientas como Kafka Streams o Apache Flink encajan mejor de forma natural.
- Procesos con requisitos de latencia estrictos en cada actividad: Temporal introduce overhead en la coordinación entre el servidor y los workers, por lo que si cada actividad del proceso tiene un SLA de milisegundos, ese overhead puede ser un problema.
Conceptos
Temporal define los siguientes conceptos.
- Workflows que representan los procesos y se implementan en código de un lenguaje de programación. En Java los workflows tienen la definición de una interfaz con un método que inicia el proceso, métodos para recibir mensajes que permiten consultar el estado del proceso y cambiar el estado. El proceso en su implementación invoca los pasos del proceso o actividades.
- Las actividades son los pasos del proceso, se ejecutan como consecuencia del progreso del proceso, cambio estado o de los mensajes que recibe el proceso.
- Las señales son la forma de comunicarse con el proceso, obtener información, cambiar el flujo de ejecución de forma síncrona y asíncrona.
- Finalmente los workers, son las unidades de cómputo que ejecutan el proceso y las actividades.
Temporal es un binario que hace de servidor coordinando la ejecución de los procesos, gestionando los cambios de estado y almacenando el estado del proceso. Registra el inicio de una nueva instancia de un proceso, los datos de entrada y salida de cada actividad junto a otros metadatos como cuanto tiempo tarda cada paso.
En modo desarrollo el estado de los procesos se almacena en memoria. En un entorno de producción el estado se aconseja guardar en una base de datos como PostgreSQL.
Esta supervisión de los procesos permite a Temporal dar visibilidad de los procesos que es una característica que modelan los procesos con coreografía está distribuída entre los diferentes procesos.
Funcionalidades
Temporal soporta diferentes funcionalidades, en la documentación se detallan entre las que están:
- Schedule: permite planificar procesos cada cierto tiempo.
- Composability: es posible crear e invocar subprocesos.
- Encryption: permite cifrar los datos del proceso, algunos de los datos pueden ser sensibles.
- Observability: se recogen métricas a raíz del estado del proceso.
- Testing: es posible crear diferentes tipos de teses unitarios, de integración y end-to-end.
Definición de un proceso
Supongamos que tenemos un proceso de compra de entradas. El sistema ha sido desarrollado utilizando domain driven design y microservicios separando las funciones del sistema en diferentes subdominios y se compone del flujo hasta que el order queda en estado confirmada.
Tras el estado de confirmación o cancelación de la orden comenzaría un siguiente proceso, subprocesos asociados o faltaría definir más pasos en el proceso hasta que el producto o servicio por el que se ha pagado ha sido entregado al cliente. Para el ejemplo es suficiente hasta confirmación.
Implementar este proceso en un sistema distribuido de microservicios en el que hay múltiples puntos de fallo basta que alguna de las actividades temporalmente no funcione para generar inconsistencias de datos aún con reintentos en los procesos de esta coreografía de microservicios.
Que pasa si, ¿se reservan los tickets, falla la autorización y no se pueden liberar los tickets temporalmente? o ¿si se crea el order pero no se puede notificar la decisión al sistema de fraude si finalmente se acepta la transacción?. Además al estar el proceso distribuido en diferentes servicios es difícil tener visibilidad con información completa del estado del proceso y pasos por los que ha pasado.
Generalmente un sistema con coreografía funciona pero en ciertos errores el proceso deja un estado inconsistente las transacciones, investigar y resolver las inconsistencias de datos, si la resolución es manual es una actividad que consume gran cantidad de tiempo.
Temporal proporciona soluciones a estos problemas a un proceso vital en cualquier dominio como son las transacciones como este en el proceso de compra.
Ejemplo con Temporal
Esta es la definición del proceso en el lenguaje de programación Java. El proceso es simplemente una interfaz con anotaciones.
Workflow
La definición del workflow.
1
2
3
4
5
6
7
8
9
10
11
|
package io.github.picodotdev.blogbitix.temporal.workflow;
import io.temporal.workflow.WorkflowInterface;
import io.temporal.workflow.WorkflowMethod;
@WorkflowInterface
public interface PlaceOrderWorkflow {
@WorkflowMethod
void placeOrder(Long listingId, int quantity);
}
|
PlaceOrderWorkflow.java
Esta es la implementación del proceso. Contiene las diferentes actividades del proceso que invocan diferentes sistemas. No contiene más unas pocas sentencias de código haciendo uso de las actividades del proceso. Como si fuera una secuencia de intrucciones u orquestado en un patrón saga.
1
2
3
4
5
6
7
8
9
10
11
|
package io.github.picodotdev.blogbitix.temporal.workflow;
import io.temporal.workflow.WorkflowInterface;
import io.temporal.workflow.WorkflowMethod;
@WorkflowInterface
public interface PlaceOrderWorkflow {
@WorkflowMethod
void placeOrder(Long listingId, int quantity);
}
|
PlaceOrderWorkflow.java
Activities
La definición en interfaces.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package io.github.picodotdev.blogbitix.temporal.activities;
import io.temporal.activity.ActivityInterface;
import io.temporal.activity.ActivityMethod;
@ActivityInterface
public interface FraudActivities {
@ActivityMethod
FraudDecide decide(Long orderId);
@ActivityMethod
void authorizeFailure(Long orderId);
@ActivityMethod
void decision(Long orderId, Long auhtorizeId, FraudDecision decision);
enum FraudDecide {
APPROVE, REJECT
}
enum FraudDecision {
APPROVE, REJECT
}
}
|
FraudActivities.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package io.github.picodotdev.blogbitix.temporal.activities;
import io.temporal.activity.ActivityInterface;
import io.temporal.activity.ActivityMethod;
@ActivityInterface
public interface InventoryActivities {
@ActivityMethod
void bookTickets(Long listingId, int quantity) throws ReserveTicketsException;
@ActivityMethod
void releaseTickets(Long listingId, int quantity) throws ReleaseTicketsException;
}
|
InventoryActivities.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package io.github.picodotdev.blogbitix.temporal.activities;
import io.temporal.activity.ActivityInterface;
import io.temporal.activity.ActivityMethod;
@ActivityInterface
public interface OrderActivities {
@ActivityMethod
Long generateOrderId();
@ActivityMethod
void createOrder(Long orderId, Long listingId, int quantity);
@ActivityMethod
OrderStatus approveOrder(Long orderId);
enum OrderStatus {
CREATED, APPROVED, CANCELLED
}
}
|
OrderActivities.java
1
2
3
4
5
6
7
8
9
10
11
|
package io.github.picodotdev.blogbitix.temporal.activities;
import io.temporal.activity.ActivityInterface;
import io.temporal.activity.ActivityMethod;
@ActivityInterface
public interface PaymentActivities {
@ActivityMethod
Long authorizePayment();
}
|
PaymentActivities.java
La implementación. Para el caso del ejemplo las actividades no invocan sistemas externos, simplemente emiten un mensaje. Los procesos han de ser reproducibles y reconstruibles, por tanto algunas funcionalidades han de usar las facilidades de Temporal, especialmente en el manejo de tiempo y datos aleatorios.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package io.github.picodotdev.blogbitix.temporal.activities;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class DefaultFraudActivities implements FraudActivities {
private static final Logger logger = LogManager.getLogger(DefaultFraudActivities.class);
@Override
public FraudDecide decide(Long orderId) {
logger.info("Fraud decide (orderId: {})", orderId);
return FraudDecide.APPROVE;
}
@Override
public void authorizeFailure(Long orderId) {
logger.info("Fraud authorize failure (orderId: {})", orderId);
}
@Override
public void decision(Long orderId, Long authorizeId, FraudDecision decision) {
logger.info("Fraud decision (orderId: {}, authorizeId: {}, decision: {})", orderId, authorizeId, decision);
}
}
|
DefaultFraudActivities.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package io.github.picodotdev.blogbitix.temporal.activities;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class DefaultInventoryActivities implements InventoryActivities {
private static final Logger logger = LogManager.getLogger(DefaultInventoryActivities.class);
@Override
public void bookTickets(Long listingId, int quantity) throws ReserveTicketsException {
logger.info("Inventory book tickets (listingId: {}, quantity: {})", listingId, quantity);
}
@Override
public void releaseTickets(Long listingId, int quantity) throws ReleaseTicketsException {
logger.info("Inventory release tickets (listingId: {}, quantity: {})", listingId, quantity);
}
}
|
DefaultInventoryActivities.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package io.github.picodotdev.blogbitix.temporal.activities;
import java.util.Random;
import io.temporal.workflow.Workflow;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class DefaultOrderActivities implements OrderActivities {
private static final Logger logger = LogManager.getLogger(DefaultOrderActivities.class);
@Override
public Long generateOrderId() {
Long orderId = new Random().nextLong(0, 1_000_000);
logger.info("Order generate id (orderId: {})", orderId);
return orderId;
}
@Override
public void createOrder(Long orderId, Long listingId, int quantity) {
logger.info("Order create (orderId: {}, listingId: {}, quantity; {})", orderId, listingId, quantity);
}
@Override
public OrderStatus approveOrder(Long orderId) {
logger.info("Order approve (orderId: {})", orderId);
return OrderStatus.APPROVED;
}
}
|
DefaultOrderActivities.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package io.github.picodotdev.blogbitix.temporal.activities;
import java.util.Random;
import io.temporal.workflow.Workflow;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class DefaultPaymentActivities implements PaymentActivities {
private static final Logger logger = LogManager.getLogger(DefaultPaymentActivities.class);
@Override
public Long authorizePayment() {
Long paymentId = new Random().nextLong(0, 1_000_000);
logger.info("Payments authorize (paymentId: {})", paymentId);
return paymentId;
}
}
|
DefaultPaymentActivities.java
Worker
El worker registra que workflows y las activities va a ejecutar.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
package io.github.picodotdev.blogbitix.temporal.workers;
import io.temporal.client.WorkflowClient;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.worker.Worker;
import io.temporal.worker.WorkerFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
...
import static io.github.picodotdev.blogbitix.temporal.Main.PLACE_ORDER_WORKFLOW_TASK_QUEUE;
public class PlaceOrderWorkflowWorker {
private static final Logger logger = LogManager.getLogger(PlaceOrderWorkflowWorker.class);
private WorkflowServiceStubs service;
private WorkflowClient client;
private WorkerFactory factory;
public PlaceOrderWorkflowWorker() {
this.service = WorkflowServiceStubs.newLocalServiceStubs();
this.client = WorkflowClient.newInstance(service);
this.factory = WorkerFactory.newInstance(client);
}
public void start() {
Worker worker = factory.newWorker(PLACE_ORDER_WORKFLOW_TASK_QUEUE);
worker.registerWorkflowImplementationTypes(DefaultPlaceOrderWorkflow.class);
worker.registerActivitiesImplementations(new DefaultFraudActivities());
worker.registerActivitiesImplementations(new DefaultOrderActivities());
worker.registerActivitiesImplementations(new DefaultPaymentActivities());
worker.registerActivitiesImplementations(new DefaultInventoryActivities());
logger.info("Starting PlaceOrderWorkflowWorker...");
factory.start();
}
public void shutdown() {
factory.shutdown();
}
}
|
PlaceOrderWorkflowWorker.java
Programa
Luego arranca una instancia el worker y el workflow,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package io.github.picodotdev.blogbitix.temporal;
...
@SpringBootApplication
public class Main implements ApplicationRunner {
public static final String PLACE_ORDER_WORKFLOW_TASK_QUEUE = "place-order-workflow-task-queue";
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
PlaceOrderWorkflowWorker worker = new PlaceOrderWorkflowWorker();
Workflow workflow = new Workflow();
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> worker.start());
Thread.sleep(1000);
executor.submit(() -> workflow.run());
Thread.sleep(5000);
worker.shutdown();
workflow.shutdown();
executor.shutdown();
}
}
|
Main.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package io.github.picodotdev.blogbitix.temporal.workflow;
import java.util.UUID;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowOptions;
import io.temporal.serviceclient.WorkflowServiceStubs;
import static io.github.picodotdev.blogbitix.temporal.Main.PLACE_ORDER_WORKFLOW_TASK_QUEUE;
public class Workflow {
private WorkflowServiceStubs service;
private WorkflowClient client;
public Workflow() {
this.service = WorkflowServiceStubs.newLocalServiceStubs();
this.client = WorkflowClient.newInstance(service);
}
public void run() {
UUID uuid = UUID.randomUUID();
PlaceOrderWorkflow workflow = client.newWorkflowStub(PlaceOrderWorkflow.class,
WorkflowOptions.newBuilder()
.setTaskQueue(PLACE_ORDER_WORKFLOW_TASK_QUEUE)
.setWorkflowId(uuid.toString())
.build()
);
workflow.placeOrder(1L, 3);
}
public void shutdown() {
service.shutdown();
service.awaitTermination(10, java.util.concurrent.TimeUnit.SECONDS);
}
}
|
Workflow.java
Servicio de Temporal
El servicio de temporal se inicia con el siguiente comando en modo desarrollo.
1
2
|
temporal server start-dev
|
temporal.sh
Esta es la salida en la consola de la ejecución del workflow.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
2026-05-07 20:19:25,156 INFO io.temporal.serviceclient.WorkflowServiceStubsImpl Created WorkflowServiceStubs for channel: ManagedChannelOrphanWrapper{delegate=ManagedChannelImpl{logId=1, target=127.0.0.1:7233}}
2026-05-07 20:19:25,244 INFO io.temporal.serviceclient.WorkflowServiceStubsImpl Created WorkflowServiceStubs for channel: ManagedChannelOrphanWrapper{delegate=ManagedChannelImpl{logId=3, target=127.0.0.1:7233}}
2026-05-07 20:19:25,278 INFO codotdev.blogbitix.temporal.workers.PlaceOrderWorkflowWorker Starting PlaceOrderWorkflowWorker...
2026-05-07 20:19:25,382 INFO io.temporal.internal.worker.MultiThreadedPoller start: MultiThreadedPoller{name=Workflow Poller taskQueue="place-order-workflow-task-queue", namespace="default", identity=691@localhost}
2026-05-07 20:19:25,386 INFO io.temporal.internal.worker.MultiThreadedPoller start: MultiThreadedPoller{name=Activity Poller taskQueue="place-order-workflow-task-queue", namespace="default", identity=691@localhost}
2026-05-07 20:19:26,453 INFO odotdev.blogbitix.temporal.activities.DefaultOrderActivities Order generate id (orderId: 196969)
2026-05-07 20:19:26,470 INFO odotdev.blogbitix.temporal.activities.DefaultFraudActivities Fraud decide (orderId: 196969)
2026-05-07 20:19:26,491 INFO dev.blogbitix.temporal.activities.DefaultInventoryActivities Inventory book tickets (listingId: 1, quantity: 3)
2026-05-07 20:19:26,497 INFO otdev.blogbitix.temporal.activities.DefaultPaymentActivities Payments authorize (paymentId: 848778)
2026-05-07 20:19:26,504 INFO odotdev.blogbitix.temporal.activities.DefaultOrderActivities Order create (orderId: 196969, listingId: 1, quantity; 3)
2026-05-07 20:19:26,513 INFO odotdev.blogbitix.temporal.activities.DefaultFraudActivities Fraud decision (orderId: 196969, authorizeId: 848778, decision: APPROVE)
2026-05-07 20:19:26,520 INFO odotdev.blogbitix.temporal.activities.DefaultOrderActivities Order approve (orderId: 196969)
2026-05-07 20:19:31,252 INFO io.temporal.worker.WorkerFactory shutdown: WorkerFactory{identity=691@localhost}
|
System.out
La línea de comandos de Temporal permite iniciar workflows, actividades y enviar mensajes además de consultar el estado de los workflows. Temporal tiene una interfaz gráfica que permite visualizar y ver el estado de la ejecución de cada proceso, con los datos de entrada y salida de las actividades así como los tiempos de inicio y fin. Esta observabilidad es una característica que no tiene un sistema distribuido que emplea coreografía cuando hay que examinar en que estado está el proceso por alguna inconsistencia de datos.
Finalmente, es posible construir teses unitarios para el proceso.
Videos de introducción
El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run