eva4j 1.0.17 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/DOMAIN_YAML_GUIDE.md +3 -1
- package/QUICK_REFERENCE.md +8 -4
- package/bin/eva4j.js +70 -2
- package/config/defaults.json +1 -0
- package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
- package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
- package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
- package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
- package/docs/commands/EVALUATE_SYSTEM.md +272 -8
- package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
- package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
- package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
- package/docs/commands/INDEX.md +27 -3
- package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
- package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
- package/docs/prototype/system/RISKS.md +277 -0
- package/docs/prototype/system/customers.yaml +133 -0
- package/docs/prototype/system/inventory.yaml +109 -0
- package/docs/prototype/system/notifications.yaml +131 -0
- package/docs/prototype/system/orders.yaml +241 -0
- package/docs/prototype/system/payments.yaml +256 -0
- package/docs/prototype/system/products.yaml +168 -0
- package/docs/prototype/system/system.yaml +269 -0
- package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
- package/examples/domain-read-models.yaml +2 -2
- package/examples/system/customer.yaml +89 -0
- package/examples/system/orders.yaml +119 -0
- package/examples/system/product.yaml +27 -0
- package/examples/system/system.yaml +80 -0
- package/package.json +1 -1
- package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
- package/src/agents/design-gap-analyst.agent.md +383 -0
- package/src/agents/design-reviewer-temporal.agent.md +412 -0
- package/src/agents/design-reviewer.agent.md +31 -5
- package/src/agents/implement-use-cases.prompt.md +179 -0
- package/src/agents/ux-gap-analyst.agent.md +412 -0
- package/src/commands/add-rabbitmq-client.js +261 -0
- package/src/commands/add-temporal-client.js +22 -2
- package/src/commands/build.js +267 -11
- package/src/commands/evaluate-system.js +700 -13
- package/src/commands/generate-entities.js +308 -16
- package/src/commands/generate-rabbitmq-event.js +665 -0
- package/src/commands/generate-rabbitmq-listener.js +205 -0
- package/src/commands/generate-temporal-activity.js +968 -34
- package/src/commands/generate-temporal-flow.js +95 -38
- package/src/commands/generate-temporal-system.js +708 -0
- package/src/skills/build-system-yaml/SKILL.md +222 -2
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +50 -4
- package/src/skills/build-system-yaml/references/module-spec.md +57 -8
- package/src/skills/build-temporal-system/SKILL.md +752 -0
- package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
- package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
- package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
- package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
- package/src/skills/implement-use-case/SKILL.md +350 -0
- package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
- package/src/skills/requirements-elicitation/SKILL.md +228 -0
- package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
- package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
- package/src/utils/bounded-context-diagram.js +844 -0
- package/src/utils/domain-validator.js +266 -4
- package/src/utils/naming.js +10 -0
- package/src/utils/system-validator.js +169 -11
- package/src/utils/system-yaml-parser.js +318 -0
- package/src/utils/temporal-validator.js +497 -0
- package/src/utils/yaml-to-entity.js +10 -7
- package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
- package/templates/aggregate/JpaAggregateRoot.java.ejs +2 -2
- package/templates/aggregate/JpaEntity.java.ejs +2 -2
- package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
- package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
- package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
- package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
- package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
- package/templates/base/root/AGENTS.md.ejs +1 -1
- package/templates/crud/EndpointsController.java.ejs +1 -1
- package/templates/crud/ScaffoldCommand.java.ejs +5 -2
- package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
- package/templates/crud/ScaffoldQuery.java.ejs +5 -2
- package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
- package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
- package/templates/evaluate/report.html.ejs +1447 -90
- package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
- package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
- package/templates/ports/PortAclMapper.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +7 -22
- package/templates/ports/PortFeignClient.java.ejs +4 -0
- package/templates/ports/PortResponseDto.java.ejs +1 -1
- package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
- package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
- package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
- package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
- package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
- package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
- package/templates/read-model/ReadModelJpa.java.ejs +1 -1
- package/templates/read-model/ReadModelJpaRepository.java.ejs +2 -0
- package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
- package/templates/read-model/ReadModelRepositoryImpl.java.ejs +9 -5
- package/templates/read-model/ReadModelSyncHandler.java.ejs +2 -0
- package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
- package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
- package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
- package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
- package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
- package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
- package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/NestedType.java.ejs +12 -0
- package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
- package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
- package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
- package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
- package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
- package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
- package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
- package/COMMAND_EVALUATION.md +0 -911
- package/test-c2010.js +0 -49
- package/test-update-compat.js +0 -109
- package/test-update-lifecycle.js +0 -121
|
@@ -0,0 +1,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
|
|
@@ -74,7 +74,7 @@ readModels:
|
|
|
74
74
|
source:
|
|
75
75
|
module: products
|
|
76
76
|
aggregate: Product
|
|
77
|
-
tableName:
|
|
77
|
+
tableName: rm_orders_products
|
|
78
78
|
fields:
|
|
79
79
|
- name: id
|
|
80
80
|
type: String
|
|
@@ -96,7 +96,7 @@ readModels:
|
|
|
96
96
|
source:
|
|
97
97
|
module: customers
|
|
98
98
|
aggregate: Customer
|
|
99
|
-
tableName:
|
|
99
|
+
tableName: rm_orders_customers
|
|
100
100
|
fields:
|
|
101
101
|
- name: id
|
|
102
102
|
type: String
|
|
@@ -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}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
# orders — Domain Model
|
|
3
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
# Gestión de órdenes de compra. Usa readModels para mantener proyecciones
|
|
5
|
+
# locales de Product y Customer, eliminando dependencias HTTP síncronas.
|
|
6
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
aggregates:
|
|
9
|
+
- name: Order
|
|
10
|
+
entities:
|
|
11
|
+
- name: Order
|
|
12
|
+
isRoot: true
|
|
13
|
+
tableName: orders
|
|
14
|
+
audit:
|
|
15
|
+
enabled: true
|
|
16
|
+
trackUser: true
|
|
17
|
+
fields:
|
|
18
|
+
- name: id
|
|
19
|
+
type: String
|
|
20
|
+
- name: orderNumber
|
|
21
|
+
type: String
|
|
22
|
+
validations:
|
|
23
|
+
- type: NotBlank
|
|
24
|
+
message: "Order number is required"
|
|
25
|
+
- name: customerId
|
|
26
|
+
type: String
|
|
27
|
+
reference:
|
|
28
|
+
aggregate: Customer
|
|
29
|
+
module: customer
|
|
30
|
+
validations:
|
|
31
|
+
- type: NotBlank
|
|
32
|
+
message: "Customer ID is required"
|
|
33
|
+
- name: productId
|
|
34
|
+
type: String
|
|
35
|
+
reference:
|
|
36
|
+
aggregate: Product
|
|
37
|
+
module: product
|
|
38
|
+
validations:
|
|
39
|
+
- type: NotBlank
|
|
40
|
+
message: "Product ID is required"
|
|
41
|
+
- name: quantity
|
|
42
|
+
type: Integer
|
|
43
|
+
validations:
|
|
44
|
+
- type: Positive
|
|
45
|
+
- name: totalAmount
|
|
46
|
+
type: BigDecimal
|
|
47
|
+
readOnly: true
|
|
48
|
+
defaultValue: "0.00"
|
|
49
|
+
|
|
50
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
# Declarative REST endpoints
|
|
52
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
endpoints:
|
|
54
|
+
basePath: /orders
|
|
55
|
+
versions:
|
|
56
|
+
- version: v1
|
|
57
|
+
operations:
|
|
58
|
+
- useCase: CreateOrder
|
|
59
|
+
method: POST
|
|
60
|
+
path: /
|
|
61
|
+
- useCase: GetOrder
|
|
62
|
+
method: GET
|
|
63
|
+
path: /{id}
|
|
64
|
+
- useCase: FindAllOrders
|
|
65
|
+
method: GET
|
|
66
|
+
path: /
|
|
67
|
+
- useCase: UpdateOrder
|
|
68
|
+
method: PUT
|
|
69
|
+
path: /{id}
|
|
70
|
+
- useCase: DeleteOrder
|
|
71
|
+
method: DELETE
|
|
72
|
+
path: /{id}
|
|
73
|
+
|
|
74
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
75
|
+
# Read Models — proyecciones locales de datos de otros módulos
|
|
76
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
77
|
+
# Cada readModel mantiene una tabla local (rm_*) sincronizada por eventos de
|
|
78
|
+
# ciclo de vida del módulo productor. Elimina la necesidad de ports: (HTTP).
|
|
79
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
80
|
+
readModels:
|
|
81
|
+
- name: ProductReadModel
|
|
82
|
+
source:
|
|
83
|
+
module: product
|
|
84
|
+
aggregate: Product
|
|
85
|
+
tableName: rm_orders_products
|
|
86
|
+
fields:
|
|
87
|
+
- name: id
|
|
88
|
+
type: String
|
|
89
|
+
- name: name
|
|
90
|
+
type: String
|
|
91
|
+
- name: price
|
|
92
|
+
type: BigDecimal
|
|
93
|
+
- name: categoryId
|
|
94
|
+
type: String
|
|
95
|
+
syncedBy:
|
|
96
|
+
- event: ProductCreatedEvent
|
|
97
|
+
action: UPSERT
|
|
98
|
+
- event: ProductUpdatedEvent
|
|
99
|
+
action: UPSERT
|
|
100
|
+
|
|
101
|
+
- name: CustomerReadModel
|
|
102
|
+
source:
|
|
103
|
+
module: customer
|
|
104
|
+
aggregate: Customer
|
|
105
|
+
tableName: rm_orders_customers
|
|
106
|
+
fields:
|
|
107
|
+
- name: id
|
|
108
|
+
type: String
|
|
109
|
+
- name: fullName
|
|
110
|
+
type: String
|
|
111
|
+
- name: email
|
|
112
|
+
type: String
|
|
113
|
+
syncedBy:
|
|
114
|
+
- event: CustomerCreatedEvent
|
|
115
|
+
action: UPSERT
|
|
116
|
+
- event: CustomerUpdatedEvent
|
|
117
|
+
action: UPSERT
|
|
118
|
+
- event: CustomerDeletedEvent
|
|
119
|
+
action: DELETE
|
|
@@ -80,6 +80,33 @@ aggregates:
|
|
|
80
80
|
- DISCONTINUED
|
|
81
81
|
|
|
82
82
|
events:
|
|
83
|
+
# ProductCreatedEvent — lifecycle: create → raise() in creation constructor
|
|
84
|
+
# Feeds readModels in consumer modules (e.g. orders/ProductReadModel)
|
|
85
|
+
- name: ProductCreatedEvent
|
|
86
|
+
lifecycle: create
|
|
87
|
+
fields:
|
|
88
|
+
- name: productId
|
|
89
|
+
type: String
|
|
90
|
+
- name: name
|
|
91
|
+
type: String
|
|
92
|
+
- name: price
|
|
93
|
+
type: BigDecimal
|
|
94
|
+
- name: categoryId
|
|
95
|
+
type: String
|
|
96
|
+
|
|
97
|
+
# ProductUpdatedEvent — lifecycle: update → raise() in update() method
|
|
98
|
+
- name: ProductUpdatedEvent
|
|
99
|
+
lifecycle: update
|
|
100
|
+
fields:
|
|
101
|
+
- name: productId
|
|
102
|
+
type: String
|
|
103
|
+
- name: name
|
|
104
|
+
type: String
|
|
105
|
+
- name: price
|
|
106
|
+
type: BigDecimal
|
|
107
|
+
- name: categoryId
|
|
108
|
+
type: String
|
|
109
|
+
|
|
83
110
|
# ProductPublishedEvent — triggered by publish transition.
|
|
84
111
|
# price: BigDecimal tests VO-unwrap: entity.price is Price, event expects BigDecimal
|
|
85
112
|
# → generator should emit: this.getPrice().getAmount()
|