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,192 @@
|
|
|
1
|
+
# Command `add rabbitmq-client`
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Adds RabbitMQ client support to an existing eva4j project. Installs Spring AMQP dependencies, generates configuration files for all environments, creates the `RabbitMQConfig.java` class with production-ready defaults, and adds a RabbitMQ service to `docker-compose.yaml`.
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Enable asynchronous event-driven communication between modules or microservices using RabbitMQ as the message broker. This command sets up the foundational infrastructure so that subsequent commands (`eva g rabbitmq-event`, `eva g rabbitmq-listener`) can generate producers and consumers.
|
|
10
|
+
|
|
11
|
+
## Syntax
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
eva add rabbitmq-client
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
No parameters required.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- Must be inside an eva4j project (created with `eva create`)
|
|
22
|
+
- At least one module must exist (created with `eva add module`)
|
|
23
|
+
- **Kafka must NOT be installed** — only one broker per project is allowed
|
|
24
|
+
|
|
25
|
+
## What It Generates
|
|
26
|
+
|
|
27
|
+
### 1. build.gradle Dependencies
|
|
28
|
+
|
|
29
|
+
```groovy
|
|
30
|
+
// RabbitMQ
|
|
31
|
+
implementation 'org.springframework.boot:spring-boot-starter-amqp'
|
|
32
|
+
testImplementation 'org.springframework.amqp:spring-rabbit-test'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Configuration Files (per environment)
|
|
36
|
+
|
|
37
|
+
**`parameters/local/rabbitmq.yaml`:**
|
|
38
|
+
```yaml
|
|
39
|
+
spring:
|
|
40
|
+
rabbitmq:
|
|
41
|
+
host: localhost
|
|
42
|
+
port: 5672
|
|
43
|
+
username: guest
|
|
44
|
+
password: guest
|
|
45
|
+
virtual-host: /
|
|
46
|
+
listener:
|
|
47
|
+
simple:
|
|
48
|
+
acknowledge-mode: manual
|
|
49
|
+
concurrency: 3
|
|
50
|
+
retry:
|
|
51
|
+
enabled: true
|
|
52
|
+
max-attempts: 3
|
|
53
|
+
initial-interval: 1500
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**`parameters/production/rabbitmq.yaml`:**
|
|
57
|
+
```yaml
|
|
58
|
+
spring:
|
|
59
|
+
rabbitmq:
|
|
60
|
+
host: ${RABBITMQ_HOST}
|
|
61
|
+
port: ${RABBITMQ_PORT:5672}
|
|
62
|
+
username: ${RABBITMQ_USERNAME}
|
|
63
|
+
password: ${RABBITMQ_PASSWORD}
|
|
64
|
+
virtual-host: ${RABBITMQ_VHOST:/}
|
|
65
|
+
connection-timeout: 5000
|
|
66
|
+
requested-heartbeat: 60
|
|
67
|
+
publisher-confirm-type: correlated
|
|
68
|
+
publisher-returns: true
|
|
69
|
+
ssl:
|
|
70
|
+
enabled: ${RABBITMQ_SSL_ENABLED:false}
|
|
71
|
+
cache:
|
|
72
|
+
channel:
|
|
73
|
+
size: 25
|
|
74
|
+
template:
|
|
75
|
+
mandatory: true
|
|
76
|
+
listener:
|
|
77
|
+
simple:
|
|
78
|
+
acknowledge-mode: manual
|
|
79
|
+
concurrency: 5
|
|
80
|
+
max-concurrency: 20
|
|
81
|
+
prefetch: 10
|
|
82
|
+
retry:
|
|
83
|
+
enabled: true
|
|
84
|
+
max-attempts: 5
|
|
85
|
+
initial-interval: 2000
|
|
86
|
+
multiplier: 2.0
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. RabbitMQConfig.java
|
|
90
|
+
|
|
91
|
+
Located at `shared/infrastructure/configurations/rabbitmqConfig/RabbitMQConfig.java`.
|
|
92
|
+
|
|
93
|
+
Includes:
|
|
94
|
+
- `Jackson2JsonMessageConverter` for JSON serialization
|
|
95
|
+
- `RabbitAdmin` for auto-declaring exchanges, queues, and bindings
|
|
96
|
+
- `RabbitTemplate` with publisher confirms + returns
|
|
97
|
+
- `SimpleRabbitListenerContainerFactory` with manual ack mode and exponential backoff retry
|
|
98
|
+
|
|
99
|
+
### 4. Docker Compose Service
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
rabbitmq:
|
|
103
|
+
image: rabbitmq:4-management
|
|
104
|
+
container_name: <project>-rabbitmq
|
|
105
|
+
ports:
|
|
106
|
+
- "5672:5672" # AMQP protocol
|
|
107
|
+
- "15672:15672" # Management UI
|
|
108
|
+
environment:
|
|
109
|
+
RABBITMQ_DEFAULT_USER: guest
|
|
110
|
+
RABBITMQ_DEFAULT_PASS: guest
|
|
111
|
+
RABBITMQ_DEFAULT_VHOST: /
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 5. Application YAML Imports
|
|
115
|
+
|
|
116
|
+
Adds `rabbitmq.yaml` to the Spring config import chain in all `application-{env}.yaml` files:
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
spring:
|
|
120
|
+
config:
|
|
121
|
+
import:
|
|
122
|
+
- "classpath:parameters/local/rabbitmq.yaml"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Generated Files Summary
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
project/
|
|
129
|
+
├── build.gradle # + AMQP dependencies
|
|
130
|
+
├── docker-compose.yaml # + RabbitMQ service
|
|
131
|
+
├── src/main/java/.../shared/
|
|
132
|
+
│ └── infrastructure/configurations/rabbitmqConfig/
|
|
133
|
+
│ └── RabbitMQConfig.java # Core config class
|
|
134
|
+
└── src/main/resources/
|
|
135
|
+
├── application-local.yaml # + rabbitmq.yaml import
|
|
136
|
+
├── application-develop.yaml # + rabbitmq.yaml import
|
|
137
|
+
├── application-test.yaml # + rabbitmq.yaml import
|
|
138
|
+
├── application-production.yaml # + rabbitmq.yaml import
|
|
139
|
+
└── parameters/
|
|
140
|
+
├── local/rabbitmq.yaml # Local config
|
|
141
|
+
├── develop/rabbitmq.yaml # Develop config
|
|
142
|
+
├── test/rabbitmq.yaml # Test config
|
|
143
|
+
└── production/rabbitmq.yaml # Production config
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Mutual Exclusivity with Kafka
|
|
147
|
+
|
|
148
|
+
Only **one** message broker is allowed per project:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
eva add kafka-client # ✅ Installs Kafka
|
|
152
|
+
eva add rabbitmq-client # ❌ Error: Kafka client is already installed
|
|
153
|
+
|
|
154
|
+
# Or vice versa:
|
|
155
|
+
eva add rabbitmq-client # ✅ Installs RabbitMQ
|
|
156
|
+
eva add kafka-client # ❌ Error: RabbitMQ client is already installed
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
To switch brokers, you must manually remove the existing broker's dependencies, config files, and feature flag from `.eva4j.json`.
|
|
160
|
+
|
|
161
|
+
## After Installation
|
|
162
|
+
|
|
163
|
+
### Start RabbitMQ
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
docker-compose up -d
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Access Management UI
|
|
170
|
+
|
|
171
|
+
Open `http://localhost:15672` — credentials: `guest` / `guest`.
|
|
172
|
+
|
|
173
|
+
### Generate Events and Listeners
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Create an event producer
|
|
177
|
+
eva g rabbitmq-event user user-created
|
|
178
|
+
|
|
179
|
+
# Create an event consumer
|
|
180
|
+
eva g rabbitmq-listener notification
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Or use domain.yaml
|
|
184
|
+
|
|
185
|
+
Declare events in `domain.yaml` and run `eva g entities <module>` — all RabbitMQ infrastructure is auto-wired when `rabbitmq-client` is installed.
|
|
186
|
+
|
|
187
|
+
## See Also
|
|
188
|
+
|
|
189
|
+
- [`generate rabbitmq-event`](./GENERATE_RABBITMQ_EVENT.md) — Create event publisher
|
|
190
|
+
- [`generate rabbitmq-listener`](./GENERATE_RABBITMQ_LISTENER.md) — Create event consumer
|
|
191
|
+
- [`generate kafka-event`](./GENERATE_KAFKA_EVENT.md) — Kafka equivalent
|
|
192
|
+
- [RabbitMQ Production Config](../RABBITMQ_PRODUCTION_CONFIG.md) — Detailed parameter reference
|
|
@@ -9,15 +9,67 @@
|
|
|
9
9
|
3. [system.yaml structure required](#3-systemyaml-structure-required)
|
|
10
10
|
4. [system.yaml evaluation criteria (S1–S5)](#4-systemyaml-evaluation-criteria-s1s5)
|
|
11
11
|
- [S1 — Module integrity](#s1--module-integrity)
|
|
12
|
+
- [S1-001 — Undeclared module referenced in integrations](#s1-001--undeclared-module-referenced-in-integrations)
|
|
13
|
+
- [S1-002 — Module with no responsibilities](#s1-002--module-with-no-responsibilities)
|
|
14
|
+
- [S1-003 — Module without description](#s1-003--module-without-description)
|
|
15
|
+
- [S1-004 — Purely reactive module not documented](#s1-004--purely-reactive-module-not-documented)
|
|
12
16
|
- [S2 — Async event graph integrity](#s2--async-event-graph-integrity)
|
|
17
|
+
- [S2-001 — Event with no consumers](#s2-001--event-with-no-consumers)
|
|
18
|
+
- [S2-002 — Duplicate topic value](#s2-002--duplicate-topic-value)
|
|
19
|
+
- [S2-003 — Self-loop (module consuming its own event)](#s2-003--self-loop-module-consuming-its-own-event)
|
|
20
|
+
- [S2-004/S2-005 — Unbalanced producer/consumer roles](#s2-004s2-005--unbalanced-producerconsumer-roles)
|
|
21
|
+
- [S2-006 — Event name convention](#s2-006--event-name-convention)
|
|
22
|
+
- [S2-007 — Topic without configured prefix](#s2-007--topic-without-configured-prefix-info)
|
|
13
23
|
- [S3 — Sync call integrity](#s3--sync-call-integrity)
|
|
24
|
+
- [S3-001 — Sync call to module without endpoints](#s3-001--sync-call-to-module-without-endpoints)
|
|
25
|
+
- [S3-002 — Endpoint not declared in target module](#s3-002--endpoint-not-declared-in-target-module)
|
|
26
|
+
- [S3-003 — Bidirectional sync coupling](#s3-003--bidirectional-sync-coupling)
|
|
27
|
+
- [S3-004 — Too many outgoing sync dependencies](#s3-004--too-many-outgoing-sync-dependencies)
|
|
28
|
+
- [S3-005 — Module consulted synchronously but emits no events](#s3-005--module-consulted-synchronously-but-emits-no-events-info)
|
|
29
|
+
- [S3-006 — Duplicate port name across caller modules](#s3-006--duplicate-port-name-across-caller-modules)
|
|
14
30
|
- [S4 — Endpoint coherence](#s4--endpoint-coherence)
|
|
31
|
+
- [S4-001 — Duplicate route](#s4-001--duplicate-route)
|
|
32
|
+
- [S4-002 — PUT without GET for same resource](#s4-002--put-without-get-for-same-resource)
|
|
33
|
+
- [S4-003 — DELETE without description](#s4-003--delete-without-description)
|
|
34
|
+
- [S4-004 — Endpoint without description](#s4-004--endpoint-without-description-info)
|
|
35
|
+
- [S4-005 — POST without GET /{id}](#s4-005--post-without-get-id-info)
|
|
15
36
|
- [S5 — Global system coherence](#s5--global-system-coherence)
|
|
37
|
+
- [S5-001 — Messaging disabled but events declared](#s5-001--messaging-disabled-but-events-declared)
|
|
38
|
+
- [S5-002 — Success event without matching failure event](#s5-002--success-event-without-matching-failure-event)
|
|
39
|
+
- [S5-003 — Auth module without integrations](#s5-003--auth-module-without-integrations-info)
|
|
40
|
+
- [S5-004 — Isolated module](#s5-004--isolated-module-info)
|
|
16
41
|
5. [Domain evaluation criteria (C1–C4) — `--domain`](#5-domain-evaluation-criteria-c1c4----domain)
|
|
17
42
|
- [C1 — Kafka event contracts](#c1--kafka-event-contracts)
|
|
43
|
+
- [C1-001 — Produced event with no consumers in system.yaml](#c1-001--produced-event-with-no-consumers-in-systemyaml)
|
|
44
|
+
- [C1-002 — Listener references event no module produces](#c1-002--listener-references-event-no-module-produces)
|
|
45
|
+
- [C1-003 — Field in listener missing in producer event](#c1-003--field-in-listener-missing-in-producer-event)
|
|
46
|
+
- [C1-004 — Incompatible field types between producer and consumer](#c1-004--incompatible-field-types-between-producer-and-consumer)
|
|
47
|
+
- [C1-005 — Registered consumer has no listener or readModel.syncedBy](#c1-005--registered-consumer-has-no-listener-or-readmodelsyncedby-in-domainyaml)
|
|
48
|
+
- [C1-006 — listener.producer references wrong module](#c1-006--listenerproducer-references-wrong-module)
|
|
49
|
+
- [C1-007 — readModel field not covered by any UPSERT event](#c1-007--readmodel-field-not-covered-by-any-upsert-event)
|
|
18
50
|
- [C2 — Behavior gaps](#c2--behavior-gaps)
|
|
51
|
+
- [C2-001 — Transition with no endpoint or listener](#c2-001--transition-with-no-endpoint-or-listener)
|
|
52
|
+
- [C2-002 — Listener useCase without REST endpoint](#c2-002--c2-003--completeness-checks-info--warning)
|
|
53
|
+
- [C2-004 — Trigger references non-existent transition method](#c2-004--trigger-references-non-existent-transition-method)
|
|
54
|
+
- [C2-005 — Transition without a Domain Event](#c2-005--transition-without-a-domain-event-info)
|
|
55
|
+
- [C2-006 — useCase collision between endpoints and listeners](#c2-006--usecase-name-collision-between-endpoints-and-listeners)
|
|
56
|
+
- [C2-007 — FindAll useCase with incorrect plural form](#c2-007--findall-usecase-with-incorrect-plural-form)
|
|
57
|
+
- [C2-008 — Event with invalid lifecycle value](#c2-008--event-with-invalid-lifecycle-value)
|
|
58
|
+
- [C2-009 — Lifecycle value incompatible with entity configuration](#c2-009--lifecycle-value-incompatible-with-entity-configuration)
|
|
59
|
+
- [C2-010 — Lifecycle event field not found in root entity](#c2-010--lifecycle-event-field-not-found-in-root-entity)
|
|
60
|
+
- [C2-011 — Endpoint useCase not resolvable to any aggregate](#c2-011--endpoint-usecase-not-resolvable-to-any-aggregate)
|
|
19
61
|
- [C3 — Cross-reference integrity](#c3--cross-reference-integrity)
|
|
62
|
+
- [C3-001 — Cross-aggregate reference without a port or listener](#c3-001--cross-aggregate-reference-without-a-port-or-listener)
|
|
63
|
+
- [C3-002 — Port target missing domain.yaml](#c3-002--port-target-missing-domainyaml)
|
|
64
|
+
- [C3-003 — Port calls undeclared endpoint](#c3-003--port-calls-undeclared-endpoint)
|
|
65
|
+
- [C3-005 — Bidirectional sync coupling](#c3-005--bidirectional-sync-coupling)
|
|
66
|
+
- [C3-006 — system.yaml sync call without matching port](#c3-006--systemyaml-sync-call-without-matching-port)
|
|
67
|
+
- [C3-007 — Cross-module useCase name collision](#c3-007--cross-module-usecase-name-collision-bean-conflict)
|
|
20
68
|
- [C4 — Audit & traceability](#c4--audit--traceability)
|
|
69
|
+
- [C4-001 — Child entity deletable without audit trail](#c4-001--child-entity-deletable-without-audit-trail)
|
|
70
|
+
- [C4-002 — Critical root entity without audit](#c4-002--critical-root-entity-without-audit)
|
|
71
|
+
- [C4-003 — External data stored as unstructured String](#c4-003--external-data-stored-as-unstructured-string)
|
|
72
|
+
- [C4-004 — readOnly field not surfaced in any event](#c4-004--readonly-field-not-surfaced-in-any-event)
|
|
21
73
|
6. [Score calculation](#6-score-calculation)
|
|
22
74
|
7. [Report output](#7-report-output)
|
|
23
75
|
8. [Practical examples with real findings](#8-practical-examples-with-real-findings)
|
|
@@ -130,7 +182,7 @@ All fields are optional except `modules[].name`. The evaluator gracefully handle
|
|
|
130
182
|
|
|
131
183
|
These rules evaluate **only** `system.yaml`. For the domain-level criteria applied when using `--domain`, see [section 5](#5-domain-evaluation-criteria-c1c4----domain).
|
|
132
184
|
|
|
133
|
-
The evaluator runs **5 rule groups (S1–S5)** with a total of **
|
|
185
|
+
The evaluator runs **5 rule groups (S1–S5)** with a total of **26 rules** across three severity levels:
|
|
134
186
|
|
|
135
187
|
| Severity | Symbol | Affects score | Description |
|
|
136
188
|
|----------|--------|--------------|-------------|
|
|
@@ -149,7 +201,7 @@ Verifies that all modules declared in `modules[]` have defined responsibilities
|
|
|
149
201
|
| S1-001 | 🔴 error | Module referenced in `integrations` but not declared in `modules[]` |
|
|
150
202
|
| S1-002 | 🔴 error | Module with no responsibilities — no exposes, no events produced or consumed |
|
|
151
203
|
| S1-003 | 🟡 warning | Module without a `description` field |
|
|
152
|
-
| S1-004 |
|
|
204
|
+
| S1-004 | � info | Purely reactive module (only consumes events) not documented explicitly in its description |
|
|
153
205
|
|
|
154
206
|
#### S1-001 — Undeclared module referenced in integrations
|
|
155
207
|
|
|
@@ -271,6 +323,7 @@ Verifies that all synchronous dependencies declared in `integrations.sync` refer
|
|
|
271
323
|
| S3-003 | 🟡 warning | Bidirectional sync coupling — A calls B and B calls A |
|
|
272
324
|
| S3-004 | 🟡 warning | Module with more than 3 distinct outgoing sync dependencies |
|
|
273
325
|
| S3-005 | 🔵 info | Module consulted synchronously but emits no events when its state changes |
|
|
326
|
+
| S3-006 | 🔴 error | Same `port:` name declared by two or more different caller modules — causes `ConflictingBeanDefinitionException` |
|
|
274
327
|
|
|
275
328
|
#### S3-001 — Sync call to module without endpoints
|
|
276
329
|
|
|
@@ -326,6 +379,36 @@ When other modules depend synchronously on a module but that module never publis
|
|
|
326
379
|
|
|
327
380
|
**Message:** `[S3-005] Módulo 'movies' es consultado síncronamente pero no emite ningún evento cuando su estado cambia`
|
|
328
381
|
|
|
382
|
+
#### S3-006 — Duplicate port name across caller modules
|
|
383
|
+
|
|
384
|
+
When two or more modules declare a sync integration with **the same `port:` value**, each module generates a `{PortName}FeignAdapter.java` bean. Since `@Component` registers beans by class name without a module namespace, Spring throws `ConflictingBeanDefinitionException` at startup.
|
|
385
|
+
|
|
386
|
+
```yaml
|
|
387
|
+
# ❌ ERROR — both modules use the same port name 'CustomerService'
|
|
388
|
+
sync:
|
|
389
|
+
- caller: orders
|
|
390
|
+
calls: customers
|
|
391
|
+
port: CustomerService # generates CustomerServiceFeignAdapter in orders
|
|
392
|
+
- caller: deliveries
|
|
393
|
+
calls: customers
|
|
394
|
+
port: CustomerService # generates CustomerServiceFeignAdapter in deliveries → collision
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Message:** `[S3-006] Port 'CustomerService' es usado por módulos distintos (orders, deliveries). Esto causa ConflictingBeanDefinitionException en Spring. Cada módulo debe usar un nombre propio: ej. OrdersCustomerService, DeliveriesCustomerService`
|
|
398
|
+
|
|
399
|
+
**Fix:** Give each module a unique, context-specific port name:
|
|
400
|
+
|
|
401
|
+
```yaml
|
|
402
|
+
sync:
|
|
403
|
+
- caller: orders
|
|
404
|
+
calls: customers
|
|
405
|
+
port: OrderCustomerService # ✅ generates OrderCustomerServiceFeignAdapter in orders
|
|
406
|
+
|
|
407
|
+
- caller: deliveries
|
|
408
|
+
calls: customers
|
|
409
|
+
port: DeliveryCustomerService # ✅ generates DeliveryCustomerServiceFeignAdapter in deliveries
|
|
410
|
+
```
|
|
411
|
+
|
|
329
412
|
---
|
|
330
413
|
|
|
331
414
|
### S4 — Endpoint coherence
|
|
@@ -452,7 +535,7 @@ When `--domain` is passed, the evaluator additionally loads a `domain.yaml` file
|
|
|
452
535
|
- Modules without a `domain.yaml` are silently skipped — their rules will not fire
|
|
453
536
|
- Domain findings appear in the **Dominio** tab of the HTML report
|
|
454
537
|
|
|
455
|
-
Domain rules are organized in **4 rule groups (C1–C4)** with a total of **
|
|
538
|
+
Domain rules are organized in **4 rule groups (C1–C4)** with a total of **29 rules**:
|
|
456
539
|
|
|
457
540
|
| Severity | Symbol | Affects score | Description |
|
|
458
541
|
|----------|--------|--------------|-------------|
|
|
@@ -468,12 +551,13 @@ Verifies that the producer → consumer graph is coherent at the code level: eve
|
|
|
468
551
|
|
|
469
552
|
| Rule | Severity | Description |
|
|
470
553
|
|------|----------|-------------|
|
|
471
|
-
| C1-001 |
|
|
554
|
+
| C1-001 | � info | Domain event produced by a module but has no consumers registered in `system.yaml` |
|
|
472
555
|
| C1-002 | 🔴 error | `listeners[]` references an event that no domain module produces |
|
|
473
556
|
| C1-003 | 🔴 error | Field in `listener.fields` does not exist in the producer event |
|
|
474
557
|
| C1-004 | 🔴 error | Field exists in both producer and consumer but with incompatible types |
|
|
475
|
-
| C1-005 | 🔴 error | `system.yaml` registers a module as consumer but that module has no matching `listener` |
|
|
558
|
+
| C1-005 | 🔴 error | `system.yaml` registers a module as consumer but that module has no matching `listener` or `readModels[].syncedBy` |
|
|
476
559
|
| C1-006 | 🔴 error | `listener.producer` references the wrong producer module |
|
|
560
|
+
| C1-007 | 🟡 warning | Field declared in a `readModels[]` entry is not covered by any UPSERT event from the source module |
|
|
477
561
|
|
|
478
562
|
#### C1-001 — Produced event with no consumers in system.yaml
|
|
479
563
|
|
|
@@ -482,7 +566,7 @@ Verifies that the producer → consumer graph is coherent at the code level: eve
|
|
|
482
566
|
aggregates:
|
|
483
567
|
- name: Order
|
|
484
568
|
events:
|
|
485
|
-
- name: OrderConfirmed #
|
|
569
|
+
- name: OrderConfirmed # 🔵 INFO — not registered in system.yaml
|
|
486
570
|
fields: [...]
|
|
487
571
|
```
|
|
488
572
|
|
|
@@ -534,7 +618,9 @@ listeners:
|
|
|
534
618
|
|
|
535
619
|
**Fix:** Align the field type in the listener with the type declared in the producer event.
|
|
536
620
|
|
|
537
|
-
#### C1-005 — Registered consumer has no listener in domain.yaml
|
|
621
|
+
#### C1-005 — Registered consumer has no listener or readModel.syncedBy in domain.yaml
|
|
622
|
+
|
|
623
|
+
For `useCase:` consumers, the module must have a matching `listeners[]` entry:
|
|
538
624
|
|
|
539
625
|
```yaml
|
|
540
626
|
# system.yaml registers notifications as consumer:
|
|
@@ -548,6 +634,20 @@ consumers:
|
|
|
548
634
|
|
|
549
635
|
**Fix:** Add a `listeners[]` entry for `OrderCreatedEvent` in `notifications/domain.yaml`.
|
|
550
636
|
|
|
637
|
+
For `readModel:` consumers, the module must have a matching `readModels[].syncedBy[]` entry:
|
|
638
|
+
|
|
639
|
+
```yaml
|
|
640
|
+
# system.yaml registers orders as readModel consumer:
|
|
641
|
+
consumers:
|
|
642
|
+
- module: orders
|
|
643
|
+
readModel: ProductReadModel
|
|
644
|
+
# orders/domain.yaml must have readModels[].syncedBy[].event matching the event
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
**Message:** `[C1-005] system.yaml registra 'orders' como consumidor readModel de 'ProductCreatedEvent' pero el módulo no tiene readModels[].syncedBy con ese evento`
|
|
648
|
+
|
|
649
|
+
**Fix:** Add a `readModels[]` entry with a `syncedBy[]` entry for the event in the consumer module's `domain.yaml`.
|
|
650
|
+
|
|
551
651
|
#### C1-006 — listener.producer references wrong module
|
|
552
652
|
|
|
553
653
|
```yaml
|
|
@@ -560,6 +660,35 @@ listeners:
|
|
|
560
660
|
|
|
561
661
|
**Fix:** Update `producer:` to match the actual producing module.
|
|
562
662
|
|
|
663
|
+
#### C1-007 — readModel field not covered by any UPSERT event
|
|
664
|
+
|
|
665
|
+
When a `readModels[]` entry declares a field that is not included in any event with `action: UPSERT` from the source module's `syncedBy`, that field will always be `null` after synchronization because no event ever carries a value for it.
|
|
666
|
+
|
|
667
|
+
```yaml
|
|
668
|
+
# orders/domain.yaml
|
|
669
|
+
readModels:
|
|
670
|
+
- name: ProductReadModel
|
|
671
|
+
source:
|
|
672
|
+
module: products
|
|
673
|
+
tableName: rm_products
|
|
674
|
+
fields:
|
|
675
|
+
- name: id
|
|
676
|
+
type: String
|
|
677
|
+
- name: name
|
|
678
|
+
type: String
|
|
679
|
+
- name: stock # ⚠️ WARNING — no UPSERT event from 'products' includes 'stock'
|
|
680
|
+
type: Integer
|
|
681
|
+
syncedBy:
|
|
682
|
+
- event: ProductCreatedEvent
|
|
683
|
+
action: UPSERT
|
|
684
|
+
- event: ProductUpdatedEvent
|
|
685
|
+
action: UPSERT
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
**Message:** `[C1-007] ReadModel 'ProductReadModel' tiene campo 'stock' que no aparece en ningún evento UPSERT de syncedBy`
|
|
689
|
+
|
|
690
|
+
**Fix:** Add `stock` to the `fields[]` of `ProductCreatedEvent` and `ProductUpdatedEvent` in `products/domain.yaml`, or remove `stock` from the readModel if it is not needed.
|
|
691
|
+
|
|
563
692
|
---
|
|
564
693
|
|
|
565
694
|
### C2 — Behavior gaps
|
|
@@ -573,6 +702,12 @@ Verifies that every transition method and every use case has a traceable activat
|
|
|
573
702
|
| C2-003 | 🟡 warning | Value in a `*Type` enum with no traceable Kafka event as origin |
|
|
574
703
|
| C2-004 | 🔴 error | Event `triggers[]` references a transition method that does not exist |
|
|
575
704
|
| C2-005 | 🔵 info | Transition method without any associated Domain Event (no `triggers`) |
|
|
705
|
+
| C2-006 | 🔴 error | Same `useCase` name declared in both `endpoints:` and `listeners:` in the same module |
|
|
706
|
+
| C2-007 | 🔴 error | `FindAll` use case name uses incorrect plural form — generator will produce a scaffold instead of full implementation |
|
|
707
|
+
| C2-008 | 🔴 error | Event declares `lifecycle:` with an invalid value (not `create`, `update`, `delete`, or `softDelete`) |
|
|
708
|
+
| C2-009 | 🟡 warning | `lifecycle: softDelete` on entity without `hasSoftDelete: true`, or `lifecycle: delete` on entity with `hasSoftDelete: true` |
|
|
709
|
+
| C2-010 | 🔴 error | Field declared in a `lifecycle` event does not exist in the root entity |
|
|
710
|
+
| C2-011 | 🔴 error | Endpoint `useCase` cannot be resolved to any aggregate in the module — generator will assign wrong types |
|
|
576
711
|
|
|
577
712
|
> **Note on C2-001:** Silenced automatically when the transition method already has an event `trigger` declared — the trigger itself is sufficient design evidence.
|
|
578
713
|
|
|
@@ -615,6 +750,151 @@ events:
|
|
|
615
750
|
- `[C2-002]` — Listener `useCase` has no REST equivalent in the same module (info; acceptable when the module is purely event-driven)
|
|
616
751
|
- `[C2-003]` — Value in a `*Type` enum (e.g., `PaymentType.BANK_TRANSFER`) has no Kafka event whose name or fields overlap — suggests the value originates from an external trigger not yet documented
|
|
617
752
|
|
|
753
|
+
#### C2-006 — useCase name collision between endpoints and listeners
|
|
754
|
+
|
|
755
|
+
When a `useCase` name appears in both `endpoints[].operations[]` and `listeners[]` in the same module, both generate a `{UseCase}Command.java` file. The endpoint version silently overwrites the listener version at generation time, leaving the listener with broken code.
|
|
756
|
+
|
|
757
|
+
```yaml
|
|
758
|
+
# ❌ ERROR — 'CreateOrder' is declared in both endpoints and listeners
|
|
759
|
+
endpoints:
|
|
760
|
+
versions:
|
|
761
|
+
- version: v1
|
|
762
|
+
operations:
|
|
763
|
+
- useCase: CreateOrder # → CreateOrderCommand.java (endpoint version)
|
|
764
|
+
method: POST
|
|
765
|
+
path: /orders
|
|
766
|
+
|
|
767
|
+
listeners:
|
|
768
|
+
- event: CartCheckedOutEvent
|
|
769
|
+
useCase: CreateOrder # ❌ also generates CreateOrderCommand.java → overwritten
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
**Message:** `[C2-006] UseCase 'CreateOrder' está declarado en endpoints: y en listeners: (evento 'CartCheckedOutEvent')`
|
|
773
|
+
|
|
774
|
+
**Fix:** Rename the listener `useCase` to reflect its bounded context:
|
|
775
|
+
|
|
776
|
+
```yaml
|
|
777
|
+
listeners:
|
|
778
|
+
- event: CartCheckedOutEvent
|
|
779
|
+
useCase: InitializeOrder # ✅ distinct name; no collision
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
#### C2-007 — FindAll useCase with incorrect plural form
|
|
783
|
+
|
|
784
|
+
The generator recognizes `FindAll{Plural}` as the standard list query and produces the full paginated implementation. If the plural is wrong (e.g., a naive `s` suffix on an irregular noun), the generator creates an empty scaffold instead.
|
|
785
|
+
|
|
786
|
+
```yaml
|
|
787
|
+
# ❌ ERROR — 'FindAllCategorys' is incorrect English plural
|
|
788
|
+
endpoints:
|
|
789
|
+
versions:
|
|
790
|
+
- version: v1
|
|
791
|
+
operations:
|
|
792
|
+
- useCase: FindAllCategorys # ❌ should be FindAllCategories
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
**Message:** `[C2-007] UseCase 'FindAllCategorys' debería ser 'FindAllCategories' (plural correcto de 'Category')`
|
|
796
|
+
|
|
797
|
+
**Fix:** Use the correct English plural:
|
|
798
|
+
|
|
799
|
+
```yaml
|
|
800
|
+
- useCase: FindAllCategories # ✅ correct plural → generates full implementation
|
|
801
|
+
- useCase: FindAllInventories # ✅ correct for 'Inventory'
|
|
802
|
+
- useCase: FindAllPolicies # ✅ correct for 'Policy'
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
#### C2-008 — Event with invalid lifecycle value
|
|
806
|
+
|
|
807
|
+
The `lifecycle:` property on a domain event only accepts `create`, `update`, `delete`, or `softDelete`. Any other value is silently ignored by the generator, leaving the event without auto-wiring.
|
|
808
|
+
|
|
809
|
+
```yaml
|
|
810
|
+
events:
|
|
811
|
+
- name: ProductArchivedEvent
|
|
812
|
+
lifecycle: archive # ❌ ERROR — not a valid lifecycle value
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
**Message:** `[C2-008] Evento 'ProductArchivedEvent' tiene lifecycle: 'archive' que no es un valor válido`
|
|
816
|
+
|
|
817
|
+
**Fix:** Use one of the valid values: `create`, `update`, `delete`, `softDelete`. If `archive` is a business transition, use `transitions:` with `triggers:` instead.
|
|
818
|
+
|
|
819
|
+
#### C2-009 — Lifecycle value incompatible with entity configuration
|
|
820
|
+
|
|
821
|
+
`lifecycle: softDelete` requires the root entity to have `hasSoftDelete: true`, and `lifecycle: delete` requires `hasSoftDelete` to be absent or `false`. Mixing these configurations generates code that either won't compile or creates an incorrect deletion mechanism.
|
|
822
|
+
|
|
823
|
+
```yaml
|
|
824
|
+
# ⚠️ WARNING — entity has no soft delete but event declares softDelete lifecycle
|
|
825
|
+
entities:
|
|
826
|
+
- name: Product
|
|
827
|
+
isRoot: true
|
|
828
|
+
# hasSoftDelete: true is missing
|
|
829
|
+
events:
|
|
830
|
+
- name: ProductDeactivatedEvent
|
|
831
|
+
lifecycle: softDelete # ⚠️ WARNING — entity doesn't support soft delete
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
**Messages:**
|
|
835
|
+
- `[C2-009] Evento 'ProductDeactivatedEvent' tiene lifecycle: 'softDelete' pero la entidad raíz 'Product' no tiene hasSoftDelete: true`
|
|
836
|
+
- `[C2-009] Evento 'ProductDeletedEvent' tiene lifecycle: 'delete' pero la entidad raíz 'Product' tiene hasSoftDelete: true`
|
|
837
|
+
|
|
838
|
+
**Fix:** Align the lifecycle value with the entity configuration:
|
|
839
|
+
- Add `hasSoftDelete: true` to the entity, **or** change `lifecycle: softDelete` to `lifecycle: delete`
|
|
840
|
+
|
|
841
|
+
#### C2-010 — Lifecycle event field not found in root entity
|
|
842
|
+
|
|
843
|
+
Fields declared in a `lifecycle` event must exist on the root entity so the generator can resolve them automatically. Two exceptions apply: `{entityName}Id` (mapped to `aggregateId`) and any field ending in `At` with type `LocalDateTime` (auto-resolved to `LocalDateTime.now()`).
|
|
844
|
+
|
|
845
|
+
```yaml
|
|
846
|
+
entities:
|
|
847
|
+
- name: Product
|
|
848
|
+
isRoot: true
|
|
849
|
+
fields:
|
|
850
|
+
- name: id
|
|
851
|
+
type: String
|
|
852
|
+
- name: name
|
|
853
|
+
type: String
|
|
854
|
+
|
|
855
|
+
events:
|
|
856
|
+
- name: ProductCreatedEvent
|
|
857
|
+
lifecycle: create
|
|
858
|
+
fields:
|
|
859
|
+
- name: productId # ✅ exception — maps to aggregateId
|
|
860
|
+
type: String
|
|
861
|
+
- name: name # ✅ exists in entity
|
|
862
|
+
type: String
|
|
863
|
+
- name: createdAt # ✅ exception — *At + LocalDateTime auto-resolved
|
|
864
|
+
type: LocalDateTime
|
|
865
|
+
- name: categoryCode # ❌ ERROR — does not exist in Product entity
|
|
866
|
+
type: String
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
**Message:** `[C2-010] Evento 'ProductCreatedEvent' (lifecycle: create) tiene campo 'categoryCode' que no existe en la entidad raíz 'Product'`
|
|
870
|
+
|
|
871
|
+
**Fix:** Remove `categoryCode` from the event fields, or add it as a field to the `Product` entity.
|
|
872
|
+
|
|
873
|
+
#### C2-011 — Endpoint useCase not resolvable to any aggregate
|
|
874
|
+
|
|
875
|
+
The generator resolves each `useCase` name to an aggregate to determine which `ResponseDto` and `CommandHandler` types to use. If the name cannot be matched via standard patterns (`Create{X}`, `Update{X}`, `Delete{X}`, `Get{X}`, `FindAll{Plural(X)}`) or fuzzy matching, the generator assigns the first aggregate in the module by default and produces code with incorrect types that won't compile.
|
|
876
|
+
|
|
877
|
+
```yaml
|
|
878
|
+
aggregates:
|
|
879
|
+
- name: Quote
|
|
880
|
+
|
|
881
|
+
endpoints:
|
|
882
|
+
versions:
|
|
883
|
+
- version: v1
|
|
884
|
+
operations:
|
|
885
|
+
- useCase: CalculatePremium # ⚠️ — 'Premium' doesn't match aggregate 'Quote'
|
|
886
|
+
- useCase: IssuePolicy # ❌ ERROR — 'Policy' is not an aggregate in this module
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
**Message:** `[C2-011] UseCase 'IssuePolicy' no se resuelve a ningún agregado del módulo — se asignará al primero ('Quote') y generará código con tipos incorrectos`
|
|
890
|
+
|
|
891
|
+
**Fix:** Ensure the useCase name contains the aggregate name (or a recognizable part of it):
|
|
892
|
+
|
|
893
|
+
```yaml
|
|
894
|
+
- useCase: CalculateQuotePremium # ✅ contains 'Quote' — resolves correctly
|
|
895
|
+
- useCase: CompleteQuote # ✅ contains 'Quote'
|
|
896
|
+
```
|
|
897
|
+
|
|
618
898
|
---
|
|
619
899
|
|
|
620
900
|
### C3 — Cross-reference integrity
|
|
@@ -623,7 +903,7 @@ Verifies that all declared dependencies between modules are consistent: cross-ag
|
|
|
623
903
|
|
|
624
904
|
| Rule | Severity | Description |
|
|
625
905
|
|------|----------|-------------|
|
|
626
|
-
| C3-001 |
|
|
906
|
+
| C3-001 | � info | Field with `reference.module=X` but no port or listener connecting to X |
|
|
627
907
|
| C3-002 | 🔴 error | `port.target` refers to an internal module with no `domain.yaml` loaded |
|
|
628
908
|
| C3-003 | 🟡 warning | Port calls an endpoint not declared in the target module |
|
|
629
909
|
| C3-004 | 🟡 warning | Sync dependency on a module that emits no Kafka events |
|
|
@@ -642,7 +922,7 @@ fields:
|
|
|
642
922
|
type: String
|
|
643
923
|
reference:
|
|
644
924
|
aggregate: Customer
|
|
645
|
-
module: customers #
|
|
925
|
+
module: customers # 🔵 INFO — no port or listener to 'customers'
|
|
646
926
|
```
|
|
647
927
|
|
|
648
928
|
**Message:** `[C3-001] Campo 'customerId' referencia módulo 'customers' pero no hay port ni listener que conecte con ese módulo`
|
|
@@ -874,7 +1154,7 @@ Running `eva evaluate system` on a cinema booking `system.yaml` with 7 modules a
|
|
|
874
1154
|
|
|
875
1155
|
| Rule | Severity | Finding | Recommendation |
|
|
876
1156
|
|------|----------|---------|----------------|
|
|
877
|
-
| S1-004 |
|
|
1157
|
+
| S1-004 | � | `notifications` is purely reactive but description doesn't say so | Add "consumes events" to its description |
|
|
878
1158
|
| S2-005 | 🟡 | `customers` consumes events but produces none | Intentional — accumulates loyalty points. Acceptable. |
|
|
879
1159
|
| S2-005 | 🟡 | `notifications` consumes events but produces none | Intentional — pure notification sink. Acceptable. |
|
|
880
1160
|
| S2-007 | 🔵 | 9 topics don't include the `cinema` prefix | Rename topics to `cinema.RESERVATION_CREATED`, etc. |
|