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,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
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
|
|
|
@@ -576,6 +660,35 @@ listeners:
|
|
|
576
660
|
|
|
577
661
|
**Fix:** Update `producer:` to match the actual producing module.
|
|
578
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
|
+
|
|
579
692
|
---
|
|
580
693
|
|
|
581
694
|
### C2 — Behavior gaps
|
|
@@ -589,6 +702,12 @@ Verifies that every transition method and every use case has a traceable activat
|
|
|
589
702
|
| C2-003 | 🟡 warning | Value in a `*Type` enum with no traceable Kafka event as origin |
|
|
590
703
|
| C2-004 | 🔴 error | Event `triggers[]` references a transition method that does not exist |
|
|
591
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 |
|
|
592
711
|
|
|
593
712
|
> **Note on C2-001:** Silenced automatically when the transition method already has an event `trigger` declared — the trigger itself is sufficient design evidence.
|
|
594
713
|
|
|
@@ -631,6 +750,151 @@ events:
|
|
|
631
750
|
- `[C2-002]` — Listener `useCase` has no REST equivalent in the same module (info; acceptable when the module is purely event-driven)
|
|
632
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
|
|
633
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
|
+
|
|
634
898
|
---
|
|
635
899
|
|
|
636
900
|
### C3 — Cross-reference integrity
|
|
@@ -639,7 +903,7 @@ Verifies that all declared dependencies between modules are consistent: cross-ag
|
|
|
639
903
|
|
|
640
904
|
| Rule | Severity | Description |
|
|
641
905
|
|------|----------|-------------|
|
|
642
|
-
| C3-001 |
|
|
906
|
+
| C3-001 | � info | Field with `reference.module=X` but no port or listener connecting to X |
|
|
643
907
|
| C3-002 | 🔴 error | `port.target` refers to an internal module with no `domain.yaml` loaded |
|
|
644
908
|
| C3-003 | 🟡 warning | Port calls an endpoint not declared in the target module |
|
|
645
909
|
| C3-004 | 🟡 warning | Sync dependency on a module that emits no Kafka events |
|
|
@@ -658,7 +922,7 @@ fields:
|
|
|
658
922
|
type: String
|
|
659
923
|
reference:
|
|
660
924
|
aggregate: Customer
|
|
661
|
-
module: customers #
|
|
925
|
+
module: customers # 🔵 INFO — no port or listener to 'customers'
|
|
662
926
|
```
|
|
663
927
|
|
|
664
928
|
**Message:** `[C3-001] Campo 'customerId' referencia módulo 'customers' pero no hay port ni listener que conecte con ese módulo`
|
|
@@ -890,7 +1154,7 @@ Running `eva evaluate system` on a cinema booking `system.yaml` with 7 modules a
|
|
|
890
1154
|
|
|
891
1155
|
| Rule | Severity | Finding | Recommendation |
|
|
892
1156
|
|------|----------|---------|----------------|
|
|
893
|
-
| S1-004 |
|
|
1157
|
+
| S1-004 | � | `notifications` is purely reactive but description doesn't say so | Add "consumes events" to its description |
|
|
894
1158
|
| S2-005 | 🟡 | `customers` consumes events but produces none | Intentional — accumulates loyalty points. Acceptable. |
|
|
895
1159
|
| S2-005 | 🟡 | `notifications` consumes events but produces none | Intentional — pure notification sink. Acceptable. |
|
|
896
1160
|
| S2-007 | 🔵 | 9 topics don't include the `cinema` prefix | Rename topics to `cinema.RESERVATION_CREATED`, etc. |
|