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
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
6
|
const ConfigManager = require('../utils/config-manager');
|
|
7
7
|
const { isEva4jProject, moduleExists } = require('../utils/validator');
|
|
8
|
-
const { toPackagePath, toPascalCase } = require('../utils/naming');
|
|
8
|
+
const { toPackagePath, toPascalCase, toCamelCase } = require('../utils/naming');
|
|
9
9
|
const { renderAndWrite } = require('../utils/template-engine');
|
|
10
10
|
const ChecksumManager = require('../utils/checksum-manager');
|
|
11
11
|
|
|
@@ -32,6 +32,9 @@ async function generateUsecaseCommand(moduleName, usecaseName, options = {}) {
|
|
|
32
32
|
const { packageName } = projectConfig;
|
|
33
33
|
const packagePath = toPackagePath(packageName);
|
|
34
34
|
|
|
35
|
+
// Normalise module name to camelCase (system.yaml uses kebab-case, .eva4j.json stores camelCase)
|
|
36
|
+
moduleName = toCamelCase(moduleName);
|
|
37
|
+
|
|
35
38
|
// Validate module exists
|
|
36
39
|
if (!(await configManager.moduleExists(moduleName))) {
|
|
37
40
|
console.error(chalk.red(`❌ Module '${moduleName}' not found in project configuration`));
|
|
@@ -47,6 +47,9 @@ Antes de generar nada, lee estos archivos del proyecto para obtener contexto:
|
|
|
47
47
|
| 6.5 | `system/c4-context.mmd` + `system/c4-container.mmd` | Este archivo (sección C4) |
|
|
48
48
|
| 7 | `system/{module}.yaml` (uno por módulo) | `references/domain-yaml-spec.md` |
|
|
49
49
|
| 8 | `system/{module}.md` (uno por módulo) | `references/module-spec.md` |
|
|
50
|
+
| 9a | `AGENTS.md` (project root — rewrite) | Este archivo (Paso 9) |
|
|
51
|
+
| 9b | `system/VALIDATION_FLOWS.md` | Este archivo (Paso 9) |
|
|
52
|
+
| 9c | `system/USER_FLOWS.md` | Este archivo (Paso 9) |
|
|
50
53
|
|
|
51
54
|
Ejecuta **todos** los pasos en orden antes de devolver el control al usuario.
|
|
52
55
|
|
|
@@ -54,7 +57,14 @@ Ejecuta **todos** los pasos en orden antes de devolver el control al usuario.
|
|
|
54
57
|
|
|
55
58
|
## Paso 1 — Recopilar información
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
**Antes de preguntar nada**, verifica si ya existen artefactos del skill `requirements-elicitation`:
|
|
61
|
+
- `system/FUNCTIONAL_REQUIREMENTS.md` — casos de uso, actores, ciclos de vida, integraciones externas
|
|
62
|
+
- `system/PRODUCT_FLOWS.md` — flujos de negocio por actor (happy paths + alternativos)
|
|
63
|
+
- `system/BUSINESS_RULES.md` — reglas de negocio, invariantes, restricciones
|
|
64
|
+
|
|
65
|
+
Si existen, **léelos primero** y extrae de ellos el contexto de negocio. Reduce o elimina las preguntas que ya están respondidas por estos archivos. Estos documentos son la fuente de verdad funcional — los módulos, casos de uso, estados y reglas que diseñes deben ser consistentes con lo que describen.
|
|
66
|
+
|
|
67
|
+
Si el usuario no proveyó todos los datos y los archivos anteriores no existen, **pregunta** antes de generar:
|
|
58
68
|
|
|
59
69
|
0. **Contexto del negocio** — ¿Cuál es el dominio? Actores, procesos clave, reglas importantes.
|
|
60
70
|
1. **¿Usa mensajería asíncrona?** `kafka` | `rabbitmq` | `sns-sqs`
|
|
@@ -62,11 +72,32 @@ Si el usuario no proveyó todos los datos, **pregunta** antes de generar:
|
|
|
62
72
|
3. **Endpoints REST** por módulo (método + path + caso de uso)
|
|
63
73
|
4. **Flujos async**: evento → productor → consumidores + `useCase` de cada consumidor
|
|
64
74
|
5. **Llamadas sync**: caller → destino → endpoints usados
|
|
75
|
+
6. **Dependencias de datos cross-module**: ¿algún módulo necesita datos de otro para validar o enriquecer? → candidato a **Read Model** (proyección local mantenida por eventos) en vez de llamada sync
|
|
65
76
|
|
|
66
77
|
> Si `system/system.yaml` ya existe, léelo y pregunta solo por cambios.
|
|
67
78
|
|
|
68
79
|
Aplica el rol funcional: sugiere módulos necesarios no mencionados, propone flujos async coherentes, anticipa invariantes. Confirma antes de agregar elementos no solicitados.
|
|
69
80
|
|
|
81
|
+
### Read Models — decisión de diseño
|
|
82
|
+
|
|
83
|
+
Cuando un módulo necesita datos de otro módulo, evalúa antes de decidir entre `ports:` (sync HTTP) y `readModels:` (async, proyección local):
|
|
84
|
+
|
|
85
|
+
| Pregunta | Si la respuesta es SÍ → |
|
|
86
|
+
|---|---|
|
|
87
|
+
| ¿El dato se consulta en cada request o en operaciones frecuentes? | `readModels:` |
|
|
88
|
+
| ¿Se tolera consistencia eventual (ms de delay)? | `readModels:` |
|
|
89
|
+
| ¿Se prepara el sistema para microservicios (`eva detach`)? | `readModels:` |
|
|
90
|
+
| ¿Se necesita consistencia fuerte (ej: saldo financiero)? | `ports:` |
|
|
91
|
+
| ¿Es una llamada infrecuente y simple (ej: lookup puntual)? | `ports:` |
|
|
92
|
+
|
|
93
|
+
**Patrón típico:** Si un módulo ya consume eventos de otro módulo (`listeners:`) y además llama sync para obtener datos del mismo módulo (`ports:`), es candidato fuerte a reemplazar el port por un readModel.
|
|
94
|
+
|
|
95
|
+
Si decides usar readModel:
|
|
96
|
+
1. En `system.yaml`: declarar eventos con `consumers[].readModel:` en vez de `useCase:`
|
|
97
|
+
2. En `{module}.yaml`: declarar `readModels:` con `source`, `tableName`, `fields`, `syncedBy`
|
|
98
|
+
3. Eliminar la entrada `integrations.sync[]` que reemplaza
|
|
99
|
+
4. Asegurar que el módulo fuente emita los eventos necesarios en sus `events:` — usar `lifecycle:` para eventos CRUD (`create`/`update`/`delete`/`softDelete`) en vez de `triggers:`
|
|
100
|
+
|
|
70
101
|
---
|
|
71
102
|
|
|
72
103
|
## Paso 2 — Estructura del system.yaml
|
|
@@ -108,6 +139,13 @@ integrations:
|
|
|
108
139
|
consumers:
|
|
109
140
|
- module: payments
|
|
110
141
|
useCase: HandleOrderPlaced
|
|
142
|
+
# Read Model sync — usa readModel: en vez de useCase:
|
|
143
|
+
- event: ProductCreatedEvent
|
|
144
|
+
producer: products
|
|
145
|
+
topic: PRODUCT_CREATED
|
|
146
|
+
consumers:
|
|
147
|
+
- module: orders
|
|
148
|
+
readModel: ProductReadModel # proyección local de datos cross-module
|
|
111
149
|
sync:
|
|
112
150
|
- caller: orders
|
|
113
151
|
calls: customers
|
|
@@ -134,6 +172,8 @@ integrations:
|
|
|
134
172
|
- Sin port names genéricos compartidos entre módulos
|
|
135
173
|
- `consumers[].useCase` siempre presente y en PascalCase
|
|
136
174
|
- `calls.using:` solo referencia endpoints de `exposes:` del destino
|
|
175
|
+
- Cada consumer declara exactamente `useCase:` (lógica de negocio) o `readModel:` (proyección local), nunca ambos
|
|
176
|
+
- `readModel:` PascalCase + sufijo `ReadModel` — su módulo consumidor declara `readModels:` en domain.yaml
|
|
137
177
|
|
|
138
178
|
---
|
|
139
179
|
|
|
@@ -146,6 +186,8 @@ Antes de proponer el `system.yaml`, verifica:
|
|
|
146
186
|
- [ ] Sin dependencias circulares síncronas
|
|
147
187
|
- [ ] Todos los `consumers[].module` existen en `modules:`
|
|
148
188
|
- [ ] Todos los `consumers[].useCase` presentes y en PascalCase
|
|
189
|
+
- [ ] `consumers[]` con `readModel:` en PascalCase + sufijo `ReadModel`
|
|
190
|
+
- [ ] Cada consumer tiene exactamente `useCase:` o `readModel:`, nunca ambos
|
|
149
191
|
- [ ] Todos los `calls.using:` existen en `exposes:` del destino
|
|
150
192
|
- [ ] Módulos pasivos no son `caller`
|
|
151
193
|
- [ ] Todo en inglés
|
|
@@ -257,6 +299,7 @@ C4Container
|
|
|
257
299
|
- `ContainerQueue()` para el broker — solo si `messaging.enabled: true`; usar el tipo de `messaging.broker`
|
|
258
300
|
- `System_Ext()` fuera del boundary para servicios externos
|
|
259
301
|
- **Flechas async siempre pasan por el broker**: `producer → broker` y `broker → consumer`, nunca directo
|
|
302
|
+
- **Read Model sync también pasa por el broker**: `Rel(source, broker, "ProductCreatedEvent", "Kafka")` + `Rel(broker, consumer, "Sync ProductReadModel", "Kafka")` — no usar flecha directa
|
|
260
303
|
- **Flechas sync** directas entre containers: de caller a callee con label del port name
|
|
261
304
|
- Los `Rel()` de eventos incluyen el nombre del evento en el campo `technology`/label
|
|
262
305
|
- Derivar actores `Person()` de quienes consumen los `exposes[]`
|
|
@@ -302,7 +345,118 @@ Antes de guardar, verifica:
|
|
|
302
345
|
|
|
303
346
|
Lee `references/domain-yaml-spec.md` para la especificación completa de estructura, reglas, restricciones y checklist del `system/{module}.yaml`.
|
|
304
347
|
|
|
305
|
-
Para cada módulo en `modules:`, genera `system/{nombre-del-modulo}.yaml` con: aggregates, entities, valueObjects, enums (con transitions si aplica), events, endpoints, listeners y
|
|
348
|
+
Para cada módulo en `modules:`, genera `system/{nombre-del-modulo}.yaml` con: aggregates, entities, valueObjects, enums (con transitions si aplica), events, endpoints, listeners, ports y **readModels** — todo inferido del `system.yaml`.
|
|
349
|
+
|
|
350
|
+
### Endpoints en módulos con múltiples agregados
|
|
351
|
+
|
|
352
|
+
Si el módulo tiene **2 o más agregados** (ej: `Product` + `Category`), la sección `endpoints:` debe usar `basePath: ""` (string vacío) y paths **absolutos** en cada operación:
|
|
353
|
+
|
|
354
|
+
```yaml
|
|
355
|
+
# Módulo con 2+ agregados → basePath vacío
|
|
356
|
+
endpoints:
|
|
357
|
+
basePath: ""
|
|
358
|
+
versions:
|
|
359
|
+
- version: v1
|
|
360
|
+
operations:
|
|
361
|
+
- useCase: CreateProduct
|
|
362
|
+
method: POST
|
|
363
|
+
path: /products
|
|
364
|
+
- useCase: CreateCategory
|
|
365
|
+
method: POST
|
|
366
|
+
path: /categories
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Si el módulo tiene **un solo agregado**, usar `basePath: /recurso` con paths relativos (ej: `/`, `/{id}`).
|
|
370
|
+
|
|
371
|
+
**NUNCA usar `basePath: /`** (con slash) — genera trailing slash en `@RequestMapping`. Usar `basePath: ""` (vacío).
|
|
372
|
+
|
|
373
|
+
### Inferencia de readModels desde system.yaml
|
|
374
|
+
|
|
375
|
+
Cuando `integrations.async[].consumers[]` tiene `readModel:` y el `module` es el módulo actual:
|
|
376
|
+
1. Agrupar todos los eventos del mismo `readModel:` → una entrada `readModels:` con múltiples `syncedBy`
|
|
377
|
+
2. `source.module` = el `producer` de esas integraciones async
|
|
378
|
+
3. `source.aggregate` = derivar del nombre del readModel (ej: `ProductReadModel` → `Product`)
|
|
379
|
+
4. `tableName` = `rm_` + consumer module + `_` + source module en snake_case (ej: `rm_orders_products`)
|
|
380
|
+
5. `fields` = inferir del payload del evento fuente (incluir siempre `id`)
|
|
381
|
+
6. `syncedBy[].action` = `UPSERT` para Created/Updated, `SOFT_DELETE` para Deactivated, `DELETE` para Deleted
|
|
382
|
+
7. Si había una entrada `integrations.sync[]` al mismo módulo fuente → **no generar `ports:`** para esa llamada (el readModel la reemplaza)
|
|
383
|
+
|
|
384
|
+
### Lifecycle events en módulos fuente
|
|
385
|
+
|
|
386
|
+
Cuando el módulo actual es `producer` en `integrations.async[]` y algún consumer tiene `readModel:`, los eventos de este módulo deben usar `lifecycle:` (operación CRUD) en vez de `triggers:` (transición de estado).
|
|
387
|
+
|
|
388
|
+
Para cada evento de este módulo consumido por un readModel:
|
|
389
|
+
|
|
390
|
+
1. Agregar `lifecycle:` al evento — derivar el valor del nombre del evento:
|
|
391
|
+
| Patrón del nombre | `lifecycle:` |
|
|
392
|
+
|---|---|
|
|
393
|
+
| `*CreatedEvent`, `*RegisteredEvent` | `create` |
|
|
394
|
+
| `*UpdatedEvent` | `update` |
|
|
395
|
+
| `*DeletedEvent` | `delete` |
|
|
396
|
+
| `*DeactivatedEvent` | `softDelete` |
|
|
397
|
+
|
|
398
|
+
2. **NO** agregar `triggers:` — estos eventos son CRUD, no transiciones de estado
|
|
399
|
+
3. Si `lifecycle: softDelete` → la entidad raíz **debe** tener `hasSoftDelete: true`
|
|
400
|
+
4. Si `lifecycle: delete` → la entidad raíz **NO debe** tener `hasSoftDelete: true`
|
|
401
|
+
5. `fields:` del evento debe incluir **todos** los campos declarados en el `readModels[].fields` del módulo consumidor (el payload es la fuente de verdad de la proyección)
|
|
402
|
+
6. Siempre incluir `{entityName}Id` como campo (se mapea a `aggregateId` del DomainEvent base)
|
|
403
|
+
7. `fields:` del lifecycle event solo puede contener: (a) `{entityName}Id` (aggregateId), (b) campos que existen en la entidad raíz, (c) campos temporales `*At` + `LocalDateTime`. No incluir campos que no existan en la entidad — genera error `C2-010`
|
|
404
|
+
8. Los campos de los readModels consumidores deben ser subconjunto de los campos de la entidad raíz del productor. Si el readModel necesita un campo, ese campo debe existir en la entidad fuente — de lo contrario los lifecycle events no podrán emitirlo (C2-010) y el campo siempre será null (C1-007)
|
|
405
|
+
|
|
406
|
+
**Ejemplo — módulo `products` como fuente de `ProductReadModel`:**
|
|
407
|
+
|
|
408
|
+
```yaml
|
|
409
|
+
aggregates:
|
|
410
|
+
- name: Product
|
|
411
|
+
entities:
|
|
412
|
+
- name: product
|
|
413
|
+
isRoot: true
|
|
414
|
+
tableName: products
|
|
415
|
+
hasSoftDelete: true
|
|
416
|
+
audit:
|
|
417
|
+
enabled: true
|
|
418
|
+
fields:
|
|
419
|
+
- name: id
|
|
420
|
+
type: String
|
|
421
|
+
- name: name
|
|
422
|
+
type: String
|
|
423
|
+
- name: price
|
|
424
|
+
type: BigDecimal
|
|
425
|
+
- name: status
|
|
426
|
+
type: String
|
|
427
|
+
readOnly: true
|
|
428
|
+
defaultValue: "ACTIVE"
|
|
429
|
+
events:
|
|
430
|
+
- name: ProductCreatedEvent
|
|
431
|
+
lifecycle: create
|
|
432
|
+
fields:
|
|
433
|
+
- name: productId
|
|
434
|
+
type: String
|
|
435
|
+
- name: name
|
|
436
|
+
type: String
|
|
437
|
+
- name: price
|
|
438
|
+
type: BigDecimal
|
|
439
|
+
- name: status
|
|
440
|
+
type: String
|
|
441
|
+
- name: ProductUpdatedEvent
|
|
442
|
+
lifecycle: update
|
|
443
|
+
fields:
|
|
444
|
+
- name: productId
|
|
445
|
+
type: String
|
|
446
|
+
- name: name
|
|
447
|
+
type: String
|
|
448
|
+
- name: price
|
|
449
|
+
type: BigDecimal
|
|
450
|
+
- name: status
|
|
451
|
+
type: String
|
|
452
|
+
- name: ProductDeactivatedEvent
|
|
453
|
+
lifecycle: softDelete
|
|
454
|
+
fields:
|
|
455
|
+
- name: productId
|
|
456
|
+
type: String
|
|
457
|
+
- name: deactivatedAt
|
|
458
|
+
type: LocalDateTime
|
|
459
|
+
```
|
|
306
460
|
|
|
307
461
|
---
|
|
308
462
|
|
|
@@ -314,10 +468,197 @@ Para cada módulo, genera `system/{nombre-del-modulo}.md` con: rol del módulo,
|
|
|
314
468
|
|
|
315
469
|
---
|
|
316
470
|
|
|
471
|
+
## Paso 9 — Artefactos post-diseño
|
|
472
|
+
|
|
473
|
+
Inmediatamente después de completar el Paso 8, genera tres artefactos finales que contextualizan el sistema recién diseñado.
|
|
474
|
+
|
|
475
|
+
### Paso 9a — Reescribir AGENTS.md (project-specific)
|
|
476
|
+
|
|
477
|
+
Reescribe el archivo `AGENTS.md` en la **raíz del proyecto** con contenido específico para el sistema diseñado.
|
|
478
|
+
|
|
479
|
+
**Proceso:**
|
|
480
|
+
|
|
481
|
+
1. Lee el `AGENTS.md` actual como template base
|
|
482
|
+
2. Analiza `system/system.yaml` y todos los `system/{module}.yaml` para detectar qué features se usan:
|
|
483
|
+
- Tipo de broker (Kafka / RabbitMQ / ninguno)
|
|
484
|
+
- `readModels:` en algún módulo
|
|
485
|
+
- `ports:` (llamadas HTTP síncronas)
|
|
486
|
+
- `listeners:` (consumidores de eventos)
|
|
487
|
+
- `events:` con `triggers:` vs `lifecycle:`
|
|
488
|
+
- `hasSoftDelete` en alguna entidad
|
|
489
|
+
- `audit.trackUser` en alguna entidad
|
|
490
|
+
- Value Objects con `methods:`
|
|
491
|
+
- Enums con `transitions:` e `initialValue`
|
|
492
|
+
- Flags de campo: `readOnly`, `hidden`, `defaultValue`, `validations`, `reference`
|
|
493
|
+
3. **Poda** secciones de features no usados según estas reglas:
|
|
494
|
+
|
|
495
|
+
| Condición | Sección a eliminar |
|
|
496
|
+
|---|---|
|
|
497
|
+
| Sin Temporal | "Temporal Workflows" completa |
|
|
498
|
+
| Sin `readModels:` | Subsecciones readModels de "Características Avanzadas" y checklist |
|
|
499
|
+
| Sin `ports:` | Subsecciones ports |
|
|
500
|
+
| Sin `listeners:` | Subsecciones listeners |
|
|
501
|
+
| Sin `hasSoftDelete` | Sección soft delete y checklist items |
|
|
502
|
+
| Sin `audit.trackUser` | Infraestructura UserContextFilter/UserContextHolder/AuditorAwareImpl (mantener audit básico si `audit.enabled`) |
|
|
503
|
+
| Sin VO `methods:` | "Value Objects con Métodos" |
|
|
504
|
+
| Sin enum `transitions:` | "Enums con Ciclo de Vida" |
|
|
505
|
+
|
|
506
|
+
4. **Especializa** el contenido restante:
|
|
507
|
+
- Reemplaza ejemplos genéricos (`User`, `Order`) con entidades/módulos reales del proyecto
|
|
508
|
+
- Actualiza la sección de comandos `eva` con los nombres de módulos reales
|
|
509
|
+
- Actualiza el ejemplo de `domain.yaml` con la estructura real del proyecto
|
|
510
|
+
- Reduce el checklist a solo items relevantes para este proyecto
|
|
511
|
+
5. Agrega un **header de contexto** al inicio:
|
|
512
|
+
|
|
513
|
+
```markdown
|
|
514
|
+
# AI Agent Guide — {System Name}
|
|
515
|
+
|
|
516
|
+
## Project Overview
|
|
517
|
+
- **System:** {name} — {brief description from system.yaml}
|
|
518
|
+
- **Modules:** {list of modules with 1-line descriptions}
|
|
519
|
+
- **Messaging:** {broker type or "none"}
|
|
520
|
+
- **Database:** {database type}
|
|
521
|
+
- **Java:** {javaVersion} / **Spring Boot:** {springBootVersion}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
6. **Siempre conserva** (son universales): principios DDD, arquitectura hexagonal, reglas de mappers, reglas de DTOs, diagramas de flujo de datos (Command write / Query read), patrones de testing
|
|
525
|
+
7. Escribe **todo en inglés**
|
|
526
|
+
8. **Límite: ≤ 1000 líneas** — poda agresivamente, comprime ejemplos, evita redundancia
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
### Paso 9b — Crear system/VALIDATION_FLOWS.md
|
|
531
|
+
|
|
532
|
+
Genera `system/VALIDATION_FLOWS.md` con los flujos de validación técnica del sistema. Toda la información se deriva de `system.yaml` y los `{module}.yaml`.
|
|
533
|
+
|
|
534
|
+
**Estructura obligatoria:**
|
|
535
|
+
|
|
536
|
+
```markdown
|
|
537
|
+
# Validation Flows — {System Name}
|
|
538
|
+
|
|
539
|
+
## Prerequisites
|
|
540
|
+
- Services: {infrastructure requerida — DB, broker, etc.}
|
|
541
|
+
- Startup order: {si relevante}
|
|
542
|
+
- Base URLs: {por módulo si difieren}
|
|
543
|
+
|
|
544
|
+
## 1. Module Validation
|
|
545
|
+
|
|
546
|
+
### 1.1 {Module Name}
|
|
547
|
+
|
|
548
|
+
#### CRUD Operations
|
|
549
|
+
| # | Operation | Endpoint | Payload/Params | Expected Result | Validates |
|
|
550
|
+
|---|-----------|----------|----------------|-----------------|------ ----|
|
|
551
|
+
| 1 | Create | POST /x | {key fields} | 201 + entity | {invariant} |
|
|
552
|
+
| 2 | Get by ID | GET /x/{id} | — | 200 + entity | — |
|
|
553
|
+
| 3 | List | GET /x | — | 200 + page | — |
|
|
554
|
+
| 4 | Update | PUT /x/{id} | {fields} | 200 + updated | — |
|
|
555
|
+
| 5 | Delete | DELETE /x/{id} | — | 204 | — |
|
|
556
|
+
|
|
557
|
+
#### State Transitions (if module has enum transitions)
|
|
558
|
+
| # | Transition | Endpoint | Precondition | Expected | Event Emitted |
|
|
559
|
+
|---|-----------|----------|--------------|----------|---------------|
|
|
560
|
+
| 1 | DRAFT→PUBLISHED | PUT /x/{id}/publish | exists in DRAFT | 200, status=PUBLISHED | XPublishedEvent |
|
|
561
|
+
|
|
562
|
+
#### Business Rules
|
|
563
|
+
| # | Rule | How to Trigger | Expected Error |
|
|
564
|
+
|---|------|----------------|----------------|
|
|
565
|
+
|
|
566
|
+
(repeat per module)
|
|
567
|
+
|
|
568
|
+
## 2. Integration Flows
|
|
569
|
+
|
|
570
|
+
### 2.1 {Flow Name}: {Event} → {Consumer Module}
|
|
571
|
+
**Trigger:** {action that emits the event}
|
|
572
|
+
**Steps:**
|
|
573
|
+
1. {Create/modify entity in producer module}
|
|
574
|
+
2. {Event emitted}: {EventName} on topic {TOPIC_NAME}
|
|
575
|
+
3. {Consumer module} processes via {useCase}
|
|
576
|
+
4. **Verify:** {expected state change in consumer}
|
|
577
|
+
|
|
578
|
+
(repeat per async integration)
|
|
579
|
+
|
|
580
|
+
## 3. Read Model Synchronization (if applicable)
|
|
581
|
+
### 3.1 {ReadModelName}
|
|
582
|
+
| Source Event | Action | Verification |
|
|
583
|
+
|---|---|---|
|
|
584
|
+
| XCreatedEvent | UPSERT | Query rm_table, record exists |
|
|
585
|
+
| XUpdatedEvent | UPSERT | Fields updated |
|
|
586
|
+
| XDeactivatedEvent | SOFT_DELETE | Record marked deleted |
|
|
587
|
+
|
|
588
|
+
## 4. Sync Port Calls (if applicable)
|
|
589
|
+
### 4.1 {PortName}: {caller} → {target}
|
|
590
|
+
| Method | Expected | Fallback |
|
|
591
|
+
|---|---|---|
|
|
592
|
+
|
|
593
|
+
## 5. Error & Edge Cases
|
|
594
|
+
| # | Scenario | Steps | Expected Error |
|
|
595
|
+
|---|----------|-------|----------------|
|
|
596
|
+
| 1 | Create with missing required field | POST /x without {field} | 400 + validation message |
|
|
597
|
+
| 2 | Invalid state transition | PUT /x/{id}/action when invalid state | 400/409 + business error |
|
|
598
|
+
| 3 | Get non-existent entity | GET /x/{invalid-id} | 404 |
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
**Reglas:**
|
|
602
|
+
- Cada flujo debe ser concreto: paths reales, nombres de eventos reales, campos reales del proyecto
|
|
603
|
+
- Incluir payloads JSON sugeridos donde sea útil
|
|
604
|
+
- Omitir secciones enteras si no aplican (ej: sin readModels → omitir sección 3)
|
|
605
|
+
- Todo en inglés
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
### Paso 9c — Crear system/USER_FLOWS.md
|
|
610
|
+
|
|
611
|
+
Genera `system/USER_FLOWS.md` con los flujos end-to-end desde la perspectiva del usuario.
|
|
612
|
+
|
|
613
|
+
**Estructura obligatoria:**
|
|
614
|
+
|
|
615
|
+
```markdown
|
|
616
|
+
# User Flows — {System Name}
|
|
617
|
+
|
|
618
|
+
## Actors
|
|
619
|
+
| Actor | Description | Modules Interacted |
|
|
620
|
+
|-------|-------------|--------------------|
|
|
621
|
+
| {Actor 1} | {role description} | {module list} |
|
|
622
|
+
|
|
623
|
+
## Flow 1: {Business Process Name}
|
|
624
|
+
**Actor:** {who}
|
|
625
|
+
**Goal:** {what they want to achieve}
|
|
626
|
+
**Preconditions:** {initial state}
|
|
627
|
+
|
|
628
|
+
### Happy Path
|
|
629
|
+
| Step | User Action | System Response | Behind the Scenes |
|
|
630
|
+
|------|-------------|-----------------|-------------------|
|
|
631
|
+
| 1 | {does X} | {sees Y} | {endpoint called, event emitted, etc.} |
|
|
632
|
+
| 2 | {does Z} | {sees W} | {consumer processes, state changes} |
|
|
633
|
+
|
|
634
|
+
### Alternative Paths
|
|
635
|
+
| Condition | At Step | What Happens |
|
|
636
|
+
|-----------|---------|---------- ---|
|
|
637
|
+
| {condition} | {N} | {alternative outcome} |
|
|
638
|
+
|
|
639
|
+
### Error Paths
|
|
640
|
+
| Error | At Step | User Sees |
|
|
641
|
+
|-------|---------|----------|
|
|
642
|
+
| {error} | {N} | {error message/behavior} |
|
|
643
|
+
|
|
644
|
+
(repeat per major business flow)
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
**Reglas:**
|
|
648
|
+
- Derivar actores de quienes consumen los `exposes[]` (misma fuente que `Person()` del C4 Context)
|
|
649
|
+
- Cada flujo es un **escenario de negocio completo** que cruza módulos cuando aplica
|
|
650
|
+
- La columna "Behind the Scenes" conecta la experiencia de usuario con la realidad técnica (eventos, procesamiento async, state changes)
|
|
651
|
+
- Incluir al menos un flujo por cada camino principal de caso de uso del sistema
|
|
652
|
+
- Foco en comportamiento observable por el usuario, no implementación interna
|
|
653
|
+
- Todo en inglés
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
317
657
|
## Ciclo de refinamiento
|
|
318
658
|
|
|
319
659
|
Después de entregar v1, si el usuario pide ajustes:
|
|
320
660
|
- Aplica el **cambio mínimo** necesario
|
|
321
661
|
- Revalida el checklist del Paso 4
|
|
322
662
|
- Actualiza `system.md`, `c4-context.mmd`, `c4-container.mmd`, `{module}.yaml` y `{module}.md` afectados
|
|
663
|
+
- Actualiza `AGENTS.md`, `VALIDATION_FLOWS.md` y `USER_FLOWS.md` si fueron afectados por el cambio
|
|
323
664
|
- Entrega solo el diff explicado
|