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,277 @@
1
+ # Temporal-Only Architecture: Análisis de Riesgos y Trade-offs
2
+
3
+ ## 📐 Visión General
4
+
5
+ Este documento analiza los riesgos de usar **Temporal como único mecanismo de
6
+ comunicación** entre módulos, reemplazando Kafka (async) y Feign (sync).
7
+
8
+ Los archivos de diseño en esta carpeta (`docs/prototype/system/`) reflejan cómo
9
+ se vería la arquitectura `test-eva` bajo este enfoque.
10
+
11
+ **Actualización (v2):** Se eliminaron Read Models. Los datos cross-module se
12
+ obtienen on-demand via Remote Activities de lectura (`GetCustomerById`,
13
+ `GetProductsByIds`). Se eliminaron 4 workflows que solo sincronizaban datos
14
+ (`ProductUpdated/Deactivated`, `CustomerCreated/Updated`). Esto reduce
15
+ significativamente el acoplamiento productor→consumidor (R1).
16
+
17
+ ---
18
+
19
+ ## 🔴 Riesgos Altos
20
+
21
+ ### R1. Acoplamiento del productor a sus consumidores
22
+
23
+ **Kafka (actual):**
24
+ ```
25
+ products publica ProductCreatedEvent → no sabe quién consume
26
+ ├── inventory (se suscribe solo)
27
+ └── orders (se suscribe solo)
28
+ └── [futuro módulo X] (se suscribe sin tocar products)
29
+ ```
30
+
31
+ **Temporal (prototipo v2):**
32
+ ```
33
+ products.ProductCreatedWorkflow lista explícitamente:
34
+ └── step: InitializeStock → target: inventory
35
+ (solo 1 consumer — efecto de negocio real, no sync de datos)
36
+
37
+ Pero: cualquier workflow puede invocar GetProductById/GetProductsByIds
38
+ sin que products lo sepa → desacoplamiento por lectura on-demand.
39
+ ```
40
+
41
+ **Impacto (v2 — REDUCIDO):**
42
+ Con la eliminación de read models, products ya no necesita saber que orders
43
+ consume sus datos. Solo mantiene `notifies: ProductCreatedWorkflow` porque
44
+ InitializeStock es un **efecto de negocio real** (crear stock), no sincronización.
45
+
46
+ `customers` queda **completamente desacoplado** — sus Domain Events son internos
47
+ y no notifican a nadie. Los workflows de orders obtienen datos del cliente via
48
+ `GetCustomerById` (Remote Activity) on-demand.
49
+
50
+ **Severidad:** 🟡 Media (reducida de 🔴 Alta) — el acoplamiento solo existe
51
+ para efectos de negocio reales (InitializeStock), no para sincronización de datos.
52
+
53
+ ---
54
+
55
+ ### R2. Pérdida de comunicación asíncrona real (fire-and-forget)
56
+
57
+ **Kafka:** El productor publica y continúa. Los consumidores procesan a su ritmo.
58
+ Si notifications tarda 30s en enviar un email, orders no se entera ni se bloquea.
59
+
60
+ **Temporal:** Aunque una activity se marque como `type: async` en el workflow, el
61
+ **workflow como instancia sigue vivo** hasta que todos los pasos terminen. Temporal
62
+ trackea el estado de cada step. Si NotifyOrderPlaced falla 3 veces, el workflow
63
+ completo queda en estado "running" indefinidamente.
64
+
65
+ **Mitigación:** Usar `Async.function()` para activities non-critical
66
+ (notificaciones) y NOT registrar compensación en la saga. Si falla, el workflow
67
+ no se bloquea — solo se pierde la notificación.
68
+
69
+ **Severidad:** 🔴 Alta — afecta resiliencia y operaciones.
70
+
71
+ ---
72
+
73
+ ### R3. Single Point of Failure: Temporal Server
74
+
75
+ **Kafka:** El broker tiene replicación nativa (particiones, ISR). Si un broker
76
+ cae, otro toma el liderazgo de la partición. Los productores y consumidores
77
+ tienen decenas de librerías y patrones maduros para manejar desconexiones.
78
+
79
+ **Temporal:** El Temporal Server (frontend + history + matching + worker services)
80
+ es el punto central. Si cae:
81
+ - Ningún workflow puede avanzar
82
+ - Ninguna activity puede ejecutarse
83
+ - La comunicación entre TODOS los módulos se detiene simultáneamente
84
+
85
+ Temporal sí soporta clusters multi-nodo, pero la complejidad operativa es mayor
86
+ que Kafka para este tipo de uso (Temporal no fue diseñado como message bus).
87
+
88
+ **Severidad:** 🔴 Alta — riesgo operativo en producción.
89
+
90
+ ---
91
+
92
+ ## 🟡 Riesgos Medios
93
+
94
+ ### R4. Complejidad operativa: N workers por módulo
95
+
96
+ Con Kafka, cada módulo tiene un consumer group. Escalar = agregar instancias.
97
+
98
+ Con Temporal y module-prefixed queues, cada módulo tiene su propio set de queues:
99
+ ```
100
+ {MODULE}_WORKFLOW_QUEUE, {MODULE}_LIGHT_TASK_QUEUE, {MODULE}_HEAVY_TASK_QUEUE
101
+ ```
102
+
103
+ Esto permite **escalado selectivo** (ej: más workers para
104
+ `PAYMENT_HEAVY_TASK_QUEUE`), pero multiplica la cantidad de workers en deployment.
105
+
106
+ **Severidad:** 🟡 Media — manejable con module-scoped queues.
107
+
108
+ ---
109
+
110
+ ### R5. Latencia on-demand vs Read Models locales
111
+
112
+ Sin read models, cada workflow que necesita datos cross-module hace una llamada
113
+ on-demand via Remote Activity:
114
+ - `PlaceOrderWorkflow` → `GetCustomerById` (~5ms) + `GetProductsByIds` (~10ms)
115
+ - `CancelOrderWorkflow` → `GetCustomerById` (~5ms)
116
+
117
+ Con read models, esta data ya estaba en la BD local del módulo (~1ms).
118
+
119
+ **Impacto:** ~10-15ms extra por workflow. Aceptable para volúmenes bajos/medios.
120
+ Para alto volumen, se pueden agregar **caches con TTL** en las Activities sin
121
+ necesidad de read models persistentes.
122
+
123
+ **Severidad:** 🟡 Media — trade-off aceptable para la simplificación obtenida.
124
+
125
+ ---
126
+
127
+ ### R6. Servicios externos siguen necesitando HTTP
128
+
129
+ El módulo `payments` llama a un gateway externo de pagos
130
+ (`https://api.payments.example.com`). Ese servicio:
131
+ - No corre workers Temporal
132
+ - No entiende Activities
133
+ - Solo expone HTTP
134
+
135
+ Resultado: `ports[]` (Feign) **no puede eliminarse** para integraciones externas.
136
+ Temporal solo reemplaza comunicación **interna** entre módulos propios.
137
+
138
+ En la práctica, esto crea un modelo híbrido: Temporal entre módulos internos +
139
+ HTTP para servicios externos. La promesa de "un solo mecanismo" no se cumple.
140
+
141
+ **Severidad:** 🟡 Media — la arquitectura no es tan uniforme como parece.
142
+
143
+ ---
144
+
145
+ ### R7. Modelo de Datos del Diseño: Secciones Nuevas sin soporte
146
+
147
+ El prototipo introduce conceptos que eva4j no tiene:
148
+
149
+ | Concepto nuevo | Dónde aparece | Genera código? |
150
+ |---|---|---|
151
+ | `orchestration:` en system.yaml | Reemplaza `messaging:` | ❌ No |
152
+ | `workflows:` en system.yaml | Reemplaza `integrations:` | ❌ No |
153
+ | `activities:` en domain.yaml | Concepto nuevo | ❌ No |
154
+ | `notifies:` en events | Reemplaza `topic:` | ❌ No |
155
+ | `parallel:` en workflow steps | Indica `Async.function()` | ❌ No |
156
+
157
+ Implementar esto requiere nuevos generadores, templates, y validadores.
158
+
159
+ **Nota:** `saga:` y `readModels.syncedBy.activity` fueron eliminados en v2.
160
+
161
+ **Severidad:** 🟡 Media — alto esfuerzo de desarrollo en eva4j.
162
+
163
+ ---
164
+
165
+ ## 🟢 Riesgos Bajos (Ventajas del enfoque)
166
+
167
+ ### V1. Saga nativa con compensación
168
+
169
+ El flujo `CreateOrder → GetCustomer → GetProducts ∥ ReserveStock →
170
+ ProcessPayment → ConfirmOrder → NotifyOrderPlaced` es **sustancialmente mejor**
171
+ con Temporal. La saga tiene compensación automática, timeouts durables, y
172
+ visibilidad del estado en el Temporal UI.
173
+
174
+ Con Kafka, este mismo flujo requiere implementar manualmente:
175
+ - Correlation IDs
176
+ - Estado de la saga en base de datos
177
+ - Compensación manual con eventos de rollback
178
+ - Timeout checks con scheduled jobs
179
+
180
+ **Veredicto:** Para este patrón específico, Temporal es claramente superior.
181
+
182
+ ---
183
+
184
+ ### V2. Observabilidad centralizada
185
+
186
+ Cada workflow es visible en el Temporal UI con su historial completo: qué
187
+ activities se ejecutaron, cuáles fallaron, tiempos de ejecución, payloads.
188
+
189
+ Con Kafka, rastrear un flujo cross-module requiere correlación de logs,
190
+ distributed tracing (Zipkin/Jaeger), y mucha disciplina en los correlation IDs.
191
+
192
+ ---
193
+
194
+ ### V3. Retry con backoff nativo
195
+
196
+ Temporal reintenta activities automáticamente con backoff exponencial configurable.
197
+ Con Kafka, los retrys se implementan con DLQ (Dead Letter Queue), retry topics,
198
+ o lógica manual en el consumer.
199
+
200
+ ---
201
+
202
+ ### V4. Simplicidad sin Read Models
203
+
204
+ Eliminar read models simplifica significativamente la arquitectura:
205
+ - No hay tablas `rm_*` que mantener sincronizadas
206
+ - No hay workflows/listeners de sincronización
207
+ - No hay riesgo de datos stale en proyecciones
208
+ - Menos acoplamiento productor→consumidor (customers y products no notifican
209
+ a nadie para sync)
210
+ - Cada módulo es responsable solo de sus datos; otros acceden on-demand
211
+
212
+ **Trade-off:** Latencia on-demand (~10ms vs ~1ms local). Mitigable con caches.
213
+
214
+ ---
215
+
216
+ ## 📊 Matriz Comparativa
217
+
218
+ | Criterio | Kafka + Read Models | Temporal-only (v2, sin RM) |
219
+ |---|---|---|
220
+ | Acoplamiento productor→consumidor | ✅ Nulo | 🟡 Solo para efectos de negocio |
221
+ | Agregar nuevo consumidor | ✅ Solo en consumidor | 🟡 Agregar Activity de lectura |
222
+ | Fan-out 1→N | ✅ Nativo (topics) | ⚠️ Manual (N steps en workflow) |
223
+ | Saga con compensación | ❌ Manual | ✅ Nativo |
224
+ | Replay de eventos | ✅ Desde offset 0 | ❌ No disponible |
225
+ | Observabilidad de flujos | ⚠️ Tracing externo | ✅ Temporal UI |
226
+ | SPOF | ⚠️ Broker (mitigable) | 🔴 Temporal Server |
227
+ | Servicios externos (HTTP) | ✅ Feign/ports[] | ⚠️ Híbrido obligatorio |
228
+ | Complejidad operativa | ⚠️ Kafka + Zookeeper/KRaft | ⚠️ Temporal Server + Workers |
229
+ | Soporte en eva4j | ✅ Completo | ❌ No existe |
230
+ | Escalabilidad selectiva | ✅ Consumer groups | ✅ Module-prefixed queues |
231
+ | Simplicidad del esquema | ⚠️ Tablas rm_* + sync | ✅ Sin proyecciones |
232
+ | Latencia cross-module | ✅ Local (~1ms) | 🟡 On-demand (~10ms) |
233
+ | Event sourcing futuro | ✅ Natural | ❌ No aplica |
234
+
235
+ ---
236
+
237
+ ## 🎯 Recomendación
238
+
239
+ ### Para este e-commerce: **Temporal-only es viable**
240
+
241
+ Con la eliminación de read models, la arquitectura Temporal-only se simplifica
242
+ considerablemente. Los riesgos principales se reducen:
243
+
244
+ ```
245
+ R1 (acoplamiento): 🔴 → 🟡 (solo efectos de negocio, no sync de datos)
246
+ R5 (read models): 🟡 → ✅ (eliminado — reemplazado por latencia on-demand)
247
+ ```
248
+
249
+ La arquitectura queda:
250
+
251
+ ```
252
+ ┌──────────────────────────────────────────────────────────────┐
253
+ │ Comunicación │
254
+ ├──────────────────────────────┬───────────────────────────────┤
255
+ │ Temporal │ Feign (HTTP) │
256
+ │ │ │
257
+ │ • Saga de orden │ • Servicios externos │
258
+ │ (PlaceOrder, CancelOrder) │ (payment gateway) │
259
+ │ • Efectos de negocio │ │
260
+ │ (ProductCreated→InitStock) │ │
261
+ │ • Lectura on-demand │ │
262
+ │ (GetCustomer, GetProducts) │ │
263
+ │ • Notificaciones │ │
264
+ │ (Async.function, no-block) │ │
265
+ │ • Retry + compensación │ │
266
+ └──────────────────────────────┴───────────────────────────────┘
267
+ ```
268
+
269
+ ### Cuándo escalar a Kafka + Temporal complementarios
270
+
271
+ Si el sistema evoluciona a:
272
+ - **15+ módulos** con fan-out 1→N frecuente → Kafka para eventos de dominio
273
+ - **Alto volumen** donde ~10ms on-demand es inaceptable → Read Models + Kafka
274
+ - **Event sourcing** necesario → Kafka como log de eventos
275
+
276
+ Para un e-commerce de complejidad media-baja, Temporal-only cubre todos los
277
+ casos con menos infraestructura y menos complejidad operativa.
@@ -0,0 +1,133 @@
1
+ # =============================================================================
2
+ # PROTOTYPE: customers.yaml — versión Temporal (sin Kafka)
3
+ # =============================================================================
4
+
5
+ aggregates:
6
+ - name: Customer
7
+ entities:
8
+ - name: customer
9
+ isRoot: true
10
+ tableName: customers
11
+ audit:
12
+ enabled: true
13
+ fields:
14
+ - name: id
15
+ type: String
16
+ - name: firstName
17
+ type: String
18
+ validations:
19
+ - type: NotBlank
20
+ message: "First name is required"
21
+ - name: lastName
22
+ type: String
23
+ validations:
24
+ - type: NotBlank
25
+ message: "Last name is required"
26
+ - name: email
27
+ type: String
28
+ validations:
29
+ - type: NotBlank
30
+ message: "Email is required"
31
+ - type: Email
32
+ message: "Invalid email format"
33
+ - name: phone
34
+ type: String
35
+ validations:
36
+ - type: NotBlank
37
+ message: "Phone number is required"
38
+ - name: address
39
+ type: Address
40
+
41
+ valueObjects:
42
+ - name: Address
43
+ fields:
44
+ - name: street
45
+ type: String
46
+ - name: city
47
+ type: String
48
+ - name: department
49
+ type: String
50
+ - name: zipCode
51
+ type: String
52
+ - name: neighborhood
53
+ type: String
54
+ methods:
55
+ - name: format
56
+ returnType: String
57
+ parameters: []
58
+ body: "return street + \", \" + neighborhood + \", \" + city + \" (\" + department + \") \" + zipCode;"
59
+
60
+ events:
61
+ - name: CustomerCreatedEvent
62
+ lifecycle: create
63
+ fields:
64
+ - name: customerId
65
+ type: String
66
+ - name: firstName
67
+ type: String
68
+ - name: lastName
69
+ type: String
70
+ - name: email
71
+ type: String
72
+ - name: phone
73
+ type: String
74
+ # SIN notifies → Domain Event interno.
75
+ # Sin read models, no hay workflows de sincronización.
76
+ # Los workflows que necesitan datos de cliente los obtienen
77
+ # on-demand via GetCustomerById (Remote Activity).
78
+
79
+ - name: CustomerUpdatedEvent
80
+ lifecycle: update
81
+ fields:
82
+ - name: customerId
83
+ type: String
84
+ - name: firstName
85
+ type: String
86
+ - name: lastName
87
+ type: String
88
+ - name: email
89
+ type: String
90
+ - name: phone
91
+ type: String
92
+ # SIN notifies → Domain Event interno.
93
+
94
+ # ─── Activities que este módulo EXPONE ───────────────────────────────────────
95
+ # Activity de lectura: permite a PlaceOrderWorkflow y CancelOrderWorkflow
96
+ # obtener datos del cliente on-demand (nombre, email para notificaciones).
97
+ activities:
98
+ - name: GetCustomerById
99
+ type: light
100
+ description: "Obtiene un cliente por su ID"
101
+ input:
102
+ - name: customerId
103
+ type: String
104
+ output:
105
+ - name: customerId
106
+ type: String
107
+ - name: firstName
108
+ type: String
109
+ - name: lastName
110
+ type: String
111
+ - name: email
112
+ type: String
113
+ - name: phone
114
+ type: String
115
+ timeout: 5s
116
+
117
+ endpoints:
118
+ basePath: /customers
119
+ versions:
120
+ - version: v1
121
+ operations:
122
+ - useCase: GetCustomer
123
+ method: GET
124
+ path: /{id}
125
+ - useCase: FindAllCustomers
126
+ method: GET
127
+ path: /
128
+ - useCase: CreateCustomer
129
+ method: POST
130
+ path: /
131
+ - useCase: UpdateCustomer
132
+ method: PUT
133
+ path: /{id}
@@ -0,0 +1,109 @@
1
+ # =============================================================================
2
+ # PROTOTYPE: inventory.yaml — versión Temporal (sin Kafka)
3
+ # =============================================================================
4
+ # Cambios vs original:
5
+ # - ELIMINA listeners[] — ya no consume ProductCreatedEvent por Kafka
6
+ # - AGREGA activities[] — expone Activities para que workflows las invoquen
7
+ # - endpoints, aggregates → SIN CAMBIOS
8
+ # =============================================================================
9
+
10
+ aggregates:
11
+ - name: Stock
12
+ entities:
13
+ - name: stock
14
+ isRoot: true
15
+ tableName: stock_items
16
+ audit:
17
+ enabled: true
18
+ fields:
19
+ - name: id
20
+ type: String
21
+ - name: productId
22
+ type: String
23
+ reference:
24
+ aggregate: Product
25
+ module: products
26
+ validations:
27
+ - type: NotBlank
28
+ message: "Product ID is required"
29
+ - name: quantity
30
+ type: Integer
31
+ validations:
32
+ - type: PositiveOrZero
33
+ message: "Quantity cannot be negative"
34
+
35
+ # ─── Activities que este módulo EXPONE ───────────────────────────────────────
36
+ activities:
37
+ - name: InitializeStock
38
+ type: light
39
+ description: "Crea un registro de stock inicial para un producto nuevo"
40
+ input:
41
+ - name: productId
42
+ type: String
43
+ - name: name
44
+ type: String
45
+ timeout: 10s
46
+
47
+ - name: ReserveStock
48
+ type: light
49
+ description: "Reserva stock para una orden"
50
+ input:
51
+ - name: orderId
52
+ type: String
53
+ - name: items
54
+ type: List<OrderItemDetail>
55
+ output:
56
+ - name: success
57
+ type: Boolean
58
+ - name: availableQuantity
59
+ type: Integer
60
+ externalTypes:
61
+ - name: OrderItemDetail
62
+ module: orders
63
+ timeout: 10s
64
+ compensation: ReleaseStock # Compensación explícita
65
+
66
+ - name: ReleaseStock
67
+ type: light
68
+ description: "Libera stock reservado (compensación)"
69
+ input:
70
+ - name: orderId
71
+ type: String
72
+ - name: items
73
+ type: List<OrderItemDetail>
74
+ externalTypes:
75
+ - name: OrderItemDetail
76
+ module: orders
77
+ timeout: 10s
78
+
79
+ - name: DeleteStock
80
+ type: light
81
+ description: "Elimina stock de un producto (compensación de InitializeStock)"
82
+ input:
83
+ - name: productId
84
+ type: String
85
+ timeout: 5s
86
+
87
+ # ─── ELIMINADOS: listeners[] ─────────────────────────────────────────────────
88
+ # ProductCreatedEvent → ya no se consume. InitializeStock es una Activity.
89
+
90
+ endpoints:
91
+ basePath: /inventory
92
+ versions:
93
+ - version: v1
94
+ operations:
95
+ - useCase: GetStock
96
+ method: GET
97
+ path: /{productId}
98
+ - useCase: FindAllStocks
99
+ method: GET
100
+ path: /
101
+ - useCase: AdjustStock
102
+ method: PUT
103
+ path: /{productId}/adjust
104
+ - useCase: ReserveStock
105
+ method: POST
106
+ path: /{productId}/reserve
107
+ - useCase: ReleaseStock
108
+ method: POST
109
+ path: /{productId}/release
@@ -0,0 +1,131 @@
1
+ # =============================================================================
2
+ # PROTOTYPE: notifications.yaml — versión Temporal (sin Kafka, sin Read Models)
3
+ # =============================================================================
4
+ # Cambios vs original:
5
+ # - ELIMINA listeners[] (4 listeners de eventos Kafka)
6
+ # - ELIMINA readModels[] (CustomerReadModel) — datos de cliente llegan como
7
+ # input de las Activities, inyectados por el workflow caller
8
+ # - AGREGA activities[] — cada notificación es una Activity
9
+ # - Activities reciben datos del cliente directamente (no lookup local)
10
+ # =============================================================================
11
+
12
+ aggregates:
13
+ - name: Notification
14
+ entities:
15
+ - name: notification
16
+ isRoot: true
17
+ tableName: notifications
18
+ audit:
19
+ enabled: true
20
+ fields:
21
+ - name: id
22
+ type: String
23
+ - name: recipientEmail
24
+ type: String
25
+ - name: recipientPhone
26
+ type: String
27
+ - name: type
28
+ type: NotificationType
29
+ - name: channel
30
+ type: NotificationChannel
31
+ - name: status
32
+ type: NotificationStatus
33
+ readOnly: true
34
+ - name: subject
35
+ type: String
36
+ - name: body
37
+ type: String
38
+ - name: referenceId
39
+ type: String
40
+ - name: sentAt
41
+ type: LocalDateTime
42
+ readOnly: true
43
+
44
+ enums:
45
+ - name: NotificationType
46
+ values:
47
+ - ORDER_PLACED
48
+ - ORDER_CANCELLED
49
+ - PAYMENT_APPROVED
50
+ - PAYMENT_FAILED
51
+
52
+ - name: NotificationChannel
53
+ values:
54
+ - EMAIL
55
+ - SMS
56
+
57
+ - name: NotificationStatus
58
+ initialValue: PENDING
59
+ transitions:
60
+ - from: PENDING
61
+ to: SENT
62
+ method: markAsSent
63
+ - from: PENDING
64
+ to: FAILED
65
+ method: markAsFailed
66
+ values: [PENDING, SENT, FAILED]
67
+
68
+ # ─── Activities que este módulo EXPONE ───────────────────────────────────────
69
+ # Cada activity recibe TODA la data necesaria como input — zero lookups locales.
70
+ # El workflow caller (PlaceOrderWorkflow, CancelOrderWorkflow) obtiene datos
71
+ # del cliente via GetCustomerById y los pasa aquí directamente.
72
+ activities:
73
+ - name: NotifyOrderPlaced
74
+ type: light
75
+ description: "Envía notificación de orden colocada al cliente"
76
+ input:
77
+ - name: orderId
78
+ type: String
79
+ - name: email
80
+ type: String
81
+ - name: firstName
82
+ type: String
83
+ - name: totalAmount
84
+ type: BigDecimal
85
+ timeout: 15s
86
+
87
+ - name: NotifyOrderCancelled
88
+ type: light
89
+ description: "Envía notificación de orden cancelada al cliente"
90
+ input:
91
+ - name: orderId
92
+ type: String
93
+ - name: email
94
+ type: String
95
+ - name: firstName
96
+ type: String
97
+ timeout: 15s
98
+
99
+ - name: NotifyPaymentApproved
100
+ type: light
101
+ description: "Envía notificación de pago aprobado al cliente"
102
+ input:
103
+ - name: orderId
104
+ type: String
105
+ - name: customerEmail
106
+ type: String
107
+ - name: customerName
108
+ type: String
109
+ - name: amount
110
+ type: BigDecimal
111
+ timeout: 15s
112
+
113
+ - name: NotifyPaymentFailed
114
+ type: light
115
+ description: "Envía notificación de pago fallido al cliente"
116
+ input:
117
+ - name: orderId
118
+ type: String
119
+ - name: customerEmail
120
+ type: String
121
+ - name: customerName
122
+ type: String
123
+ - name: reason
124
+ type: String
125
+ timeout: 15s
126
+
127
+ # ─── ELIMINADOS: listeners[], readModels[], SyncCustomerReadModel ────────────
128
+ # CustomerReadModel ELIMINADO — datos del cliente llegan como input directo.
129
+ # SyncCustomerReadModel ELIMINADO — ya no hay proyección local que mantener.
130
+ # Consecuencia: notifications es un módulo stateless para datos cross-module.
131
+ # Solo persiste las notificaciones enviadas (entidad Notification).