eva4j 1.0.17 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/DOMAIN_YAML_GUIDE.md +3 -1
- package/QUICK_REFERENCE.md +8 -4
- package/bin/eva4j.js +70 -2
- package/config/defaults.json +1 -0
- package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
- package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
- package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
- package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
- package/docs/commands/EVALUATE_SYSTEM.md +272 -8
- package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
- package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
- package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
- package/docs/commands/INDEX.md +27 -3
- package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
- package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
- package/docs/prototype/system/RISKS.md +277 -0
- package/docs/prototype/system/customers.yaml +133 -0
- package/docs/prototype/system/inventory.yaml +109 -0
- package/docs/prototype/system/notifications.yaml +131 -0
- package/docs/prototype/system/orders.yaml +241 -0
- package/docs/prototype/system/payments.yaml +256 -0
- package/docs/prototype/system/products.yaml +168 -0
- package/docs/prototype/system/system.yaml +269 -0
- package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
- package/examples/domain-read-models.yaml +2 -2
- package/examples/system/customer.yaml +89 -0
- package/examples/system/orders.yaml +119 -0
- package/examples/system/product.yaml +27 -0
- package/examples/system/system.yaml +80 -0
- package/package.json +1 -1
- package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
- package/src/agents/design-gap-analyst.agent.md +383 -0
- package/src/agents/design-reviewer-temporal.agent.md +412 -0
- package/src/agents/design-reviewer.agent.md +31 -5
- package/src/agents/implement-use-cases.prompt.md +179 -0
- package/src/agents/ux-gap-analyst.agent.md +412 -0
- package/src/commands/add-rabbitmq-client.js +261 -0
- package/src/commands/add-temporal-client.js +22 -2
- package/src/commands/build.js +267 -11
- package/src/commands/evaluate-system.js +700 -13
- package/src/commands/generate-entities.js +308 -16
- package/src/commands/generate-rabbitmq-event.js +665 -0
- package/src/commands/generate-rabbitmq-listener.js +205 -0
- package/src/commands/generate-temporal-activity.js +968 -34
- package/src/commands/generate-temporal-flow.js +95 -38
- package/src/commands/generate-temporal-system.js +708 -0
- package/src/skills/build-system-yaml/SKILL.md +222 -2
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +50 -4
- package/src/skills/build-system-yaml/references/module-spec.md +57 -8
- package/src/skills/build-temporal-system/SKILL.md +752 -0
- package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
- package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
- package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
- package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
- package/src/skills/implement-use-case/SKILL.md +350 -0
- package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
- package/src/skills/requirements-elicitation/SKILL.md +228 -0
- package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
- package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
- package/src/utils/bounded-context-diagram.js +844 -0
- package/src/utils/domain-validator.js +266 -4
- package/src/utils/naming.js +10 -0
- package/src/utils/system-validator.js +169 -11
- package/src/utils/system-yaml-parser.js +318 -0
- package/src/utils/temporal-validator.js +497 -0
- package/src/utils/yaml-to-entity.js +10 -7
- package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
- package/templates/aggregate/JpaAggregateRoot.java.ejs +2 -2
- package/templates/aggregate/JpaEntity.java.ejs +2 -2
- package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
- package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
- package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
- package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
- package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
- package/templates/base/root/AGENTS.md.ejs +1 -1
- package/templates/crud/EndpointsController.java.ejs +1 -1
- package/templates/crud/ScaffoldCommand.java.ejs +5 -2
- package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
- package/templates/crud/ScaffoldQuery.java.ejs +5 -2
- package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
- package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
- package/templates/evaluate/report.html.ejs +1447 -90
- package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
- package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
- package/templates/ports/PortAclMapper.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +7 -22
- package/templates/ports/PortFeignClient.java.ejs +4 -0
- package/templates/ports/PortResponseDto.java.ejs +1 -1
- package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
- package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
- package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
- package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
- package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
- package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
- package/templates/read-model/ReadModelJpa.java.ejs +1 -1
- package/templates/read-model/ReadModelJpaRepository.java.ejs +2 -0
- package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
- package/templates/read-model/ReadModelRepositoryImpl.java.ejs +9 -5
- package/templates/read-model/ReadModelSyncHandler.java.ejs +2 -0
- package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
- package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
- package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
- package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
- package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
- package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
- package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/NestedType.java.ejs +12 -0
- package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
- package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
- package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
- package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
- package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
- package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
- package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
- package/COMMAND_EVALUATION.md +0 -911
- package/test-c2010.js +0 -49
- package/test-update-compat.js +0 -109
- package/test-update-lifecycle.js +0 -121
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
# Patrones de Comunicación entre Microservicios con Temporal
|
|
2
|
+
|
|
3
|
+
## 📋 Propósito
|
|
4
|
+
|
|
5
|
+
Cuando Temporal actúa como broker de orquestación, los microservicios se comunican de formas distintas según el caso de uso. Este documento describe los **4 patrones principales**, cuándo usar cada uno, y ejemplos concretos aplicados a un e-commerce.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🔄 Resumen Comparativo
|
|
10
|
+
|
|
11
|
+
| Patrón | Acoplamiento | Dirección | Espera respuesta | Caso de uso |
|
|
12
|
+
|--------|-------------|-----------|------------------|-------------|
|
|
13
|
+
| **Remote Activity** | Medio | Request → Response | ✅ Sí (bloqueante) | Operaciones atómicas cross-service |
|
|
14
|
+
| **Remote Activity + Async** | Medio | Request → Response (paralelo) | ✅ Sí (no bloqueante) | Múltiples operaciones independientes en paralelo |
|
|
15
|
+
| **Child Workflow** | Alto | Parent → Child | ✅ Sí | Subprocesos con ciclo de vida propio |
|
|
16
|
+
| **Signal External** | Bajo | Fire & Forget | ❌ No | Notificaciones entre workflows activos |
|
|
17
|
+
| **Signal + Await** | Bajo | Bidireccional | ✅ Sí (con espera) | Esperar evento externo con timeout |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 1. Remote Activity
|
|
22
|
+
|
|
23
|
+
### Concepto
|
|
24
|
+
|
|
25
|
+
Un workflow en el **Servicio A** invoca una actividad cuya implementación vive en el **Servicio B**. Temporal enruta la tarea al worker correcto a través del **Task Queue**. No hay HTTP entre servicios.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Servicio A (workflow) Temporal Server Servicio B (worker)
|
|
29
|
+
│ │ │
|
|
30
|
+
│ stub.doSomething(data) │ │
|
|
31
|
+
│───────────────────────────►│ │
|
|
32
|
+
│ │ tarea en QUEUE_B ─────►│
|
|
33
|
+
│ │ │ ejecuta con acceso a BD
|
|
34
|
+
│ │ resultado ◄────────────│
|
|
35
|
+
│ resultado ◄───────────────│ │
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Cuándo usarlo
|
|
39
|
+
|
|
40
|
+
- Operaciones **atómicas** y **sin ciclo de vida propio**
|
|
41
|
+
- El workflow necesita el resultado para continuar
|
|
42
|
+
- Lectura de datos de otro servicio
|
|
43
|
+
- Escritura simple en otro servicio
|
|
44
|
+
|
|
45
|
+
### Ejemplo: Orders invoca una actividad de Inventory
|
|
46
|
+
|
|
47
|
+
**Interfaz compartida** (en módulo `shared` o artefacto de contrato):
|
|
48
|
+
|
|
49
|
+
```java
|
|
50
|
+
@ActivityInterface
|
|
51
|
+
public interface InventoryActivity {
|
|
52
|
+
ReserveResult reserveStock(String orderId, List<ItemRequest> items);
|
|
53
|
+
void releaseStock(String reservationId);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Implementación** (vive en el microservicio `inventory`):
|
|
58
|
+
|
|
59
|
+
```java
|
|
60
|
+
public class InventoryActivityImpl implements InventoryActivity {
|
|
61
|
+
|
|
62
|
+
private final InventoryRepository inventoryRepository;
|
|
63
|
+
|
|
64
|
+
@Override
|
|
65
|
+
public ReserveResult reserveStock(String orderId, List<ItemRequest> items) {
|
|
66
|
+
// Acceso directo a la BD de inventory
|
|
67
|
+
for (ItemRequest item : items) {
|
|
68
|
+
Product product = inventoryRepository.findById(item.getProductId())
|
|
69
|
+
.orElseThrow(() -> new ProductNotFoundException(item.getProductId()));
|
|
70
|
+
product.reserve(item.getQuantity());
|
|
71
|
+
inventoryRepository.save(product);
|
|
72
|
+
}
|
|
73
|
+
String reservationId = UUID.randomUUID().toString();
|
|
74
|
+
return new ReserveResult(reservationId, orderId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Override
|
|
78
|
+
public void releaseStock(String reservationId) {
|
|
79
|
+
// Compensación: liberar stock reservado
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Worker** (registra la activity en el microservicio `inventory`):
|
|
85
|
+
|
|
86
|
+
```java
|
|
87
|
+
Worker inventoryWorker = workerFactory.newWorker("INVENTORY_QUEUE");
|
|
88
|
+
inventoryWorker.registerActivitiesImplementations(
|
|
89
|
+
new InventoryActivityImpl(inventoryRepository)
|
|
90
|
+
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Invocación desde el workflow** (en el microservicio `orders`):
|
|
94
|
+
|
|
95
|
+
```java
|
|
96
|
+
public class PlaceOrderWorkFlowImpl implements PlaceOrderWorkFlow {
|
|
97
|
+
|
|
98
|
+
// Stub que apunta al queue de inventory — NO es HTTP
|
|
99
|
+
private final InventoryActivity inventoryActivities = Workflow.newActivityStub(
|
|
100
|
+
InventoryActivity.class,
|
|
101
|
+
ActivityOptions.newBuilder()
|
|
102
|
+
.setTaskQueue("INVENTORY_QUEUE")
|
|
103
|
+
.setStartToCloseTimeout(Duration.ofSeconds(30))
|
|
104
|
+
.setRetryOptions(RetryOptions.newBuilder()
|
|
105
|
+
.setMaximumAttempts(3)
|
|
106
|
+
.build())
|
|
107
|
+
.build()
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
@Override
|
|
111
|
+
public void start(String orderId) {
|
|
112
|
+
// Esto viaja por Temporal, no por HTTP
|
|
113
|
+
ReserveResult reserved = inventoryActivities.reserveStock(orderId, items);
|
|
114
|
+
|
|
115
|
+
// Registrar compensación en la Saga
|
|
116
|
+
saga.addCompensation(() -> inventoryActivities.releaseStock(reserved.getReservationId()));
|
|
117
|
+
|
|
118
|
+
// Continuar con el siguiente paso...
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Qué necesita cada microservicio
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
orders (invocador):
|
|
127
|
+
├── InventoryActivity.java ← interfaz (solo el contrato)
|
|
128
|
+
├── ReserveResult.java ← DTO de respuesta
|
|
129
|
+
└── ItemRequest.java ← DTO de entrada
|
|
130
|
+
|
|
131
|
+
inventory (ejecutor):
|
|
132
|
+
├── InventoryActivity.java ← interfaz (misma)
|
|
133
|
+
├── InventoryActivityImpl.java ← implementación con acceso a BD
|
|
134
|
+
└── InventoryWorker.java ← registra la activity en el queue
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Variante: Ejecución Paralela con `Async.function()`
|
|
138
|
+
|
|
139
|
+
Por defecto, Remote Activity es **bloqueante** — el workflow se detiene hasta recibir el resultado. Sin embargo, cuando necesitas invocar múltiples actividades que son **independientes entre sí**, puedes ejecutarlas en paralelo usando `Async.function()`.
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
Secuencial (default): Paralelo (Async.function):
|
|
143
|
+
|
|
144
|
+
reserveStock() ──────► 2s reserveStock() ───► 2s ──┐
|
|
145
|
+
validateAddress() ─────► 1s validateAddress() ──► 1s ──┤ max(2s, 1s, 1.5s) = 2s
|
|
146
|
+
checkFraud() ──────────► 1.5s checkFraud() ───────► 1.5s┘
|
|
147
|
+
───────────────────────── ─────────────────────────
|
|
148
|
+
Total: 4.5s Total: 2s
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Ejemplo: Validaciones paralelas antes de confirmar una orden**
|
|
152
|
+
|
|
153
|
+
```java
|
|
154
|
+
public class PlaceOrderWorkFlowImpl implements PlaceOrderWorkFlow {
|
|
155
|
+
|
|
156
|
+
private final InventoryActivity inventoryActivities = Workflow.newActivityStub(
|
|
157
|
+
InventoryActivity.class,
|
|
158
|
+
ActivityOptions.newBuilder()
|
|
159
|
+
.setTaskQueue("INVENTORY_HEAVY_TASK_QUEUE")
|
|
160
|
+
.setStartToCloseTimeout(Duration.ofSeconds(30))
|
|
161
|
+
.build()
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
private final ShippingActivity shippingActivities = Workflow.newActivityStub(
|
|
165
|
+
ShippingActivity.class,
|
|
166
|
+
ActivityOptions.newBuilder()
|
|
167
|
+
.setTaskQueue("SHIPPING_LIGHT_TASK_QUEUE")
|
|
168
|
+
.setStartToCloseTimeout(Duration.ofSeconds(10))
|
|
169
|
+
.build()
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
private final RiskActivity riskActivities = Workflow.newActivityStub(
|
|
173
|
+
RiskActivity.class,
|
|
174
|
+
ActivityOptions.newBuilder()
|
|
175
|
+
.setTaskQueue("RISK_LIGHT_TASK_QUEUE")
|
|
176
|
+
.setStartToCloseTimeout(Duration.ofSeconds(15))
|
|
177
|
+
.build()
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
@Override
|
|
181
|
+
public void start(String orderId) {
|
|
182
|
+
// Lanzar las 3 actividades en paralelo — NO bloquean individualmente
|
|
183
|
+
Promise<ReserveResult> stockPromise = Async.function(
|
|
184
|
+
inventoryActivities::reserveStock, orderId, items
|
|
185
|
+
);
|
|
186
|
+
Promise<AddressValidation> addressPromise = Async.function(
|
|
187
|
+
shippingActivities::validateAddress, shippingAddress
|
|
188
|
+
);
|
|
189
|
+
Promise<FraudScore> fraudPromise = Async.function(
|
|
190
|
+
riskActivities::quickCheck, orderId, totalAmount
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Esperar TODAS — el workflow se bloquea aquí hasta que las 3 terminen
|
|
194
|
+
ReserveResult reserved = stockPromise.get();
|
|
195
|
+
AddressValidation address = addressPromise.get();
|
|
196
|
+
FraudScore fraud = fraudPromise.get();
|
|
197
|
+
|
|
198
|
+
// Con los 3 resultados, tomar decisión
|
|
199
|
+
if (fraud.isRejected()) {
|
|
200
|
+
inventoryActivities.releaseStock(reserved.getReservationId());
|
|
201
|
+
throw ApplicationFailure.newFailure("Fraud detected", "FRAUD");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Continuar con el flujo...
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**API de `Promise`:**
|
|
210
|
+
|
|
211
|
+
| Método | Comportamiento |
|
|
212
|
+
|--------|---------------|
|
|
213
|
+
| `promise.get()` | Bloquea el workflow hasta que la actividad termine. Propaga excepciones. |
|
|
214
|
+
| `Promise.allOf(p1, p2, p3)` | Retorna un `Promise<Void>` que se completa cuando **todas** terminan. |
|
|
215
|
+
| `Promise.anyOf(p1, p2, p3)` | Retorna un `Promise<Object>` que se completa cuando **la primera** termina. |
|
|
216
|
+
|
|
217
|
+
**`Promise.allOf` — espera explícita con manejo unificado:**
|
|
218
|
+
|
|
219
|
+
```java
|
|
220
|
+
Promise.allOf(stockPromise, addressPromise, fraudPromise).get(); // espera las 3
|
|
221
|
+
|
|
222
|
+
// Ahora todos los .get() son inmediatos (ya completaron)
|
|
223
|
+
ReserveResult reserved = stockPromise.get();
|
|
224
|
+
AddressValidation address = addressPromise.get();
|
|
225
|
+
FraudScore fraud = fraudPromise.get();
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**`Promise.anyOf` — patrón race (el primero gana):**
|
|
229
|
+
|
|
230
|
+
```java
|
|
231
|
+
// Útil para failover: consultar múltiples proveedores, usar el primero que responda
|
|
232
|
+
Promise<ShippingRate> dhlPromise = Async.function(dhlActivities::getRate, pkg);
|
|
233
|
+
Promise<ShippingRate> fedexPromise = Async.function(fedexActivities::getRate, pkg);
|
|
234
|
+
|
|
235
|
+
Promise<Object> first = Promise.anyOf(dhlPromise, fedexPromise);
|
|
236
|
+
first.get(); // espera al primero
|
|
237
|
+
|
|
238
|
+
ShippingRate rate = dhlPromise.isCompleted()
|
|
239
|
+
? dhlPromise.get()
|
|
240
|
+
: fedexPromise.get();
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Cuándo usar `Async.function()` vs secuencial
|
|
244
|
+
|
|
245
|
+
| Criterio | Secuencial (default) | Paralelo (`Async.function`) |
|
|
246
|
+
|----------|---------------------|------------------------------|
|
|
247
|
+
| Actividades **dependen** una de otra | ✅ Usar | ❌ No aplica |
|
|
248
|
+
| Actividades **independientes** | ❌ Desperdicia tiempo | ✅ Usar |
|
|
249
|
+
| Compensación en Saga | Registrar después de cada `.get()` | Registrar después de `Promise.allOf()` |
|
|
250
|
+
| Manejo de errores | Falla en el paso, compensa lo anterior | Una falla cancela las pendientes si usas `CancellationScope` |
|
|
251
|
+
|
|
252
|
+
> **Nota importante:** `Async.function()` es parte del SDK de Temporal y es **determinista** — seguro de usar dentro de workflows. No confundir con `CompletableFuture` de Java, que NO es determinista y está prohibido en workflows Temporal.
|
|
253
|
+
|
|
254
|
+
### En Temporal Web UI
|
|
255
|
+
|
|
256
|
+
Aparecen como una **actividad normal** dentro del workflow. No se distingue de una actividad local. Cuando se usa `Async.function()`, las actividades paralelas aparecen como múltiples `ActivityTaskScheduled` events casi simultáneos en el historial.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 2. Child Workflow
|
|
261
|
+
|
|
262
|
+
### Concepto
|
|
263
|
+
|
|
264
|
+
Un workflow en el **Servicio A** lanza un **sub-workflow** que puede vivir en el **Servicio B**. El child tiene su propio historial, puede ser cancelado independientemente, y el padre puede esperar su resultado o continuar sin esperarlo.
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
Servicio A (parent workflow) Temporal Server Servicio B (child workflow)
|
|
268
|
+
│ │ │
|
|
269
|
+
│ childStub.start(data) │ │
|
|
270
|
+
│───────────────────────────────►│ │
|
|
271
|
+
│ │ nueva ejecución ──────►│
|
|
272
|
+
│ │ │ ejecuta pasos
|
|
273
|
+
│ │ │ propios (activities,
|
|
274
|
+
│ │ │ signals, timers)
|
|
275
|
+
│ │ resultado ◄────────────│
|
|
276
|
+
│ resultado ◄───────────────────│ │
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Cuándo usarlo
|
|
280
|
+
|
|
281
|
+
- El subproceso tiene **ciclo de vida propio** (puede cancelarse, consultarse, reintentar)
|
|
282
|
+
- El subproceso tiene **lógica compleja** con múltiples pasos internos
|
|
283
|
+
- Necesitas **limitar el alcance de la cancelación** (cancelar el child sin cancelar el parent)
|
|
284
|
+
- El historial del parent sería demasiado largo si incluyera todo inline
|
|
285
|
+
|
|
286
|
+
### Ejemplo: PlaceOrder lanza ProcessPayment como child
|
|
287
|
+
|
|
288
|
+
**Interfaz del child** (compartida):
|
|
289
|
+
|
|
290
|
+
```java
|
|
291
|
+
@WorkflowInterface
|
|
292
|
+
public interface ProcessPaymentWorkFlow {
|
|
293
|
+
|
|
294
|
+
@WorkflowMethod
|
|
295
|
+
PaymentResult start(PaymentRequest request);
|
|
296
|
+
|
|
297
|
+
@SignalMethod
|
|
298
|
+
void onThreeDSecureCompleted(String verificationToken);
|
|
299
|
+
|
|
300
|
+
@QueryMethod
|
|
301
|
+
String getPaymentStatus();
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**Implementación** (vive en el microservicio `payments`):
|
|
306
|
+
|
|
307
|
+
```java
|
|
308
|
+
public class ProcessPaymentWorkFlowImpl implements ProcessPaymentWorkFlow {
|
|
309
|
+
|
|
310
|
+
private final PaymentActivity paymentActivities = Workflow.newActivityStub(
|
|
311
|
+
PaymentActivity.class,
|
|
312
|
+
ActivityOptions.newBuilder()
|
|
313
|
+
.setTaskQueue("PAYMENT_QUEUE")
|
|
314
|
+
.setStartToCloseTimeout(Duration.ofSeconds(60))
|
|
315
|
+
.build()
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
private String status = "PENDING";
|
|
319
|
+
private String verificationToken = null;
|
|
320
|
+
|
|
321
|
+
@Override
|
|
322
|
+
public PaymentResult start(PaymentRequest request) {
|
|
323
|
+
|
|
324
|
+
// 1. Intentar cobro
|
|
325
|
+
ChargeResult charge = paymentActivities.charge(request);
|
|
326
|
+
|
|
327
|
+
// 2. Si requiere 3D Secure, esperar verificación del usuario
|
|
328
|
+
if (charge.isRequires3DSecure()) {
|
|
329
|
+
status = "AWAITING_VERIFICATION";
|
|
330
|
+
|
|
331
|
+
// Espera hasta 10 minutos que el usuario complete 3D Secure
|
|
332
|
+
boolean verified = Workflow.await(
|
|
333
|
+
Duration.ofMinutes(10),
|
|
334
|
+
() -> this.verificationToken != null
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
if (!verified) {
|
|
338
|
+
status = "TIMEOUT";
|
|
339
|
+
return new PaymentResult(null, "TIMEOUT");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Confirmar con el token de verificación
|
|
343
|
+
charge = paymentActivities.confirmWithVerification(
|
|
344
|
+
charge.getTransactionId(), verificationToken
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
status = "COMPLETED";
|
|
349
|
+
return new PaymentResult(charge.getTransactionId(), "SUCCESS");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
@Override
|
|
353
|
+
public void onThreeDSecureCompleted(String token) {
|
|
354
|
+
this.verificationToken = token;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
@Override
|
|
358
|
+
public String getPaymentStatus() {
|
|
359
|
+
return status;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Invocación desde el parent** (en el microservicio `orders`):
|
|
365
|
+
|
|
366
|
+
```java
|
|
367
|
+
public class PlaceOrderWorkFlowImpl implements PlaceOrderWorkFlow {
|
|
368
|
+
|
|
369
|
+
@Override
|
|
370
|
+
public void start(String orderId) {
|
|
371
|
+
// ... reservar stock ...
|
|
372
|
+
|
|
373
|
+
// Lanzar pago como child workflow
|
|
374
|
+
ProcessPaymentWorkFlow paymentWorkflow = Workflow.newChildWorkflowStub(
|
|
375
|
+
ProcessPaymentWorkFlow.class,
|
|
376
|
+
ChildWorkflowOptions.newBuilder()
|
|
377
|
+
.setWorkflowId("payment-" + orderId)
|
|
378
|
+
.setTaskQueue("PAYMENT_FLOW_QUEUE")
|
|
379
|
+
.build()
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
// Espera el resultado del child (bloqueante para el parent)
|
|
383
|
+
PaymentResult result = paymentWorkflow.start(
|
|
384
|
+
new PaymentRequest(orderId, totalAmount, paymentToken)
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
if (!"SUCCESS".equals(result.getStatus())) {
|
|
388
|
+
saga.compensate(); // Fallo en pago → compensar stock
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
saga.addCompensation(() -> paymentActivities.refund(result.getPaymentId()));
|
|
393
|
+
|
|
394
|
+
// Continuar: confirmar orden...
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Diferencia clave con Remote Activity
|
|
400
|
+
|
|
401
|
+
```
|
|
402
|
+
Remote Activity:
|
|
403
|
+
- Una operación: charge() → resultado
|
|
404
|
+
- Sin estado interno
|
|
405
|
+
- Sin signals ni queries
|
|
406
|
+
|
|
407
|
+
Child Workflow:
|
|
408
|
+
- Múltiples pasos: charge() → esperar 3D Secure → confirm()
|
|
409
|
+
- Estado interno (status, verificationToken)
|
|
410
|
+
- Acepta signals (onThreeDSecureCompleted)
|
|
411
|
+
- Se puede consultar (getPaymentStatus)
|
|
412
|
+
- Historial propio en Temporal Web
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### En Temporal Web UI
|
|
416
|
+
|
|
417
|
+
Aparecen como **dos ejecuciones vinculadas**: el parent muestra un evento `ChildWorkflowExecutionStarted` y el child tiene su propio historial completo con todos sus pasos internos.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## 3. Signal External (Fire & Forget)
|
|
422
|
+
|
|
423
|
+
### Concepto
|
|
424
|
+
|
|
425
|
+
Un servicio envía una **señal** a un workflow que ya está corriendo en otro servicio. No espera respuesta. El emisor solo necesita conocer el `workflowId` del receptor.
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
Servicio A Temporal Server Servicio B (workflow activo)
|
|
429
|
+
│ │ │
|
|
430
|
+
│ signal(workflowId, data) │ │
|
|
431
|
+
│───────────────────────────►│ │
|
|
432
|
+
│ │ entrega el signal ─────────►│
|
|
433
|
+
│ (continúa sin esperar) │ │ procesa el signal
|
|
434
|
+
│ │ │
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Cuándo usarlo
|
|
438
|
+
|
|
439
|
+
- Avisar a un workflow activo que **algo pasó**
|
|
440
|
+
- No necesitas respuesta del receptor
|
|
441
|
+
- Los servicios son **completamente independientes**
|
|
442
|
+
- Comunicación basada en eventos entre workflows
|
|
443
|
+
|
|
444
|
+
### Ejemplo: Webhook de pagos notifica al workflow de orders
|
|
445
|
+
|
|
446
|
+
**Escenario:** El usuario completó el pago en una pasarela externa (PayPal, 3D Secure). La pasarela llama a un webhook en el microservicio `payments`. Este debe avisar al workflow `PlaceOrder` en el microservicio `orders` que el pago fue exitoso.
|
|
447
|
+
|
|
448
|
+
**Receptor — workflow en orders** (ya está corriendo y esperando):
|
|
449
|
+
|
|
450
|
+
```java
|
|
451
|
+
@WorkflowInterface
|
|
452
|
+
public interface PlaceOrderWorkFlow {
|
|
453
|
+
|
|
454
|
+
@WorkflowMethod
|
|
455
|
+
void start(String cartId, String paymentToken);
|
|
456
|
+
|
|
457
|
+
@SignalMethod
|
|
458
|
+
void paymentCompleted(String paymentId);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
public class PlaceOrderWorkFlowImpl implements PlaceOrderWorkFlow {
|
|
462
|
+
|
|
463
|
+
private String paymentId = null;
|
|
464
|
+
|
|
465
|
+
@Override
|
|
466
|
+
public void start(String cartId, String paymentToken) {
|
|
467
|
+
// ... pasos previos (reservar stock, etc.) ...
|
|
468
|
+
|
|
469
|
+
// Esperar señal de pago (máximo 30 minutos)
|
|
470
|
+
boolean paid = Workflow.await(
|
|
471
|
+
Duration.ofMinutes(30),
|
|
472
|
+
() -> this.paymentId != null
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
if (!paid) {
|
|
476
|
+
saga.compensate(); // timeout → liberar stock
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Pago recibido, continuar
|
|
481
|
+
orderActivities.confirmOrder(orderId, this.paymentId);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
@Override
|
|
485
|
+
public void paymentCompleted(String paymentId) {
|
|
486
|
+
this.paymentId = paymentId; // desbloquea el await
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**Emisor — webhook controller en payments:**
|
|
492
|
+
|
|
493
|
+
```java
|
|
494
|
+
@RestController
|
|
495
|
+
public class PaymentWebhookController {
|
|
496
|
+
|
|
497
|
+
private final WorkflowClient workflowClient;
|
|
498
|
+
|
|
499
|
+
// POST /webhooks/payments (llamado por la pasarela externa)
|
|
500
|
+
@PostMapping("/webhooks/payments")
|
|
501
|
+
public ResponseEntity<Void> handlePaymentWebhook(@RequestBody PaymentNotification notification) {
|
|
502
|
+
|
|
503
|
+
// El workflowId se guardó cuando se inició el checkout
|
|
504
|
+
String workflowId = notification.getMetadata().get("workflowId");
|
|
505
|
+
|
|
506
|
+
// Enviar signal al workflow de orders — fire & forget
|
|
507
|
+
PlaceOrderWorkFlow workflow = workflowClient.newUntypedWorkflowStub(workflowId)
|
|
508
|
+
.signal("paymentCompleted", notification.getPaymentId());
|
|
509
|
+
|
|
510
|
+
return ResponseEntity.ok().build();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**Alternativa sin conocer la interfaz** (acoplamiento mínimo):
|
|
516
|
+
|
|
517
|
+
```java
|
|
518
|
+
// Signal sin tipar — solo necesitas el workflowId y el nombre del signal
|
|
519
|
+
workflowClient.newUntypedWorkflowStub(workflowId)
|
|
520
|
+
.signal("paymentCompleted", paymentId);
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Qué necesita cada microservicio
|
|
524
|
+
|
|
525
|
+
```
|
|
526
|
+
payments (emisor):
|
|
527
|
+
├── WorkflowClient ← del SDK de Temporal
|
|
528
|
+
└── workflowId ← guardado cuando se inició el checkout
|
|
529
|
+
|
|
530
|
+
orders (receptor):
|
|
531
|
+
└── @SignalMethod en su workflow ← define qué signals acepta
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**El emisor NO necesita la interfaz del receptor** si usa `newUntypedWorkflowStub`. Es el patrón de menor acoplamiento.
|
|
535
|
+
|
|
536
|
+
### En Temporal Web UI
|
|
537
|
+
|
|
538
|
+
Aparece como un evento `WorkflowSignaled` en el historial del workflow receptor. En el emisor no queda registro (es fire & forget desde su perspectiva).
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## 4. Signal + Await (Espera con Timeout)
|
|
543
|
+
|
|
544
|
+
### Concepto
|
|
545
|
+
|
|
546
|
+
Combina Signal External con `Workflow.await()` para crear un patrón **request-wait** entre servicios. Un workflow envía un signal y luego espera que el otro le responda con otro signal.
|
|
547
|
+
|
|
548
|
+
```
|
|
549
|
+
Servicio A (workflow) Temporal Server Servicio B (workflow)
|
|
550
|
+
│ │ │
|
|
551
|
+
│ signal → B │ │
|
|
552
|
+
│───────────────────────────►│────────────────────────►│
|
|
553
|
+
│ │ │
|
|
554
|
+
│ await(timeout) │ │ procesa...
|
|
555
|
+
│ zzz... │ │
|
|
556
|
+
│ │ │
|
|
557
|
+
│ │ signal → A ◄───────────│
|
|
558
|
+
│ ◄─────────────────────────│ │
|
|
559
|
+
│ (despierta y continúa) │ │
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Cuándo usarlo
|
|
563
|
+
|
|
564
|
+
- Necesitas respuesta de otro servicio pero **no quieres bloquear con una activity**
|
|
565
|
+
- El otro servicio puede tardar minutos, horas o días
|
|
566
|
+
- Necesitas un **timeout** si la respuesta no llega
|
|
567
|
+
- Útil para aprobaciones humanas, verificaciones externas, procesos batch
|
|
568
|
+
|
|
569
|
+
### Ejemplo: Orders solicita aprobación de fraude a Risk
|
|
570
|
+
|
|
571
|
+
```java
|
|
572
|
+
// En PlaceOrderWorkFlowImpl (orders)
|
|
573
|
+
private FraudCheckResult fraudResult = null;
|
|
574
|
+
|
|
575
|
+
@Override
|
|
576
|
+
public void start(String orderId) {
|
|
577
|
+
// ... crear orden, reservar stock ...
|
|
578
|
+
|
|
579
|
+
// Solicitar análisis de fraude (signal al servicio de risk)
|
|
580
|
+
String riskWorkflowId = "risk-check-" + orderId;
|
|
581
|
+
|
|
582
|
+
// Iniciar el workflow de risk assessment
|
|
583
|
+
RiskAssessmentWorkFlow riskWorkflow = workflowClient.newWorkflowStub(
|
|
584
|
+
RiskAssessmentWorkFlow.class,
|
|
585
|
+
WorkflowOptions.newBuilder()
|
|
586
|
+
.setWorkflowId(riskWorkflowId)
|
|
587
|
+
.setTaskQueue("RISK_QUEUE")
|
|
588
|
+
.build()
|
|
589
|
+
);
|
|
590
|
+
WorkflowClient.start(riskWorkflow::assess, orderId, orderAmount);
|
|
591
|
+
|
|
592
|
+
// Esperar resultado (máximo 5 minutos)
|
|
593
|
+
boolean received = Workflow.await(
|
|
594
|
+
Duration.ofMinutes(5),
|
|
595
|
+
() -> this.fraudResult != null
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
if (!received) {
|
|
599
|
+
// Timeout: decidir política (aprobar por defecto o rechazar)
|
|
600
|
+
orderActivities.flagForManualReview(orderId);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (fraudResult.isRejected()) {
|
|
605
|
+
saga.compensate();
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Aprobado → continuar con cobro
|
|
610
|
+
paymentActivities.charge(orderId, totalAmount, paymentToken);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
@SignalMethod
|
|
614
|
+
public void onFraudCheckCompleted(FraudCheckResult result) {
|
|
615
|
+
this.fraudResult = result;
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
```java
|
|
620
|
+
// En RiskAssessmentWorkFlowImpl (risk)
|
|
621
|
+
@Override
|
|
622
|
+
public void assess(String orderId, BigDecimal amount) {
|
|
623
|
+
// Análisis que puede tardar...
|
|
624
|
+
FraudScore score = riskActivities.analyzeTransaction(orderId, amount);
|
|
625
|
+
|
|
626
|
+
FraudCheckResult result = new FraudCheckResult(
|
|
627
|
+
score.isAboveThreshold() ? "REJECTED" : "APPROVED"
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
// Enviar resultado de vuelta al workflow de orders
|
|
631
|
+
String orderWorkflowId = "PlaceOrderWorkFlow-" + orderId;
|
|
632
|
+
workflowClient.newUntypedWorkflowStub(orderWorkflowId)
|
|
633
|
+
.signal("onFraudCheckCompleted", result);
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
639
|
+
## 🎯 Árbol de Decisión
|
|
640
|
+
|
|
641
|
+
```
|
|
642
|
+
¿Necesito datos/resultado del otro servicio?
|
|
643
|
+
│
|
|
644
|
+
├─ NO → Signal External (fire & forget)
|
|
645
|
+
│ Ejemplo: avisar que un pago se completó
|
|
646
|
+
│
|
|
647
|
+
└─ SÍ
|
|
648
|
+
│
|
|
649
|
+
├─ ¿La operación es simple y rápida (< 2 min)?
|
|
650
|
+
│ │
|
|
651
|
+
│ ├─ ¿Solo una operación, o varias que dependen entre sí?
|
|
652
|
+
│ │ └─ SÍ → Remote Activity (secuencial)
|
|
653
|
+
│ │ Ejemplo: reservar stock, leer un carrito, crear un registro
|
|
654
|
+
│ │
|
|
655
|
+
│ └─ ¿Múltiples operaciones independientes que puedo lanzar a la vez?
|
|
656
|
+
│ └─ SÍ → Remote Activity + Async.function() (paralelo)
|
|
657
|
+
│ Ejemplo: reservar stock + validar dirección + check fraude
|
|
658
|
+
│
|
|
659
|
+
└─ ¿La operación tiene múltiples pasos, estado interno, o puede tardar mucho?
|
|
660
|
+
│
|
|
661
|
+
├─ ¿Necesito controlar su ciclo de vida (cancelar, consultar estado)?
|
|
662
|
+
│ └─ SÍ → Child Workflow
|
|
663
|
+
│ Ejemplo: proceso de pago con 3D Secure, fulfillment multi-paso
|
|
664
|
+
│
|
|
665
|
+
└─ ¿Solo necesito esperar una respuesta eventual?
|
|
666
|
+
└─ SÍ → Signal + Await
|
|
667
|
+
Ejemplo: aprobación de fraude, validación humana
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
## 📊 E-commerce: Qué patrón usa cada comunicación
|
|
673
|
+
|
|
674
|
+
```
|
|
675
|
+
PlaceOrderWorkFlow (orders)
|
|
676
|
+
│
|
|
677
|
+
├── cartActivities.fetchCart() → Remote Activity (carts)
|
|
678
|
+
├── orderActivities.createDraft() → Remote Activity (orders, local)
|
|
679
|
+
├── inventoryActivities.reserveStock() → Remote Activity (inventory)
|
|
680
|
+
│
|
|
681
|
+
├── ProcessPaymentWorkFlow → Child Workflow (payments)
|
|
682
|
+
│ ├── paymentActivities.charge() → Remote Activity (payments, local)
|
|
683
|
+
│ └── await 3DSecure signal → Signal + Await (webhook externo)
|
|
684
|
+
│
|
|
685
|
+
├── orderActivities.confirmOrder() → Remote Activity (orders, local)
|
|
686
|
+
├── cartActivities.markCheckedOut() → Remote Activity (carts)
|
|
687
|
+
├── notificationActivities.sendConfirmation() → Remote Activity (notifications)
|
|
688
|
+
│
|
|
689
|
+
└── [si falla] saga.compensate()
|
|
690
|
+
├── inventoryActivities.releaseStock() → Remote Activity (inventory)
|
|
691
|
+
└── paymentActivities.refund() → Remote Activity (payments)
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
## 🔧 Notas Técnicas
|
|
697
|
+
|
|
698
|
+
### Task Queues
|
|
699
|
+
|
|
700
|
+
Each microservicio escucha en sus propias colas con prefijo de módulo:
|
|
701
|
+
|
|
702
|
+
```
|
|
703
|
+
orders: ORDER_WORKFLOW_QUEUE, ORDER_LIGHT_TASK_QUEUE, ORDER_HEAVY_TASK_QUEUE
|
|
704
|
+
carts: CART_LIGHT_TASK_QUEUE, CART_HEAVY_TASK_QUEUE
|
|
705
|
+
inventory: INVENTORY_LIGHT_TASK_QUEUE, INVENTORY_HEAVY_TASK_QUEUE
|
|
706
|
+
payments: PAYMENT_WORKFLOW_QUEUE, PAYMENT_LIGHT_TASK_QUEUE, PAYMENT_HEAVY_TASK_QUEUE
|
|
707
|
+
notifications: NOTIFICATION_WORKFLOW_QUEUE, NOTIFICATION_LIGHT_TASK_QUEUE
|
|
708
|
+
shipping: SHIPPING_LIGHT_TASK_QUEUE, SHIPPING_HEAVY_TASK_QUEUE
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### Qué comparten los servicios
|
|
712
|
+
|
|
713
|
+
```
|
|
714
|
+
Remote Activity: Interfaz (@ActivityInterface) + DTOs
|
|
715
|
+
Child Workflow: Interfaz (@WorkflowInterface) + DTOs
|
|
716
|
+
Signal External: Solo el workflowId (y opcionalmente el nombre del signal)
|
|
717
|
+
Signal + Await: workflowId + nombre del signal + DTOs del payload
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### Reintentos y resiliencia
|
|
721
|
+
|
|
722
|
+
| Patrón | Reintentos | Qué pasa si el servicio remoto cae |
|
|
723
|
+
|--------|-----------|-------------------------------------|
|
|
724
|
+
| Remote Activity | Configurables vía `RetryOptions` | Temporal reintenta hasta que el worker vuelva o se agoten intentos |
|
|
725
|
+
| Child Workflow | El child tiene sus propios reintentos | El parent espera; el child retoma cuando el worker vuelve |
|
|
726
|
+
| Signal External | No aplica (fire & forget) | El signal se encola y se entrega cuando el workflow lo procese |
|
|
727
|
+
| Signal + Await | El await tiene timeout | Si no llega el signal, el timeout dispara lógica alternativa |
|
|
728
|
+
|
|
729
|
+
---
|
|
730
|
+
|
|
731
|
+
**Última actualización:** 2026-04-07
|