eva4j 1.0.16 → 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.
Files changed (151) hide show
  1. package/AGENTS.md +220 -5
  2. package/DOMAIN_YAML_GUIDE.md +188 -3
  3. package/FUTURE_FEATURES.md +33 -52
  4. package/QUICK_REFERENCE.md +8 -4
  5. package/bin/eva4j.js +70 -2
  6. package/config/defaults.json +1 -0
  7. package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
  8. package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
  9. package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
  10. package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
  11. package/docs/commands/EVALUATE_SYSTEM.md +290 -10
  12. package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
  13. package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
  14. package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
  15. package/docs/commands/INDEX.md +27 -3
  16. package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
  17. package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
  18. package/docs/prototype/system/RISKS.md +277 -0
  19. package/docs/prototype/system/customers.yaml +133 -0
  20. package/docs/prototype/system/inventory.yaml +109 -0
  21. package/docs/prototype/system/notifications.yaml +131 -0
  22. package/docs/prototype/system/orders.yaml +241 -0
  23. package/docs/prototype/system/payments.yaml +256 -0
  24. package/docs/prototype/system/products.yaml +168 -0
  25. package/docs/prototype/system/system.yaml +269 -0
  26. package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
  27. package/examples/domain-events.yaml +26 -0
  28. package/examples/domain-read-models.yaml +113 -0
  29. package/examples/system/customer.yaml +89 -0
  30. package/examples/system/orders.yaml +119 -0
  31. package/examples/system/product.yaml +27 -0
  32. package/examples/system/system.yaml +80 -0
  33. package/package.json +1 -1
  34. package/read-model-spec.md +664 -0
  35. package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
  36. package/src/agents/design-gap-analyst.agent.md +383 -0
  37. package/src/agents/design-reviewer-temporal.agent.md +412 -0
  38. package/src/agents/design-reviewer.agent.md +34 -5
  39. package/src/agents/implement-use-cases.prompt.md +179 -0
  40. package/src/agents/ux-gap-analyst.agent.md +412 -0
  41. package/src/commands/add-rabbitmq-client.js +261 -0
  42. package/src/commands/add-temporal-client.js +22 -2
  43. package/src/commands/build.js +267 -11
  44. package/src/commands/evaluate-system.js +700 -13
  45. package/src/commands/generate-entities.js +560 -24
  46. package/src/commands/generate-http-exchange.js +3 -0
  47. package/src/commands/generate-kafka-event.js +3 -0
  48. package/src/commands/generate-kafka-listener.js +3 -0
  49. package/src/commands/generate-rabbitmq-event.js +665 -0
  50. package/src/commands/generate-rabbitmq-listener.js +205 -0
  51. package/src/commands/generate-record.js +2 -2
  52. package/src/commands/generate-resource.js +4 -1
  53. package/src/commands/generate-temporal-activity.js +970 -33
  54. package/src/commands/generate-temporal-flow.js +98 -38
  55. package/src/commands/generate-temporal-system.js +708 -0
  56. package/src/commands/generate-usecase.js +4 -1
  57. package/src/skills/build-system-yaml/SKILL.md +343 -2
  58. package/src/skills/build-system-yaml/references/domain-yaml-spec.md +253 -26
  59. package/src/skills/build-system-yaml/references/module-spec.md +90 -9
  60. package/src/skills/build-system-yaml/references/system-yaml-spec.md +36 -0
  61. package/src/skills/build-temporal-system/SKILL.md +752 -0
  62. package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
  63. package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
  64. package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
  65. package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
  66. package/src/skills/implement-use-case/SKILL.md +350 -0
  67. package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
  68. package/src/skills/requirements-elicitation/SKILL.md +228 -0
  69. package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
  70. package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
  71. package/src/utils/bounded-context-diagram.js +844 -0
  72. package/src/utils/config-manager.js +4 -2
  73. package/src/utils/domain-validator.js +495 -17
  74. package/src/utils/naming.js +20 -0
  75. package/src/utils/system-validator.js +169 -11
  76. package/src/utils/system-yaml-parser.js +318 -0
  77. package/src/utils/temporal-validator.js +497 -0
  78. package/src/utils/validator.js +3 -1
  79. package/src/utils/yaml-to-entity.js +281 -9
  80. package/templates/aggregate/AggregateRepository.java.ejs +4 -0
  81. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +8 -0
  82. package/templates/aggregate/AggregateRoot.java.ejs +38 -4
  83. package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
  84. package/templates/aggregate/JpaAggregateRoot.java.ejs +4 -4
  85. package/templates/aggregate/JpaEntity.java.ejs +2 -2
  86. package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
  87. package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
  88. package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
  89. package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
  90. package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
  91. package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
  92. package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
  93. package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
  94. package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
  95. package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
  96. package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
  97. package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
  98. package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
  99. package/templates/base/root/AGENTS.md.ejs +1 -1
  100. package/templates/crud/DeleteCommandHandler.java.ejs +19 -1
  101. package/templates/crud/EndpointsController.java.ejs +1 -1
  102. package/templates/crud/ScaffoldCommand.java.ejs +5 -2
  103. package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
  104. package/templates/crud/ScaffoldQuery.java.ejs +5 -2
  105. package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
  106. package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
  107. package/templates/crud/UpdateCommandHandler.java.ejs +53 -2
  108. package/templates/evaluate/report.html.ejs +1447 -90
  109. package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
  110. package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
  111. package/templates/ports/PortAclMapper.java.ejs +35 -0
  112. package/templates/ports/PortFeignAdapter.java.ejs +7 -22
  113. package/templates/ports/PortFeignClient.java.ejs +4 -0
  114. package/templates/ports/PortResponseDto.java.ejs +1 -1
  115. package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
  116. package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
  117. package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
  118. package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
  119. package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
  120. package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
  121. package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
  122. package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
  123. package/templates/read-model/ReadModelDomain.java.ejs +46 -0
  124. package/templates/read-model/ReadModelJpa.java.ejs +58 -0
  125. package/templates/read-model/ReadModelJpaRepository.java.ejs +13 -0
  126. package/templates/read-model/ReadModelKafkaListener.java.ejs +64 -0
  127. package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
  128. package/templates/read-model/ReadModelRepository.java.ejs +42 -0
  129. package/templates/read-model/ReadModelRepositoryImpl.java.ejs +85 -0
  130. package/templates/read-model/ReadModelSyncHandler.java.ejs +54 -0
  131. package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
  132. package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
  133. package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
  134. package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
  135. package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
  136. package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
  137. package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
  138. package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
  139. package/templates/temporal-activity/NestedType.java.ejs +12 -0
  140. package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
  141. package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
  142. package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
  143. package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
  144. package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
  145. package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
  146. package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
  147. package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
  148. package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
  149. package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
  150. package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
  151. package/COMMAND_EVALUATION.md +0 -911
@@ -0,0 +1,740 @@
1
+ # Metodología de Diseño con Temporal como Broker
2
+
3
+ ## 📋 Propósito
4
+
5
+ Guía paso a paso para diseñar un sistema de microservicios usando **Temporal como único mecanismo de comunicación inter-módulo**, reemplazando Kafka (async) y Feign (sync). Define qué va en `system.yaml`, qué va en cada `{domain}.yaml`, y las mejores prácticas para tomar decisiones de diseño.
6
+
7
+ **Documentos complementarios:**
8
+ - [TEMPORAL_COMMUNICATION_PATTERNS.md](TEMPORAL_COMMUNICATION_PATTERNS.md) — Patrones de comunicación (Remote Activity, Child Workflow, Signal, Async.function)
9
+ - [system/RISKS.md](system/RISKS.md) — Análisis de riesgos y trade-offs
10
+
11
+ ---
12
+
13
+ ## 🗺️ Visión General de la Arquitectura
14
+
15
+ ```
16
+ ┌─────────────────────────────────────────────────────────────────┐
17
+ │ system.yaml │
18
+ │ ┌──────────┐ ┌──────────┐ ┌─────────────────────────────┐ │
19
+ │ │ system: │ │ modules: │ │ workflows: │ │
20
+ │ │ │ │ │ │ PlaceOrderWorkflow │ │
21
+ │ │ name │ │ products │ │ CancelOrderWorkflow │ │
22
+ │ │ database │ │ orders │ │ ProductCreatedWorkflow │ │
23
+ │ │ temporal │ │ payments │ │ (orquestación cross-module) │ │
24
+ │ └──────────┘ └──────────┘ └─────────────────────────────┘ │
25
+ └─────────────────────────────────────────────────────────────────┘
26
+ ↕ ↕ ↕
27
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────────┐
28
+ │ products.yaml│ │ orders.yaml │ │ payments.yaml │
29
+ │ │ │ │ │ │
30
+ │ aggregates: │ │ aggregates: │ │ aggregates: │
31
+ │ Product │ │ Order │ │ Payment │
32
+ │ │ │ │ │ │
33
+ │ events: │ │ events: │ │ events: │
34
+ │ (internos) │ │ (→notifies) │ │ (internos) │
35
+ │ │ │ │ │ │
36
+ │ activities: │ │ activities: │ │ activities: │
37
+ │ GetProduct │ │ ConfirmOrder│ │ ProcessPayment │
38
+ │ (cross-mod) │ │ (local) │ │ RefundPayment (cross-mod) │
39
+ │ │ │ │ │ RetryCharge (interna) │
40
+ │ endpoints: │ │ endpoints: │ │ │
41
+ │ REST API │ │ REST API │ │ workflows: (single-module) │
42
+ │ │ │ │ │ RetryChargeWorkflow │
43
+ │ │ │ workflows: │ │ │
44
+ │ │ │ ExpireOrder │ │ ports: (servicios EXTERNOS) │
45
+ │ │ │ (interno) │ │ PaymentGateway (HTTP) │
46
+ └──────────────┘ └──────────────┘ └──────────────────────────────┘
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 📐 Qué va en Cada Archivo
52
+
53
+ ### `system.yaml` — Vista de Pájaro (Orquestación)
54
+
55
+ El system.yaml es el **mapa de navegación** del sistema. Contiene todo lo que cruza fronteras de módulo.
56
+
57
+ | Sección | Contenido | Ejemplo |
58
+ |---------|-----------|---------|
59
+ | `system:` | Metadata del proyecto | nombre, database, Spring Boot version |
60
+ | `orchestration:` | Configuración de Temporal | target, namespace |
61
+ | `modules:` | Lista de módulos con endpoints REST | products, orders, payments |
62
+ | `workflows:` | **Flujos cross-module** | PlaceOrderWorkflow, CancelOrderWorkflow |
63
+
64
+ **Regla cardinal:** Si un flujo toca **2+ módulos**, se define en `system.yaml`.
65
+
66
+ ```yaml
67
+ # system.yaml — SOLO orquestación cross-module
68
+ workflows:
69
+ - name: PlaceOrderWorkflow
70
+ trigger:
71
+ module: orders
72
+ on: create
73
+ taskQueue: ORDER_WORKFLOW_QUEUE
74
+ saga: true
75
+ steps:
76
+ - activity: GetCustomerById # → customers
77
+ - activity: GetProductsByIds # → products ⎫ parallel
78
+ - activity: ReserveStock # → inventory ⎭
79
+ - activity: ProcessOrderPayment # → payments
80
+ - activity: ConfirmOrder # → orders (local)
81
+ - activity: NotifyOrderPlaced # → notifications (async)
82
+ ```
83
+
84
+ ### `{domain}.yaml` — Vista Interna del Módulo
85
+
86
+ Cada domain.yaml define **lo que el módulo ES y lo que OFRECE**.
87
+
88
+ | Sección | Contenido | Ejemplo |
89
+ |---------|-----------|---------|
90
+ | `aggregates:` | Modelo de dominio (entities, VOs, enums) | Product, Order, Payment |
91
+ | `events:` | Domain Events internos (con o sin `notifies`) | ProductCreatedEvent |
92
+ | `activities:` | **Capacidades del módulo** (cross-module e internas) | GetProductById, ReserveStock, ConfirmOrder |
93
+ | `workflows:` | **Flujos internos** del módulo (single-module) | RetryChargeWorkflow, ExpireOrderWorkflow |
94
+ | `endpoints:` | API REST del módulo | GET /products, POST /orders |
95
+ | `ports:` | Servicios **EXTERNOS** (no-Temporal) | PaymentGateway HTTP |
96
+
97
+ **Regla cardinal:** El domain.yaml declara lo que el módulo **sabe hacer**. NO declara cómo otros lo usan.
98
+
99
+ ```yaml
100
+ # inventory.yaml — lo que inventory OFRECE
101
+ activities:
102
+ - name: ReserveStock # Capacidad de escritura
103
+ type: light
104
+ input: [orderId, items]
105
+ output: [success]
106
+ compensation: ReleaseStock # Su propia compensación
107
+
108
+ - name: ReleaseStock # Capacidad de compensación
109
+ type: light
110
+ input: [orderId, items]
111
+ ```
112
+
113
+ ### Workflows en `system.yaml` vs `{domain}.yaml`
114
+
115
+ **Regla de separación:**
116
+
117
+ | Criterio | Ubicación | Ejemplo |
118
+ |----------|-----------|--------|
119
+ | Flujo cruza **2+ módulos** | `system.yaml` | PlaceOrderWorkflow (orders→inventory→payments→notifications) |
120
+ | Flujo es **interno** a 1 módulo | `{domain}.yaml` | RetryChargeWorkflow (payments→payments) |
121
+
122
+ Un workflow single-module es un proceso interno del bounded context que no involucra activities de otros módulos. Se declara en el `{domain}.yaml` del módulo porque es una **capacidad interna**, no una decisión de sistema.
123
+
124
+ ```yaml
125
+ # payments.yaml — workflow INTERNO del módulo
126
+ workflows:
127
+ - name: RetryChargeWorkflow
128
+ trigger:
129
+ on: paymentFailed
130
+ taskQueue: PAYMENT_WORKFLOW_QUEUE
131
+ steps:
132
+ - activity: RetryCharge # activity local de payments
133
+ retryPolicy:
134
+ maxAttempts: 3
135
+ backoff: exponential
136
+ - activity: NotifyChargeResult # activity local de payments
137
+ ```
138
+
139
+ **Ejemplos de workflows single-module:**
140
+
141
+ | Módulo | Workflow | Propósito |
142
+ |--------|----------|---------|
143
+ | payments | `RetryChargeWorkflow` | Reintentar cobro fallido con backoff exponencial |
144
+ | inventory | `ReplenishStockWorkflow` | Reabastecer cuando el stock cae bajo mínimo |
145
+ | customers | `VerifyEmailWorkflow` | Enviar código, esperar verificación con timeout |
146
+ | orders | `ExpireOrderWorkflow` | Cancelar orden pendiente tras X minutos sin pago |
147
+
148
+ **Señales de que un workflow es single-module:**
149
+ - Todas sus activities pertenecen al mismo módulo
150
+ - No necesita datos de otros bounded contexts
151
+ - Es un proceso interno (retry, timeout, scheduling, verificación)
152
+ - Otros módulos no necesitan saber que existe
153
+
154
+ ---
155
+
156
+ ## 🔄 Proceso de Diseño (8 Pasos)
157
+
158
+ ### Paso 1: Identificar los Bounded Contexts (Módulos)
159
+
160
+ Listar los módulos del sistema con su responsabilidad principal.
161
+
162
+ ```yaml
163
+ modules:
164
+ - name: products # "Qué vendemos"
165
+ - name: customers # "A quién le vendemos"
166
+ - name: orders # "Qué nos piden"
167
+ - name: payments # "Cómo nos pagan"
168
+ - name: inventory # "Cuánto tenemos"
169
+ - name: notifications # "A quién avisamos"
170
+ ```
171
+
172
+ **Pregunta clave:** ¿Este módulo tiene su propia base de datos / tabla? Si sí, es un módulo.
173
+
174
+ ### Paso 2: Modelar los Agregados (domain.yaml)
175
+
176
+ Para cada módulo, definir el modelo de dominio puro. Este paso es **idéntico** con o sin Temporal.
177
+
178
+ ```yaml
179
+ # orders.yaml
180
+ aggregates:
181
+ - name: Order
182
+ entities:
183
+ - name: order
184
+ isRoot: true
185
+ fields: [id, customerId, status, totalAmount, ...]
186
+ enums:
187
+ - name: OrderStatus
188
+ transitions: [...]
189
+ valueObjects:
190
+ - name: ShippingAddress
191
+ ```
192
+
193
+ **No pensar en workflows todavía.** Solo el dominio puro.
194
+
195
+ ### Paso 3: Identificar los Domain Events
196
+
197
+ Para cada agregado, listar los hechos de negocio relevantes:
198
+
199
+ ```yaml
200
+ events:
201
+ - name: OrderPlacedEvent # Se colocó una orden
202
+ - name: OrderCancelledEvent # Se canceló una orden
203
+ - name: ProductCreatedEvent # Se creó un producto nuevo
204
+ - name: PaymentApprovedEvent # Se aprobó un pago
205
+ ```
206
+
207
+ **Pregunta clave para cada evento:** ¿Algo DEBE ocurrir en OTRO módulo cuando esto pasa?
208
+
209
+ | Evento | ¿Efecto cross-module? | Resultado |
210
+ |--------|----------------------|-----------|
211
+ | OrderPlacedEvent | SÍ → reservar stock, cobrar, notificar | **Workflow** |
212
+ | OrderCancelledEvent | SÍ → liberar stock, reembolsar, notificar | **Workflow** |
213
+ | ProductCreatedEvent | SÍ → inicializar stock en inventory | **Workflow** |
214
+ | ProductUpdatedEvent | NO → nadie necesita reaccionar | **Domain Event interno** |
215
+ | CustomerCreatedEvent | NO → nadie necesita reaccionar | **Domain Event interno** |
216
+ | PaymentApprovedEvent | NO → ya está dentro del workflow de orden | **Domain Event interno** |
217
+
218
+ ### Paso 4: Diseñar los Workflows (system.yaml)
219
+
220
+ Para cada evento que **SÍ tiene efecto cross-module**, diseñar el workflow.
221
+
222
+ **Árbol de decisión para cada step:**
223
+
224
+ ```
225
+ ¿Este step modifica datos?
226
+
227
+ ├─ SÍ → ¿En otro módulo?
228
+ │ │
229
+ │ ├─ SÍ → ¿Necesita compensación si un paso posterior falla?
230
+ │ │ │
231
+ │ │ ├─ SÍ → Activity con compensation: (dentro de saga: true)
232
+ │ │ │ Ej: ReserveStock + compensation: ReleaseStock
233
+ │ │ │
234
+ │ │ └─ NO → Activity sin compensación
235
+ │ │ Ej: paso final de la saga, nada que deshacer
236
+ │ │
237
+ │ └─ NO → Activity LOCAL del propio módulo
238
+ │ Ej: ConfirmOrder (orders actualiza su propia orden)
239
+
240
+ └─ NO → ¿Lee datos de otro módulo?
241
+
242
+ ├─ SÍ → Activity de lectura (Remote Activity)
243
+ │ Ej: GetCustomerById → customers
244
+
245
+ └─ NO → ¿Notifica sin esperar respuesta?
246
+
247
+ ├─ SÍ → Activity async (type: async / Async.function)
248
+ │ Ej: NotifyOrderPlaced → notifications
249
+
250
+ └─ NO → No necesita step
251
+ ```
252
+
253
+ **¿Steps paralelos?** Si 2+ steps son **independientes entre sí**, marcarlos con `parallel: true`:
254
+
255
+ ```yaml
256
+ # GetProductsByIds y ReserveStock no dependen uno del otro
257
+ - activity: GetProductsByIds
258
+ parallel: true # Async.function()
259
+ - activity: ReserveStock
260
+ parallel: true # Async.function()
261
+ # → Promise.allOf(productPromise, stockPromise).get()
262
+ ```
263
+
264
+ ### Paso 5: Definir las Activities de cada Módulo (domain.yaml)
265
+
266
+ Cada módulo declara las **capacidades** que ofrece — tanto a workflows cross-module como a sus propios workflows internos.
267
+
268
+ **4 tipos de activities:**
269
+
270
+ | Tipo | Ámbito | Propósito | Ejemplo | Task Queue |
271
+ |------|--------|-----------|---------|------------|
272
+ | **Lectura** | Cross-module | Consultar datos del módulo | `GetCustomerById` | `{MOD}_LIGHT_TASK_QUEUE` |
273
+ | **Escritura** | Cross-module | Ejecutar una operación de negocio | `ReserveStock`, `ProcessPayment` | `{MOD}_LIGHT_TASK_QUEUE` o `{MOD}_HEAVY_TASK_QUEUE` |
274
+ | **Compensación** | Cross-module | Deshacer una operación | `ReleaseStock`, `RefundPayment` | misma que su operación |
275
+ | **Local** | Interno | Operación invocada por workflows del propio módulo | `ConfirmOrder`, `RetryCharge` | `{MOD}_LIGHT_TASK_QUEUE` |
276
+
277
+ > **Nota:** Una activity **local** y una **cross-module** se declaran igual en `activities[]` — la diferencia es solo quién la invoca. Si un workflow de `system.yaml` referencia una activity, es cross-module. Si solo la usan workflows del propio `{domain}.yaml`, es local. No hay distinción sintáctica.
278
+
279
+ **Regla de aislamiento de datos:** Cada activity accede **SOLO** a la base de datos de su propio módulo. Datos de otro bounded context deben llegar como input del workflow — la activity nunca consulta repositorios ajenos.
280
+
281
+ ```yaml
282
+ # ❌ INCORRECTO — la activity consulta la BD de otro módulo
283
+ activities:
284
+ - name: NotifyOrderPlaced
285
+ input: [orderId, customerId]
286
+ # Internamente: customerRepo.findById(customerId) ← ACOPLAMIENTO CROSS-MODULE
287
+
288
+ # ✅ CORRECTO — el workflow ensambla la data y se la pasa
289
+ activities:
290
+ - name: NotifyOrderPlaced
291
+ input: [orderId, customerEmail, customerName, totalAmount]
292
+ # No necesita saber nada de customers
293
+ ```
294
+
295
+ > **Aclaración:** Una activity SÍ consulta su propia BD. `ReserveStock` lee el stock actual de su repositorio de inventory — eso es correcto. Lo prohibido es que una activity de inventory consulte la tabla de customers.
296
+
297
+ **Regla de compensación explícita:** Si una activity tiene compensación, declararla en el mismo módulo.
298
+
299
+ ```yaml
300
+ activities:
301
+ - name: ReserveStock
302
+ compensation: ReleaseStock # ← declarado aquí mismo
303
+
304
+ - name: ReleaseStock # ← la compensación es otra activity
305
+ input: [orderId, items]
306
+ ```
307
+
308
+ ### Paso 6: Conectar Events con Workflows (notifies)
309
+
310
+ Solo los eventos que disparan workflows llevan `notifies:`.
311
+
312
+ ```yaml
313
+ # orders.yaml — el evento SABE qué workflow lanza
314
+ events:
315
+ - name: OrderPlacedEvent
316
+ notifies:
317
+ - workflow: PlaceOrderWorkflow # definido en system.yaml
318
+
319
+ - name: OrderCancelledEvent
320
+ notifies:
321
+ - workflow: CancelOrderWorkflow
322
+ ```
323
+
324
+ ```yaml
325
+ # customers.yaml — el evento NO lanza ningún workflow
326
+ events:
327
+ - name: CustomerCreatedEvent
328
+ # SIN notifies → Domain Event interno puro
329
+ # Otros módulos obtienen datos de customer via GetCustomerById
330
+ ```
331
+
332
+ **Regla de desacoplamiento:** Si un evento solo sirve para que **otro módulo se entere** de datos actualizados (sincronización), **NO necesita notifies**. El workflow que necesite esos datos los obtiene on-demand via Remote Activity de lectura.
333
+
334
+ ### Paso 7: Diseñar Workflows Internos (domain.yaml)
335
+
336
+ Para cada módulo, identificar si existen **procesos internos que necesitan durabilidad**: reintentos con backoff, timeouts, scheduling, verificaciones con espera.
337
+
338
+ **Pregunta clave:** ¿Este proceso interno necesita sobrevivir a reinicios del servicio, tiene reintentos complejos, o espera eventos con timeout?
339
+
340
+ | SÍ → | NO → |
341
+ |-------|-------|
342
+ | Workflow single-module en `{domain}.yaml` | Lógica síncrona directa (service method, @Scheduled) |
343
+
344
+ ```yaml
345
+ # orders.yaml — workflow INTERNO
346
+ workflows:
347
+ - name: ExpireOrderWorkflow
348
+ trigger:
349
+ on: orderCreated
350
+ taskQueue: ORDER_WORKFLOW_QUEUE
351
+ steps:
352
+ - activity: WaitForPayment # Workflow.sleep() o Signal + Await
353
+ timeout: 30m
354
+ - activity: CancelExpiredOrder # activity local de orders
355
+ ```
356
+
357
+ ```yaml
358
+ # payments.yaml — workflow INTERNO
359
+ workflows:
360
+ - name: RetryChargeWorkflow
361
+ trigger:
362
+ on: paymentFailed
363
+ taskQueue: PAYMENT_WORKFLOW_QUEUE
364
+ steps:
365
+ - activity: RetryCharge # activity local de payments
366
+ retryPolicy:
367
+ maxAttempts: 3
368
+ backoff: exponential
369
+ - activity: NotifyChargeResult # activity local de payments
370
+ ```
371
+
372
+ **Regla:** Los workflows single-module solo componen activities del propio módulo. Si descubres que un step necesita datos o acciones de otro módulo, el workflow debe subir a `system.yaml`.
373
+
374
+ ### Paso 8: Declarar Servicios Externos (ports)
375
+
376
+ `ports[]` solo se usa para servicios que **NO corren workers Temporal**: APIs de terceros, payment gateways, servicios SaaS externos.
377
+
378
+ ```yaml
379
+ # payments.yaml — el gateway externo es HTTP puro
380
+ ports:
381
+ - name: processCharge
382
+ service: PaymentGatewayService
383
+ target: payment-gateway
384
+ baseUrl: https://api.payments.example.com
385
+ http: POST /charges
386
+ ```
387
+
388
+ **Regla:** Si el servicio está **dentro** de tu sistema (tiene domain.yaml), usa Activities. Si es **externo**, usa `ports[]`.
389
+
390
+ ---
391
+
392
+ ## ✅ Checklist por Tipo de Módulo
393
+
394
+ > **Nota:** Los roles no son categorías excluyentes — un módulo puede combinar características de varios tipos. Por ejemplo, `orders` es principalmente Orquestador (sus eventos disparan sagas), pero también declara activities locales (`ConfirmOrder`) y workflows internos (`ExpireOrderWorkflow`). Los checklists son guías por **rol principal**, no restricciones absolutas.
395
+
396
+ ### Módulo Orquestador (ej: orders)
397
+
398
+ Es el módulo que **inicia** los workflows cross-module. Sus eventos disparan sagas.
399
+
400
+ - [ ] `events[].notifies` → apunta a workflows en system.yaml
401
+ - [ ] Puede tener `activities[]` **locales** invocadas por workflows que corren en su queue (ej: `ConfirmOrder`)
402
+ - [ ] Puede tener `workflows[]` **single-module** para procesos internos (ej: `ExpireOrderWorkflow`)
403
+ - [ ] **NO tiene** `listeners[]`, `readModels[]`
404
+ - [ ] **NO** declara sagas cross-module — la orquestación va en system.yaml
405
+
406
+ ```yaml
407
+ # orders.yaml — orquestador
408
+ events:
409
+ - name: OrderPlacedEvent
410
+ notifies:
411
+ - workflow: PlaceOrderWorkflow
412
+
413
+ activities:
414
+ - name: ConfirmOrder # local: invocada por PlaceOrderWorkflow
415
+ type: light
416
+
417
+ workflows:
418
+ - name: ExpireOrderWorkflow # interno: cancelar orden si no paga
419
+ ```
420
+
421
+ ### Módulo Proveedor de Datos (ej: customers, products)
422
+
423
+ Sus datos son consumidos **on-demand** por workflows de otros módulos.
424
+
425
+ - [ ] `activities[]` → al menos una activity de **lectura** (GetXById)
426
+ - [ ] `events[]` → Domain Events internos, **SIN notifies** (a menos que haya un efecto de negocio real)
427
+ - [ ] Considerar activities **batch** si se consultan múltiples registros (GetProductsByIds)
428
+
429
+ ```yaml
430
+ # customers.yaml — proveedor de datos
431
+ activities:
432
+ - name: GetCustomerById # ← lectura on-demand
433
+ type: light
434
+ input: [customerId]
435
+ output: [id, firstName, lastName, email, phone]
436
+
437
+ events:
438
+ - name: CustomerCreatedEvent # ← interno, SIN notifies
439
+ ```
440
+
441
+ ### Módulo Ejecutor (ej: inventory, payments)
442
+
443
+ Ofrece **operaciones de negocio** que los workflows invocan.
444
+
445
+ - [ ] `activities[]` → operaciones de escritura + sus compensaciones
446
+ - [ ] `compensation:` → declarada explícitamente en cada activity reversible
447
+ - [ ] `timeout:` y `retryPolicy:` → configurados por activity
448
+ - [ ] `ports[]` → solo si llama a servicios EXTERNOS (ej: payment gateway)
449
+
450
+ ```yaml
451
+ # inventory.yaml — ejecutor
452
+ activities:
453
+ - name: ReserveStock
454
+ type: light
455
+ input: [orderId, items]
456
+ compensation: ReleaseStock
457
+
458
+ - name: ReleaseStock
459
+ type: light
460
+ input: [orderId, items]
461
+ ```
462
+
463
+ ### Módulo Reactor (ej: notifications)
464
+
465
+ Ejecuta efectos secundarios (emails, SMS, webhooks) invocados por workflows.
466
+
467
+ - [ ] `activities[]` → reciben **TODA** la data como input (zero lookups)
468
+ - [ ] Invocados como `type: async` en el workflow (non-blocking)
469
+ - [ ] **NO** tienen `readModels[]` — son stateless para datos cross-module
470
+ - [ ] Solo persisten sus propias entidades (ej: registro de notificación enviada)
471
+
472
+ ```yaml
473
+ # notifications.yaml — reactor
474
+ activities:
475
+ - name: NotifyOrderPlaced
476
+ type: light
477
+ input: [orderId, customerEmail, customerName, totalAmount]
478
+ # Toda la data viene del workflow caller
479
+ ```
480
+
481
+ ---
482
+
483
+ ## 🏗️ Patrones de Diseño de Workflows
484
+
485
+ ### Patrón 1: Saga con Compensación
486
+
487
+ Flujo multi-step donde el fallo de un paso deshace los anteriores.
488
+
489
+ ```yaml
490
+ workflows:
491
+ - name: PlaceOrderWorkflow
492
+ saga: true
493
+ steps:
494
+ - activity: ReserveStock
495
+ compensation: ReleaseStock # ← se ejecuta si paso posterior falla
496
+ - activity: ProcessOrderPayment
497
+ compensation: RefundPayment
498
+ - activity: ConfirmOrder # último paso, sin compensación
499
+ ```
500
+
501
+ **Cuándo:** Operaciones que involucran escritura en múltiples módulos y necesitan consistencia eventual.
502
+
503
+ ### Patrón 2: Enriquecimiento + Acción
504
+
505
+ Obtener datos de lectura antes de ejecutar la acción principal.
506
+
507
+ ```yaml
508
+ steps:
509
+ # 1. Enriquecimiento (lectura on-demand)
510
+ - activity: GetCustomerById
511
+ output: [firstName, email]
512
+
513
+ # 2. Acción (usa los datos obtenidos)
514
+ - activity: ProcessOrderPayment
515
+ input: [orderId, customerId, totalAmount]
516
+ ```
517
+
518
+ **Cuándo:** El workflow necesita datos de otro módulo para tomar decisiones o pasarlos a steps posteriores.
519
+
520
+ ### Patrón 3: Steps Paralelos (Async.function)
521
+
522
+ Ejecutar steps independientes en paralelo para reducir latencia.
523
+
524
+ ```yaml
525
+ steps:
526
+ - activity: GetProductsByIds
527
+ parallel: true # ⎫ ejecutar juntos
528
+ - activity: ReserveStock # ⎭
529
+ parallel: true
530
+ # → Promise.allOf().get()
531
+ - activity: ProcessOrderPayment # después de ambos
532
+ ```
533
+
534
+ **Cuándo:** 2+ steps son independientes entre sí (no usan el output del otro).
535
+
536
+ ### Patrón 4: Efecto de Negocio Puntual
537
+
538
+ Un evento dispara una única acción en otro módulo.
539
+
540
+ ```yaml
541
+ workflows:
542
+ - name: ProductCreatedWorkflow
543
+ trigger:
544
+ module: products
545
+ on: create
546
+ steps:
547
+ - activity: InitializeStock # solo 1 step
548
+ compensation: DeleteStock
549
+ ```
550
+
551
+ **Cuándo:** Efecto colateral simple, no una saga compleja.
552
+
553
+ ### Patrón 5: Notificación Non-Blocking
554
+
555
+ Último step del workflow, no afecta el resultado de la saga.
556
+
557
+ ```yaml
558
+ steps:
559
+ # ... pasos críticos de la saga ...
560
+ - activity: NotifyOrderPlaced
561
+ type: async # fire-and-forget
562
+ # NO tiene compensation — si falla, no revierte la saga
563
+ ```
564
+
565
+ **Cuándo:** Efectos secundarios no-críticos (emails, logs, analytics).
566
+
567
+ ---
568
+
569
+ ## ⚠️ Errores Comunes
570
+
571
+ ### ❌ Crear workflows de sincronización de datos
572
+
573
+ ```yaml
574
+ # ❌ NO HACER — workflow que solo sincroniza datos
575
+ - name: CustomerUpdatedWorkflow
576
+ steps:
577
+ - activity: SyncCustomerReadModel
578
+ target: orders
579
+ - activity: SyncCustomerReadModel
580
+ target: notifications
581
+
582
+ # ✅ SÍ HACER — lectura on-demand en el workflow que necesita el dato
583
+ # PlaceOrderWorkflow → GetCustomerById → customers
584
+ ```
585
+
586
+ **Por qué:** Los workflows de sync acoplan al productor con todos sus consumidores. La lectura on-demand invierte la dependencia.
587
+
588
+ ### ❌ Activities que hacen lookups cross-module
589
+
590
+ ```yaml
591
+ # ❌ NO HACER — la activity de notifications busca datos de customers
592
+ activities:
593
+ - name: NotifyOrderPlaced
594
+ input: [orderId, customerId]
595
+ # Internamente: fetch customer data from DB/service
596
+
597
+ # ✅ SÍ HACER — el workflow obtiene los datos y los pasa
598
+ activities:
599
+ - name: NotifyOrderPlaced
600
+ input: [orderId, customerEmail, customerName, totalAmount]
601
+ # La activity tiene todo lo que necesita
602
+ ```
603
+
604
+ **Por qué:** Si notifications busca datos de customers internamente, crea una dependencia oculta. El workflow debe ser el orquestador que ensambla la data.
605
+
606
+ ### ❌ Poner orquestación **cross-module** en el domain.yaml
607
+
608
+ ```yaml
609
+ # ❌ NO HACER en orders.yaml
610
+ saga:
611
+ workflow: PlaceOrderWorkflow
612
+ steps: [...]
613
+
614
+ # ✅ SÍ HACER — la orquestación cross-module va en system.yaml
615
+ # El domain.yaml solo declara el evento con notifies:
616
+ events:
617
+ - name: OrderPlacedEvent
618
+ notifies:
619
+ - workflow: PlaceOrderWorkflow
620
+ ```
621
+
622
+ **Por qué:** La orquestación cross-module es una **decisión de sistema**, no del módulo individual.
623
+
624
+ > **Excepción:** Los workflows **single-module** SÍ se declaran en `{domain}.yaml` porque son procesos internos del bounded context (ej: `RetryChargeWorkflow`, `ExpireOrderWorkflow`). La regla aplica solo a sagas que tocan 2+ módulos.
625
+
626
+ ### ❌ Usar `notifies:` para eventos que no tienen efecto cross-module
627
+
628
+ ```yaml
629
+ # ❌ NO HACER — evento de sync disfrazado de workflow
630
+ events:
631
+ - name: CustomerUpdatedEvent
632
+ notifies:
633
+ - workflow: CustomerUpdatedWorkflow # solo sincroniza datos
634
+
635
+ # ✅ SÍ HACER — Domain Event interno
636
+ events:
637
+ - name: CustomerUpdatedEvent
638
+ # SIN notifies — otros módulos obtienen la data on-demand
639
+ ```
640
+
641
+ ### ❌ Olvidar `compensation:` en activities dentro de sagas
642
+
643
+ ```yaml
644
+ # ❌ NO HACER — sin compensación, la saga no puede deshacer
645
+ steps:
646
+ - activity: ReserveStock # ¿qué pasa si el pago falla?
647
+
648
+ # ✅ SÍ HACER
649
+ steps:
650
+ - activity: ReserveStock
651
+ compensation: ReleaseStock # deshace si falla un paso posterior
652
+ ```
653
+
654
+ ---
655
+
656
+ ## 📊 Matriz de Decisión: ¿Es un Workflow o un Domain Event Interno?
657
+
658
+ | Pregunta | SÍ → | NO → |
659
+ |----------|------|------|
660
+ | ¿Algo DEBE ocurrir en otro módulo? | Workflow cross-module (system.yaml) | ↓ siguiente pregunta |
661
+ | ¿Es un proceso interno con durabilidad (retry, timeout, scheduling)? | Workflow single-module (domain.yaml) | Domain Event interno |
662
+ | ¿Son múltiples pasos coordinados cross-module? | Saga (workflow) | Activity puntual |
663
+ | ¿Necesita consistencia (compensación)? | Saga con compensación | Activity async |
664
+ | ¿Solo otro módulo necesita enterarse de datos nuevos? | **NI workflow NI notifies** — lectura on-demand | — |
665
+
666
+ ---
667
+
668
+ ## 📊 Matriz de Decisión: ¿Qué Tipo de Activity?
669
+
670
+ | Pregunta | Tipo |
671
+ |----------|------|
672
+ | ¿Solo lee datos de este módulo? | **Lectura** (`GetXById`, `GetXsByIds`) |
673
+ | ¿Modifica datos y puede deshacerse? | **Escritura** + `compensation:` |
674
+ | ¿Modifica datos irreversiblemente? | **Escritura** sin compensación |
675
+ | ¿Es el reverso de otra activity? | **Compensación** (referenciada en `compensation:`) |
676
+ | ¿Es un efecto secundario no-crítico? | **Reactor** (invocado como `type: async`) |
677
+ | ¿Es una operación interna del módulo? | **Local** (invocada por workflows del propio `{domain}.yaml` o por un workflow cross-module que corre en el queue del módulo) |
678
+
679
+ ---
680
+
681
+ ## 🔧 Convenciones de Naming
682
+
683
+ ### Task Queues (Module-Prefixed)
684
+
685
+ ```
686
+ {MODULE_SCREAMING_SNAKE}_WORKFLOW_QUEUE → ORDER_WORKFLOW_QUEUE
687
+ {MODULE_SCREAMING_SNAKE}_LIGHT_TASK_QUEUE → ORDER_LIGHT_TASK_QUEUE
688
+ {MODULE_SCREAMING_SNAKE}_HEAVY_TASK_QUEUE → PAYMENT_HEAVY_TASK_QUEUE
689
+ ```
690
+
691
+ ### Activities
692
+
693
+ | Tipo | Patrón | Ejemplo |
694
+ |------|--------|---------|
695
+ | Lectura singular | `Get{Entity}ById` | `GetCustomerById` |
696
+ | Lectura batch | `Get{Entities}ByIds` | `GetProductsByIds` |
697
+ | Escritura | `{Verbo}{Sustantivo}` | `ReserveStock`, `ProcessOrderPayment` |
698
+ | Compensación | `{Verbo inverso}{Sustantivo}` | `ReleaseStock`, `RefundPayment` |
699
+ | Reactor | `Notify{Evento}` | `NotifyOrderPlaced`, `NotifyOrderCancelled` |
700
+ | Local | `{Verbo}{Sustantivo}` (mismo patrón que escritura) | `ConfirmOrder`, `RetryCharge` |
701
+
702
+ ### Workflows
703
+
704
+ ```
705
+ {Verbo}{Entidad}Workflow → PlaceOrderWorkflow
706
+ {Entidad}{Evento}Workflow → ProductCreatedWorkflow
707
+ {Verbo}{Entidad}Workflow → CancelOrderWorkflow
708
+ ```
709
+
710
+ ---
711
+
712
+ ## 📦 Resumen: Distribución de Responsabilidades
713
+
714
+ ```
715
+ system.yaml
716
+ ├── system: → Metadata del proyecto
717
+ ├── orchestration: → Configuración de Temporal (target, namespace)
718
+ ├── modules: → Lista de módulos + endpoints REST
719
+ └── workflows: → Flujos cross-module (sagas, efectos de negocio)
720
+ ¿QUIÉN orquesta QUÉ?
721
+
722
+ {domain}.yaml
723
+ ├── aggregates: → Modelo de dominio (entities, VOs, enums, transitions)
724
+ ├── events: → Domain Events (con o sin notifies: → workflow)
725
+ ├── activities: → Capacidades del módulo (cross-module + internas)
726
+ ├── workflows: → Flujos INTERNOS del módulo (single-module, no cross-module)
727
+ ├── endpoints: → API REST del módulo
728
+ └── ports: → Solo servicios EXTERNOS no-Temporal (HTTP)
729
+ ¿QUÉ sabe hacer este módulo?
730
+ ```
731
+
732
+ **Principio fundamental:**
733
+ > `system.yaml` define **la coreografía** (quién habla con quién).
734
+ > `{domain}.yaml` define **las capacidades** (qué puede hacer cada uno).
735
+ > Los workflows cross-module **ensamblan** las capacidades en flujos de negocio.
736
+ > Los workflows single-module son **procesos internos** del bounded context.
737
+
738
+ ---
739
+
740
+ **Última actualización:** 2026-04-07