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.
- package/AGENTS.md +220 -5
- package/DOMAIN_YAML_GUIDE.md +188 -3
- package/FUTURE_FEATURES.md +33 -52
- 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 +290 -10
- 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-events.yaml +26 -0
- package/examples/domain-read-models.yaml +113 -0
- 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/read-model-spec.md +664 -0
- 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 +34 -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 +560 -24
- package/src/commands/generate-http-exchange.js +3 -0
- package/src/commands/generate-kafka-event.js +3 -0
- package/src/commands/generate-kafka-listener.js +3 -0
- package/src/commands/generate-rabbitmq-event.js +665 -0
- package/src/commands/generate-rabbitmq-listener.js +205 -0
- package/src/commands/generate-record.js +2 -2
- package/src/commands/generate-resource.js +4 -1
- package/src/commands/generate-temporal-activity.js +970 -33
- package/src/commands/generate-temporal-flow.js +98 -38
- package/src/commands/generate-temporal-system.js +708 -0
- package/src/commands/generate-usecase.js +4 -1
- package/src/skills/build-system-yaml/SKILL.md +343 -2
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +253 -26
- package/src/skills/build-system-yaml/references/module-spec.md +90 -9
- package/src/skills/build-system-yaml/references/system-yaml-spec.md +36 -0
- 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/config-manager.js +4 -2
- package/src/utils/domain-validator.js +495 -17
- package/src/utils/naming.js +20 -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/validator.js +3 -1
- package/src/utils/yaml-to-entity.js +281 -9
- package/templates/aggregate/AggregateRepository.java.ejs +4 -0
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +8 -0
- package/templates/aggregate/AggregateRoot.java.ejs +38 -4
- package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
- package/templates/aggregate/JpaAggregateRoot.java.ejs +4 -4
- 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/DeleteCommandHandler.java.ejs +19 -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/crud/UpdateCommandHandler.java.ejs +53 -2
- 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/ReadModelDomain.java.ejs +46 -0
- package/templates/read-model/ReadModelJpa.java.ejs +58 -0
- package/templates/read-model/ReadModelJpaRepository.java.ejs +13 -0
- package/templates/read-model/ReadModelKafkaListener.java.ejs +64 -0
- package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
- package/templates/read-model/ReadModelRepository.java.ejs +42 -0
- package/templates/read-model/ReadModelRepositoryImpl.java.ejs +85 -0
- package/templates/read-model/ReadModelSyncHandler.java.ejs +54 -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
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# PROTOTYPE: system.yaml con Temporal como único mecanismo de comunicación
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Este es el formato target para generación de código con Temporal.
|
|
5
|
+
# eva4j leerá este archivo para generar workflows cross-module
|
|
6
|
+
# y contratos compartidos entre módulos.
|
|
7
|
+
# =============================================================================
|
|
8
|
+
|
|
9
|
+
system:
|
|
10
|
+
name: test-eva
|
|
11
|
+
groupId: com.example
|
|
12
|
+
javaVersion: 21
|
|
13
|
+
springBootVersion: 3.5.5
|
|
14
|
+
database: postgresql
|
|
15
|
+
|
|
16
|
+
# ─── Temporal reemplaza tanto messaging (Kafka) como sync (Feign) ────────────
|
|
17
|
+
orchestration:
|
|
18
|
+
enabled: true
|
|
19
|
+
engine: temporal
|
|
20
|
+
temporal:
|
|
21
|
+
target: localhost:7233
|
|
22
|
+
namespace: test-eva
|
|
23
|
+
# Task queues con prefijo de módulo (module-scoped)
|
|
24
|
+
# Cada módulo tiene su propio set de queues:
|
|
25
|
+
# {MODULE}_WORKFLOW_QUEUE — Workflows del módulo
|
|
26
|
+
# {MODULE}_LIGHT_TASK_QUEUE — Activities rápidas (< 30s)
|
|
27
|
+
# {MODULE}_HEAVY_TASK_QUEUE — Activities pesadas (hasta 2min)
|
|
28
|
+
|
|
29
|
+
modules:
|
|
30
|
+
- name: products
|
|
31
|
+
description: "Product catalog management for household goods"
|
|
32
|
+
exposes:
|
|
33
|
+
- method: GET
|
|
34
|
+
path: /products/{id}
|
|
35
|
+
useCase: GetProduct
|
|
36
|
+
- method: GET
|
|
37
|
+
path: /products
|
|
38
|
+
useCase: FindAllProducts
|
|
39
|
+
- method: POST
|
|
40
|
+
path: /products
|
|
41
|
+
useCase: CreateProduct
|
|
42
|
+
- method: PUT
|
|
43
|
+
path: /products/{id}
|
|
44
|
+
useCase: UpdateProduct
|
|
45
|
+
- method: DELETE
|
|
46
|
+
path: /products/{id}
|
|
47
|
+
useCase: DeleteProduct
|
|
48
|
+
|
|
49
|
+
- name: customers
|
|
50
|
+
description: "Customer registration and profile management"
|
|
51
|
+
exposes:
|
|
52
|
+
- method: GET
|
|
53
|
+
path: /customers/{id}
|
|
54
|
+
useCase: GetCustomer
|
|
55
|
+
- method: GET
|
|
56
|
+
path: /customers
|
|
57
|
+
useCase: FindAllCustomers
|
|
58
|
+
- method: POST
|
|
59
|
+
path: /customers
|
|
60
|
+
useCase: CreateCustomer
|
|
61
|
+
- method: PUT
|
|
62
|
+
path: /customers/{id}
|
|
63
|
+
useCase: UpdateCustomer
|
|
64
|
+
|
|
65
|
+
- name: orders
|
|
66
|
+
description: "Order lifecycle management from placement to completion"
|
|
67
|
+
exposes:
|
|
68
|
+
- method: POST
|
|
69
|
+
path: /orders
|
|
70
|
+
useCase: CreateOrder
|
|
71
|
+
- method: GET
|
|
72
|
+
path: /orders/{id}
|
|
73
|
+
useCase: GetOrder
|
|
74
|
+
- method: GET
|
|
75
|
+
path: /orders
|
|
76
|
+
useCase: FindAllOrders
|
|
77
|
+
- method: PUT
|
|
78
|
+
path: /orders/{id}/cancel
|
|
79
|
+
useCase: CancelOrder
|
|
80
|
+
|
|
81
|
+
- name: payments
|
|
82
|
+
description: "Payment processing and external payment gateway integration"
|
|
83
|
+
exposes:
|
|
84
|
+
- method: POST
|
|
85
|
+
path: /payments
|
|
86
|
+
useCase: CreatePayment
|
|
87
|
+
- method: GET
|
|
88
|
+
path: /payments/{id}
|
|
89
|
+
useCase: GetPayment
|
|
90
|
+
- method: GET
|
|
91
|
+
path: /payments
|
|
92
|
+
useCase: FindAllPayments
|
|
93
|
+
- method: POST
|
|
94
|
+
path: /payments/{id}/refund
|
|
95
|
+
useCase: RefundPayment
|
|
96
|
+
|
|
97
|
+
- name: inventory
|
|
98
|
+
description: "Stock level management, reservation, and replenishment"
|
|
99
|
+
exposes:
|
|
100
|
+
- method: GET
|
|
101
|
+
path: /inventory/{productId}
|
|
102
|
+
useCase: GetStock
|
|
103
|
+
- method: GET
|
|
104
|
+
path: /inventory
|
|
105
|
+
useCase: FindAllStocks
|
|
106
|
+
- method: PUT
|
|
107
|
+
path: /inventory/{productId}/adjust
|
|
108
|
+
useCase: AdjustStock
|
|
109
|
+
- method: POST
|
|
110
|
+
path: /inventory/{productId}/reserve
|
|
111
|
+
useCase: ReserveStock
|
|
112
|
+
- method: POST
|
|
113
|
+
path: /inventory/{productId}/release
|
|
114
|
+
useCase: ReleaseStock
|
|
115
|
+
|
|
116
|
+
- name: notifications
|
|
117
|
+
description: "Notification delivery service for email and SMS alerts"
|
|
118
|
+
|
|
119
|
+
# =============================================================================
|
|
120
|
+
# WORKFLOWS: Flujos de negocio cross-module orquestados por Temporal
|
|
121
|
+
# =============================================================================
|
|
122
|
+
# Solo flujos de negocio REALES — no hay workflows de sincronización de datos.
|
|
123
|
+
# Datos cross-module se obtienen on-demand con Remote Activities de lectura.
|
|
124
|
+
# Patrones usados (ver TEMPORAL_COMMUNICATION_PATTERNS.md):
|
|
125
|
+
# - Remote Activity: operación atómica cross-service
|
|
126
|
+
# - Remote Activity + Async.function(): operaciones independientes en paralelo
|
|
127
|
+
# =============================================================================
|
|
128
|
+
|
|
129
|
+
workflows:
|
|
130
|
+
|
|
131
|
+
# ─── Producto creado: inicializar stock en inventory ───────────────────────
|
|
132
|
+
# Único consumer real de ProductCreatedEvent.
|
|
133
|
+
# Sin read models, ya no hay steps de sincronización.
|
|
134
|
+
- name: ProductCreatedWorkflow
|
|
135
|
+
trigger:
|
|
136
|
+
module: products
|
|
137
|
+
on: create
|
|
138
|
+
taskQueue: PRODUCT_WORKFLOW_QUEUE
|
|
139
|
+
steps:
|
|
140
|
+
- activity: InitializeStock # Remote Activity → inventory
|
|
141
|
+
target: inventory
|
|
142
|
+
type: sync
|
|
143
|
+
input:
|
|
144
|
+
- productId
|
|
145
|
+
- name
|
|
146
|
+
compensation: DeleteStock
|
|
147
|
+
timeout: 10s
|
|
148
|
+
|
|
149
|
+
# ─── Orden creada: SAGA completa ───────────────────────────────────────────
|
|
150
|
+
# El caso estrella de Temporal — saga con compensación durable.
|
|
151
|
+
# Sin read models: customer y product data se obtienen on-demand
|
|
152
|
+
# mediante Remote Activities de lectura.
|
|
153
|
+
- name: PlaceOrderWorkflow
|
|
154
|
+
trigger:
|
|
155
|
+
module: orders
|
|
156
|
+
on: create
|
|
157
|
+
taskQueue: ORDER_WORKFLOW_QUEUE
|
|
158
|
+
saga: true
|
|
159
|
+
steps:
|
|
160
|
+
# Paso 1: Obtener detalles de la orden (Local Activity → orders)
|
|
161
|
+
# Lee el agregado completo: items, customerId, totalAmount.
|
|
162
|
+
# Permite que el workflow input sea solo orderId.
|
|
163
|
+
- activity: GetOrderDetails
|
|
164
|
+
target: orders
|
|
165
|
+
type: sync
|
|
166
|
+
input: [orderId]
|
|
167
|
+
output: [customerId, items, totalAmount]
|
|
168
|
+
timeout: 5s
|
|
169
|
+
|
|
170
|
+
# Paso 2: Obtener datos del cliente (Remote Activity → customers)
|
|
171
|
+
# Valida existencia y obtiene email/nombre para notificaciones.
|
|
172
|
+
- activity: GetCustomerById
|
|
173
|
+
target: customers
|
|
174
|
+
type: sync
|
|
175
|
+
input: [customerId]
|
|
176
|
+
output: [customerId, firstName, lastName, email, phone]
|
|
177
|
+
timeout: 5s
|
|
178
|
+
|
|
179
|
+
# Paso 3: Reservar stock (Remote Activity → inventory)
|
|
180
|
+
- activity: ReserveStock
|
|
181
|
+
target: inventory
|
|
182
|
+
type: sync
|
|
183
|
+
input: [orderId, items]
|
|
184
|
+
compensation: ReleaseStock
|
|
185
|
+
timeout: 10s
|
|
186
|
+
|
|
187
|
+
# Paso 4: Procesar pago (Remote Activity → payments)
|
|
188
|
+
- activity: ProcessOrderPayment
|
|
189
|
+
target: payments
|
|
190
|
+
type: sync
|
|
191
|
+
input: [orderId, customerId, totalAmount]
|
|
192
|
+
output: [paymentId]
|
|
193
|
+
compensation: RefundPayment
|
|
194
|
+
timeout: 30s
|
|
195
|
+
|
|
196
|
+
# Paso 5: Confirmar orden (Local Activity → orders)
|
|
197
|
+
- activity: ConfirmOrder
|
|
198
|
+
target: orders
|
|
199
|
+
type: sync
|
|
200
|
+
input: [orderId]
|
|
201
|
+
|
|
202
|
+
# Paso 6: Notificar (Remote Activity → notifications)
|
|
203
|
+
# ⚡ Async.function() — fire-and-forget, NO bloquea la saga.
|
|
204
|
+
- activity: NotifyOrderPlaced
|
|
205
|
+
target: notifications
|
|
206
|
+
type: async
|
|
207
|
+
input: [orderId, email, firstName, totalAmount]
|
|
208
|
+
|
|
209
|
+
# ─── Orden cancelada ──────────────────────────────────────────────────────
|
|
210
|
+
- name: CancelOrderWorkflow
|
|
211
|
+
trigger:
|
|
212
|
+
module: orders
|
|
213
|
+
on: cancel
|
|
214
|
+
taskQueue: ORDER_WORKFLOW_QUEUE
|
|
215
|
+
steps:
|
|
216
|
+
# Paso 1: Obtener detalles de la orden (Local Activity → orders)
|
|
217
|
+
- activity: GetOrderDetails
|
|
218
|
+
target: orders
|
|
219
|
+
type: sync
|
|
220
|
+
input: [orderId]
|
|
221
|
+
output: [customerId, items, paymentId]
|
|
222
|
+
timeout: 5s
|
|
223
|
+
|
|
224
|
+
# Paso 2: Obtener datos del cliente (para email de notificación)
|
|
225
|
+
- activity: GetCustomerById
|
|
226
|
+
target: customers
|
|
227
|
+
type: sync
|
|
228
|
+
input: [customerId]
|
|
229
|
+
output: [customerId, firstName, lastName, email, phone]
|
|
230
|
+
timeout: 5s
|
|
231
|
+
|
|
232
|
+
# Paso 3: Liberar stock reservado
|
|
233
|
+
- activity: ReleaseStock
|
|
234
|
+
target: inventory
|
|
235
|
+
type: sync
|
|
236
|
+
input: [orderId, items]
|
|
237
|
+
timeout: 10s
|
|
238
|
+
|
|
239
|
+
# Paso 4: Reembolsar pago (si hubo pago previo)
|
|
240
|
+
- activity: RefundPayment
|
|
241
|
+
target: payments
|
|
242
|
+
type: sync
|
|
243
|
+
input: [paymentId]
|
|
244
|
+
timeout: 30s
|
|
245
|
+
optional: true # No falla si no hubo pago
|
|
246
|
+
|
|
247
|
+
# Paso 5: Notificar cancelación (async, non-blocking)
|
|
248
|
+
- activity: NotifyOrderCancelled
|
|
249
|
+
target: notifications
|
|
250
|
+
type: async
|
|
251
|
+
input: [orderId, email, firstName]
|
|
252
|
+
|
|
253
|
+
# ─── ELIMINADOS: Workflows de sincronización de datos ──────────────────────
|
|
254
|
+
# Antes había 4 workflows que SOLO sincronizaban read models:
|
|
255
|
+
# - ProductUpdatedWorkflow → SyncProductReadModel en orders
|
|
256
|
+
# - ProductDeactivatedWorkflow → SoftDeleteProductReadModel en orders
|
|
257
|
+
# - CustomerCreatedWorkflow → SyncCustomerReadModel en orders + notifications
|
|
258
|
+
# - CustomerUpdatedWorkflow → SyncCustomerReadModel en orders + notifications
|
|
259
|
+
#
|
|
260
|
+
# ELIMINADOS porque:
|
|
261
|
+
# 1. Read models eliminados — datos se obtienen on-demand via Remote Activity
|
|
262
|
+
# 2. GetCustomerById reemplaza las proyecciones locales
|
|
263
|
+
# 3. Menos acoplamiento productor→consumidor (customers/products ya no saben
|
|
264
|
+
# quién los consume)
|
|
265
|
+
|
|
266
|
+
# ─── ABSORBIDOS por PlaceOrderWorkflow (saga) ─────────────────────────────
|
|
267
|
+
# PaymentApprovedWorkflow / PaymentFailedWorkflow → la saga integra el
|
|
268
|
+
# resultado de ProcessOrderPayment directamente. Si falla → compensa.
|
|
269
|
+
# Si aprueba → ConfirmOrder.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# EJEMPLO: ENDPOINTS — MÓDULO CON MÚLTIPLES AGREGADOS
|
|
2
|
+
# Módulo de productos con dos agregados: Product y Category.
|
|
3
|
+
#
|
|
4
|
+
# REGLA CLAVE:
|
|
5
|
+
# Cuando un módulo tiene 2+ agregados, NO se puede usar un solo
|
|
6
|
+
# basePath (ej: /products) porque cada agregado tiene su propio
|
|
7
|
+
# recurso REST. En este caso:
|
|
8
|
+
#
|
|
9
|
+
# - Usar basePath: "" (string vacío — NUNCA basePath: / con slash)
|
|
10
|
+
# - Declarar paths ABSOLUTOS en cada operación
|
|
11
|
+
# - El controlador generado tendrá @RequestMapping("/api/v1") (limpio)
|
|
12
|
+
#
|
|
13
|
+
# CONTRASTE CON MÓDULO DE UN SOLO AGREGADO:
|
|
14
|
+
# basePath: /orders → paths relativos: /, /{id}, /{id}/confirm
|
|
15
|
+
# basePath: "" → paths absolutos: /products, /categories/{id}
|
|
16
|
+
|
|
17
|
+
aggregates:
|
|
18
|
+
# ── Primer agregado: Product ──────────────────────────────────────────────
|
|
19
|
+
- name: Product
|
|
20
|
+
entities:
|
|
21
|
+
- name: product
|
|
22
|
+
isRoot: true
|
|
23
|
+
tableName: products
|
|
24
|
+
hasSoftDelete: true
|
|
25
|
+
audit:
|
|
26
|
+
enabled: true
|
|
27
|
+
trackUser: true
|
|
28
|
+
fields:
|
|
29
|
+
- name: id
|
|
30
|
+
type: String
|
|
31
|
+
- name: name
|
|
32
|
+
type: String
|
|
33
|
+
validations:
|
|
34
|
+
- type: NotBlank
|
|
35
|
+
message: "Product name is required"
|
|
36
|
+
- name: description
|
|
37
|
+
type: String
|
|
38
|
+
- name: price
|
|
39
|
+
type: BigDecimal
|
|
40
|
+
validations:
|
|
41
|
+
- type: Positive
|
|
42
|
+
- name: categoryId
|
|
43
|
+
type: String
|
|
44
|
+
reference:
|
|
45
|
+
aggregate: Category
|
|
46
|
+
- name: status
|
|
47
|
+
type: ProductStatus
|
|
48
|
+
readOnly: true
|
|
49
|
+
|
|
50
|
+
enums:
|
|
51
|
+
- name: ProductStatus
|
|
52
|
+
initialValue: DRAFT
|
|
53
|
+
transitions:
|
|
54
|
+
- from: DRAFT
|
|
55
|
+
to: PUBLISHED
|
|
56
|
+
method: publish
|
|
57
|
+
- from: PUBLISHED
|
|
58
|
+
to: ARCHIVED
|
|
59
|
+
method: archive
|
|
60
|
+
values: [DRAFT, PUBLISHED, ARCHIVED]
|
|
61
|
+
|
|
62
|
+
events:
|
|
63
|
+
- name: ProductCreatedEvent
|
|
64
|
+
lifecycle: create
|
|
65
|
+
fields:
|
|
66
|
+
- name: productId
|
|
67
|
+
type: String
|
|
68
|
+
- name: name
|
|
69
|
+
type: String
|
|
70
|
+
- name: price
|
|
71
|
+
type: BigDecimal
|
|
72
|
+
- name: ProductPublishedEvent
|
|
73
|
+
triggers:
|
|
74
|
+
- publish
|
|
75
|
+
fields:
|
|
76
|
+
- name: productId
|
|
77
|
+
type: String
|
|
78
|
+
- name: publishedAt
|
|
79
|
+
type: LocalDateTime
|
|
80
|
+
|
|
81
|
+
# ── Segundo agregado: Category ────────────────────────────────────────────
|
|
82
|
+
- name: Category
|
|
83
|
+
entities:
|
|
84
|
+
- name: category
|
|
85
|
+
isRoot: true
|
|
86
|
+
tableName: categories
|
|
87
|
+
audit:
|
|
88
|
+
enabled: true
|
|
89
|
+
fields:
|
|
90
|
+
- name: id
|
|
91
|
+
type: String
|
|
92
|
+
- name: name
|
|
93
|
+
type: String
|
|
94
|
+
validations:
|
|
95
|
+
- type: NotBlank
|
|
96
|
+
message: "Category name is required"
|
|
97
|
+
- name: description
|
|
98
|
+
type: String
|
|
99
|
+
|
|
100
|
+
# ── Endpoints: basePath vacío + paths absolutos ─────────────────────────────
|
|
101
|
+
# NOTA: basePath: "" (vacío) porque hay 2 agregados.
|
|
102
|
+
# Cada path es absoluto: /products, /categories, etc.
|
|
103
|
+
endpoints:
|
|
104
|
+
basePath: ""
|
|
105
|
+
versions:
|
|
106
|
+
- version: v1
|
|
107
|
+
operations:
|
|
108
|
+
# ── Product operations ──
|
|
109
|
+
- useCase: CreateProduct
|
|
110
|
+
method: POST
|
|
111
|
+
path: /products
|
|
112
|
+
- useCase: UpdateProduct
|
|
113
|
+
method: PUT
|
|
114
|
+
path: /products/{id}
|
|
115
|
+
- useCase: GetProduct
|
|
116
|
+
method: GET
|
|
117
|
+
path: /products/{id}
|
|
118
|
+
- useCase: FindAllProducts
|
|
119
|
+
method: GET
|
|
120
|
+
path: /products
|
|
121
|
+
- useCase: PublishProduct
|
|
122
|
+
method: PUT
|
|
123
|
+
path: /products/{id}/publish
|
|
124
|
+
# ── Category operations ──
|
|
125
|
+
- useCase: CreateCategory
|
|
126
|
+
method: POST
|
|
127
|
+
path: /categories
|
|
128
|
+
- useCase: UpdateCategory
|
|
129
|
+
method: PUT
|
|
130
|
+
path: /categories/{id}
|
|
131
|
+
- useCase: GetCategory
|
|
132
|
+
method: GET
|
|
133
|
+
path: /categories/{id}
|
|
134
|
+
- useCase: FindAllCategories
|
|
135
|
+
method: GET
|
|
136
|
+
path: /categories
|
|
137
|
+
# ── Cross-aggregate query ──
|
|
138
|
+
- useCase: FindProductsByCategory
|
|
139
|
+
method: GET
|
|
140
|
+
path: /categories/{id}/products
|
|
@@ -305,6 +305,32 @@ aggregates:
|
|
|
305
305
|
- name: estimatedDeliveryDate # no resuelto → null /* TODO */
|
|
306
306
|
type: LocalDate
|
|
307
307
|
|
|
308
|
+
# ─── Eventos con lifecycle: (alterntiva a triggers:) ──────────────
|
|
309
|
+
# Cuando el evento se produce en una operación CRUD y no en una transición de estado,
|
|
310
|
+
# se usa lifecycle: en lugar de triggers:
|
|
311
|
+
# El generador emite raise() automáticamente en el punto CRUD correspondiente.
|
|
312
|
+
|
|
313
|
+
- name: OrderCreatedEvent
|
|
314
|
+
lifecycle: create # raise() en el constructor de creación
|
|
315
|
+
fields: # UUID auto-generado como id antes de raise()
|
|
316
|
+
- name: orderId
|
|
317
|
+
type: String
|
|
318
|
+
- name: customerName # → this.getCustomerName() si existe en la entidad
|
|
319
|
+
type: String
|
|
320
|
+
- name: createdAt
|
|
321
|
+
type: LocalDateTime # → LocalDateTime.now()
|
|
322
|
+
|
|
323
|
+
- name: OrderUpdatedEvent
|
|
324
|
+
lifecycle: update # raise() en el método update() de la entidad raíz
|
|
325
|
+
fields:
|
|
326
|
+
- name: orderId
|
|
327
|
+
type: String
|
|
328
|
+
- name: customerName
|
|
329
|
+
type: String
|
|
330
|
+
|
|
331
|
+
# lifecycle: delete requiere hasSoftDelete: false en la entidad raíz
|
|
332
|
+
# lifecycle: softDelete requiere hasSoftDelete: true en la entidad raíz
|
|
333
|
+
|
|
308
334
|
# ─── Eventos externos que este módulo CONSUME ────────────────────────────────
|
|
309
335
|
# Nivel raíz, sibling de aggregates:
|
|
310
336
|
# Requiere broker instalado (eva add kafka-client) para generación.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Example: Read Models (Local Projections)
|
|
2
|
+
#
|
|
3
|
+
# This example shows an "orders" module that uses readModels to maintain
|
|
4
|
+
# local projections of data from "products" and "customers" modules,
|
|
5
|
+
# eliminating synchronous HTTP dependencies.
|
|
6
|
+
#
|
|
7
|
+
# ─── SOURCE MODULE NOTE ──────────────────────────────────────────────────────
|
|
8
|
+
# The source module (e.g. products/domain.yaml) must declare the events
|
|
9
|
+
# that this readModel's syncedBy entries reference. Use lifecycle: to
|
|
10
|
+
# automatically emit raise() at CRUD operations:
|
|
11
|
+
#
|
|
12
|
+
# events:
|
|
13
|
+
# - name: ProductCreatedEvent
|
|
14
|
+
# lifecycle: create # raise() in creation constructor
|
|
15
|
+
# fields:
|
|
16
|
+
# - name: productId
|
|
17
|
+
# type: String
|
|
18
|
+
# - name: name
|
|
19
|
+
# type: String
|
|
20
|
+
# - name: price
|
|
21
|
+
# type: BigDecimal
|
|
22
|
+
# - name: status
|
|
23
|
+
# type: String
|
|
24
|
+
#
|
|
25
|
+
# - name: ProductUpdatedEvent
|
|
26
|
+
# lifecycle: update # raise() in UpdateCommandHandler
|
|
27
|
+
# fields: [...]
|
|
28
|
+
#
|
|
29
|
+
# - name: ProductDeactivatedEvent
|
|
30
|
+
# lifecycle: softDelete # raise() in softDelete() method
|
|
31
|
+
# fields: [...]
|
|
32
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
aggregates:
|
|
35
|
+
- name: Order
|
|
36
|
+
entities:
|
|
37
|
+
- name: Order
|
|
38
|
+
isRoot: true
|
|
39
|
+
tableName: orders
|
|
40
|
+
audit:
|
|
41
|
+
enabled: true
|
|
42
|
+
trackUser: true
|
|
43
|
+
fields:
|
|
44
|
+
- name: id
|
|
45
|
+
type: String
|
|
46
|
+
- name: orderNumber
|
|
47
|
+
type: String
|
|
48
|
+
- name: customerId
|
|
49
|
+
type: String
|
|
50
|
+
reference:
|
|
51
|
+
aggregate: Customer
|
|
52
|
+
module: customers
|
|
53
|
+
- name: totalAmount
|
|
54
|
+
type: BigDecimal
|
|
55
|
+
readOnly: true
|
|
56
|
+
defaultValue: "0.00"
|
|
57
|
+
enums:
|
|
58
|
+
- name: OrderStatus
|
|
59
|
+
initialValue: DRAFT
|
|
60
|
+
values: [DRAFT, PLACED, CONFIRMED, CANCELLED]
|
|
61
|
+
transitions:
|
|
62
|
+
- from: DRAFT
|
|
63
|
+
to: PLACED
|
|
64
|
+
method: place
|
|
65
|
+
- from: PLACED
|
|
66
|
+
to: CONFIRMED
|
|
67
|
+
method: confirm
|
|
68
|
+
- from: [DRAFT, PLACED]
|
|
69
|
+
to: CANCELLED
|
|
70
|
+
method: cancel
|
|
71
|
+
|
|
72
|
+
readModels:
|
|
73
|
+
- name: ProductReadModel
|
|
74
|
+
source:
|
|
75
|
+
module: products
|
|
76
|
+
aggregate: Product
|
|
77
|
+
tableName: rm_orders_products
|
|
78
|
+
fields:
|
|
79
|
+
- name: id
|
|
80
|
+
type: String
|
|
81
|
+
- name: name
|
|
82
|
+
type: String
|
|
83
|
+
- name: price
|
|
84
|
+
type: BigDecimal
|
|
85
|
+
- name: status
|
|
86
|
+
type: String
|
|
87
|
+
syncedBy:
|
|
88
|
+
- event: ProductCreatedEvent
|
|
89
|
+
action: UPSERT
|
|
90
|
+
- event: ProductUpdatedEvent
|
|
91
|
+
action: UPSERT
|
|
92
|
+
- event: ProductDeactivatedEvent
|
|
93
|
+
action: SOFT_DELETE
|
|
94
|
+
|
|
95
|
+
- name: CustomerReadModel
|
|
96
|
+
source:
|
|
97
|
+
module: customers
|
|
98
|
+
aggregate: Customer
|
|
99
|
+
tableName: rm_orders_customers
|
|
100
|
+
fields:
|
|
101
|
+
- name: id
|
|
102
|
+
type: String
|
|
103
|
+
- name: fullName
|
|
104
|
+
type: String
|
|
105
|
+
- name: email
|
|
106
|
+
type: String
|
|
107
|
+
syncedBy:
|
|
108
|
+
- event: CustomerRegisteredEvent
|
|
109
|
+
action: UPSERT
|
|
110
|
+
- event: CustomerUpdatedEvent
|
|
111
|
+
action: UPSERT
|
|
112
|
+
- event: CustomerDeletedEvent
|
|
113
|
+
action: DELETE
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
# customer — Domain Model
|
|
3
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
# Gestión de clientes. Emite eventos de ciclo de vida (create, update, delete)
|
|
5
|
+
# para que otros módulos mantengan proyecciones locales via readModels.
|
|
6
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
aggregates:
|
|
9
|
+
- name: Customer
|
|
10
|
+
entities:
|
|
11
|
+
- name: Customer
|
|
12
|
+
isRoot: true
|
|
13
|
+
tableName: customers
|
|
14
|
+
audit:
|
|
15
|
+
enabled: true
|
|
16
|
+
trackUser: true
|
|
17
|
+
fields:
|
|
18
|
+
- name: id
|
|
19
|
+
type: String
|
|
20
|
+
- name: fullName
|
|
21
|
+
type: String
|
|
22
|
+
validations:
|
|
23
|
+
- type: NotBlank
|
|
24
|
+
message: "Full name is required"
|
|
25
|
+
- name: email
|
|
26
|
+
type: String
|
|
27
|
+
validations:
|
|
28
|
+
- type: NotBlank
|
|
29
|
+
message: "Email is required"
|
|
30
|
+
- type: Email
|
|
31
|
+
message: "Invalid email format"
|
|
32
|
+
- name: phone
|
|
33
|
+
type: String
|
|
34
|
+
|
|
35
|
+
events:
|
|
36
|
+
# CustomerCreatedEvent — lifecycle: create → raise() in creation constructor
|
|
37
|
+
- name: CustomerCreatedEvent
|
|
38
|
+
lifecycle: create
|
|
39
|
+
fields:
|
|
40
|
+
- name: customerId
|
|
41
|
+
type: String
|
|
42
|
+
- name: fullName
|
|
43
|
+
type: String
|
|
44
|
+
- name: email
|
|
45
|
+
type: String
|
|
46
|
+
|
|
47
|
+
# CustomerUpdatedEvent — lifecycle: update → raise() in update() method
|
|
48
|
+
- name: CustomerUpdatedEvent
|
|
49
|
+
lifecycle: update
|
|
50
|
+
fields:
|
|
51
|
+
- name: customerId
|
|
52
|
+
type: String
|
|
53
|
+
- name: fullName
|
|
54
|
+
type: String
|
|
55
|
+
- name: email
|
|
56
|
+
type: String
|
|
57
|
+
|
|
58
|
+
# CustomerDeletedEvent — lifecycle: delete → raise() in DeleteCommandHandler
|
|
59
|
+
- name: CustomerDeletedEvent
|
|
60
|
+
lifecycle: delete
|
|
61
|
+
fields:
|
|
62
|
+
- name: customerId
|
|
63
|
+
type: String
|
|
64
|
+
- name: deletedAt
|
|
65
|
+
type: LocalDateTime
|
|
66
|
+
|
|
67
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
68
|
+
# Declarative REST endpoints
|
|
69
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
70
|
+
endpoints:
|
|
71
|
+
basePath: /customers
|
|
72
|
+
versions:
|
|
73
|
+
- version: v1
|
|
74
|
+
operations:
|
|
75
|
+
- useCase: CreateCustomer
|
|
76
|
+
method: POST
|
|
77
|
+
path: /
|
|
78
|
+
- useCase: GetCustomer
|
|
79
|
+
method: GET
|
|
80
|
+
path: /{id}
|
|
81
|
+
- useCase: FindAllCustomers
|
|
82
|
+
method: GET
|
|
83
|
+
path: /
|
|
84
|
+
- useCase: UpdateCustomer
|
|
85
|
+
method: PUT
|
|
86
|
+
path: /{id}
|
|
87
|
+
- useCase: DeleteCustomer
|
|
88
|
+
method: DELETE
|
|
89
|
+
path: /{id}
|