eva4j 1.0.13 → 1.0.14
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 +51 -9
- package/DOMAIN_YAML_GUIDE.md +150 -0
- package/bin/eva4j.js +31 -1
- package/design-system.md +797 -0
- package/docs/commands/EVALUATE_SYSTEM.md +542 -0
- package/docs/commands/GENERATE_ENTITIES.md +196 -0
- package/docs/commands/INDEX.md +10 -1
- package/examples/domain-endpoints-relations.yaml +353 -0
- package/examples/domain-endpoints-versioned.yaml +144 -0
- package/examples/domain-endpoints.yaml +135 -0
- package/examples/system.yaml +289 -0
- package/package.json +1 -1
- package/src/commands/create.js +6 -3
- package/src/commands/evaluate-system.js +384 -0
- package/src/commands/generate-entities.js +677 -14
- package/src/commands/generate-kafka-event.js +59 -5
- package/src/commands/generate-system.js +243 -0
- package/src/generators/base-generator.js +9 -1
- package/src/utils/naming.js +3 -2
- package/src/utils/system-validator.js +314 -0
- package/src/utils/yaml-to-entity.js +31 -2
- package/templates/aggregate/AggregateRepository.java.ejs +5 -0
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +9 -0
- package/templates/aggregate/DomainEventHandler.java.ejs +24 -20
- package/templates/aggregate/JpaRepository.java.ejs +5 -0
- package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +1103 -0
- package/templates/base/root/skill-build-domain-yaml.ejs +292 -0
- package/templates/base/root/skill-build-system-yaml.ejs +252 -0
- package/templates/base/root/system.yaml.ejs +97 -0
- package/templates/crud/EndpointsController.java.ejs +178 -0
- package/templates/crud/FindByQuery.java.ejs +17 -0
- package/templates/crud/FindByQueryHandler.java.ejs +57 -0
- package/templates/crud/ScaffoldCommand.java.ejs +12 -0
- package/templates/crud/ScaffoldCommandHandler.java.ejs +43 -0
- package/templates/crud/ScaffoldQuery.java.ejs +12 -0
- package/templates/crud/ScaffoldQueryHandler.java.ejs +40 -0
- package/templates/crud/SubEntityAddCommand.java.ejs +17 -0
- package/templates/crud/SubEntityAddCommandHandler.java.ejs +43 -0
- package/templates/crud/SubEntityRemoveCommand.java.ejs +9 -0
- package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +42 -0
- package/templates/crud/TransitionCommand.java.ejs +9 -0
- package/templates/crud/TransitionCommandHandler.java.ejs +39 -0
- package/templates/evaluate/report.html.ejs +971 -0
- package/templates/kafka-event/Event.java.ejs +7 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
# Command `evaluate system`
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Description and purpose](#1-description-and-purpose)
|
|
8
|
+
2. [Syntax and options](#2-syntax-and-options)
|
|
9
|
+
3. [system.yaml structure required](#3-systemyaml-structure-required)
|
|
10
|
+
4. [Evaluation criteria](#4-evaluation-criteria)
|
|
11
|
+
- [Check 1 — Referential integrity](#check-1--referential-integrity)
|
|
12
|
+
- [Check 2 — Sync cycle detection](#check-2--sync-cycle-detection)
|
|
13
|
+
- [Check 3 — Module role analysis](#check-3--module-role-analysis)
|
|
14
|
+
- [Check 4 — Behavior gaps](#check-4--behavior-gaps)
|
|
15
|
+
- [Check 5 — Coupling patterns](#check-5--coupling-patterns)
|
|
16
|
+
5. [Score calculation](#5-score-calculation)
|
|
17
|
+
6. [Report output](#6-report-output)
|
|
18
|
+
7. [Practical examples with real findings](#7-practical-examples-with-real-findings)
|
|
19
|
+
8. [Common errors and how to fix them](#8-common-errors-and-how-to-fix-them)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. Description and purpose
|
|
24
|
+
|
|
25
|
+
`evaluate system` statically analyzes a `system.yaml` file to detect architectural problems in a microservices design **before writing a single line of Java code**.
|
|
26
|
+
|
|
27
|
+
The command is domain-agnostic: it works for any system described in `system.yaml`, whether it's a cinema booking platform, an e-commerce system, a fintech application, or any other domain.
|
|
28
|
+
|
|
29
|
+
**What it produces:**
|
|
30
|
+
|
|
31
|
+
- A **quality score** (0–100%) based on checks passed vs. total
|
|
32
|
+
- A list of **critical errors** (broken references, hard cycles)
|
|
33
|
+
- A list of **warnings** (potential design problems that aren't necessarily wrong)
|
|
34
|
+
- A list of **passed validations** (proof that good practices are in place)
|
|
35
|
+
- An **interactive HTML report** with three tabs:
|
|
36
|
+
- **Validation** — errors, warnings, score
|
|
37
|
+
- **Flow Simulator** — step-by-step visualization of each async event flow
|
|
38
|
+
- **Architecture** — per-module dependency explorer + sync dependency map + Kafka topic map + interactive network diagram
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 2. Syntax and options
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
eva evaluate system
|
|
46
|
+
eva evaluate system --port 8080 # serve the report on a custom port (default: 3000)
|
|
47
|
+
eva evaluate system --output ./report.html # write HTML to a custom path
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Parameters
|
|
51
|
+
|
|
52
|
+
None. The command always reads `system.yaml` from the current working directory.
|
|
53
|
+
|
|
54
|
+
### Options
|
|
55
|
+
|
|
56
|
+
| Option | Default | Description |
|
|
57
|
+
|--------|---------|-------------|
|
|
58
|
+
| `--port <n>` | `3000` | Port for the local HTTP preview server |
|
|
59
|
+
| `--output <path>` | `./system-report.html` | Where to write the generated HTML file |
|
|
60
|
+
|
|
61
|
+
### Requirements
|
|
62
|
+
|
|
63
|
+
- Must be run from a directory containing a `system.yaml` file
|
|
64
|
+
- No eva4j project scaffold is required — `system.yaml` can be a standalone design file
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 3. system.yaml structure required
|
|
69
|
+
|
|
70
|
+
The minimal structure the evaluator expects:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
system:
|
|
74
|
+
name: my-system # used as report title
|
|
75
|
+
|
|
76
|
+
modules:
|
|
77
|
+
- name: orders # unique module identifier (camelCase or kebab-case)
|
|
78
|
+
description: "..." # shown in the report
|
|
79
|
+
exposes: # REST endpoints this module offers
|
|
80
|
+
- method: POST
|
|
81
|
+
path: /orders
|
|
82
|
+
useCase: CreateOrder
|
|
83
|
+
description: "..."
|
|
84
|
+
- method: GET
|
|
85
|
+
path: /orders/{id}
|
|
86
|
+
useCase: GetOrder
|
|
87
|
+
description: "..."
|
|
88
|
+
|
|
89
|
+
integrations:
|
|
90
|
+
async: # async event flows (Kafka, RabbitMQ, etc.)
|
|
91
|
+
- event: OrderCreatedEvent
|
|
92
|
+
producer: orders
|
|
93
|
+
topic: ORDER_CREATED
|
|
94
|
+
consumers:
|
|
95
|
+
- module: payments
|
|
96
|
+
- module: notifications
|
|
97
|
+
|
|
98
|
+
sync: # synchronous HTTP calls between modules
|
|
99
|
+
- caller: payments
|
|
100
|
+
calls: orders
|
|
101
|
+
port: OrderService # Java interface name that will be generated
|
|
102
|
+
using:
|
|
103
|
+
- GET /orders/{id}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
All fields are optional except `modules[].name`. The evaluator gracefully handles missing sections (no async events, no sync calls, etc.).
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 4. Evaluation criteria
|
|
111
|
+
|
|
112
|
+
The evaluator runs **5 independent checks** against the parsed YAML. Each check produces errors, warnings, or passing validations.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### Check 1 — Referential integrity
|
|
117
|
+
|
|
118
|
+
**What it validates:** Every name referenced anywhere in `integrations` points to a module or endpoint actually declared in `modules[]`.
|
|
119
|
+
|
|
120
|
+
#### 1a. Event producers
|
|
121
|
+
|
|
122
|
+
Every `integrations.async[].producer` must be a module declared in `modules[]`.
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
# ✅ PASSES — 'orders' is declared in modules[]
|
|
126
|
+
- event: OrderCreatedEvent
|
|
127
|
+
producer: orders
|
|
128
|
+
|
|
129
|
+
# ❌ ERROR — 'billing' is not declared in modules[]
|
|
130
|
+
- event: InvoiceCreatedEvent
|
|
131
|
+
producer: billing
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Error message:**
|
|
135
|
+
```
|
|
136
|
+
Integridad referencial: el productor 'billing' del evento 'InvoiceCreatedEvent'
|
|
137
|
+
no está declarado en modules[]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### 1b. Event consumers
|
|
141
|
+
|
|
142
|
+
Every module listed in `integrations.async[].consumers` must be declared in `modules[]`.
|
|
143
|
+
|
|
144
|
+
```yaml
|
|
145
|
+
consumers:
|
|
146
|
+
- module: payments # ✅ must exist in modules[]
|
|
147
|
+
- module: ghost-service # ❌ ERROR if not declared
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Error message:**
|
|
151
|
+
```
|
|
152
|
+
Integridad referencial: el consumidor 'ghost-service' del evento 'OrderCreatedEvent'
|
|
153
|
+
no está declarado en modules[]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### 1c. Sync caller / callee existence
|
|
157
|
+
|
|
158
|
+
Both `caller` and `calls` in every `integrations.sync[]` entry must be declared modules.
|
|
159
|
+
|
|
160
|
+
#### 1d. Sync endpoints exist in target module's `exposes[]`
|
|
161
|
+
|
|
162
|
+
Every endpoint listed in `sync[].using` must match an endpoint declared in the target module's `exposes[]`. Matching uses path templating awareness (`/orders/{id}` matches `GET /orders/{id}`).
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
# payments calls orders
|
|
166
|
+
sync:
|
|
167
|
+
- caller: payments
|
|
168
|
+
calls: orders
|
|
169
|
+
port: OrderService
|
|
170
|
+
using:
|
|
171
|
+
- GET /orders/{id} # ✅ must appear in orders.exposes[]
|
|
172
|
+
- GET /orders/{id}/total # ❌ ERROR if not declared in orders.exposes[]
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Error message:**
|
|
176
|
+
```
|
|
177
|
+
Integridad referencial: el endpoint 'GET /orders/{id}/total' usado por 'payments'
|
|
178
|
+
no está declarado en el exposes[] de 'orders'
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Why this matters:** Undeclared endpoints indicate either a missing API design or a stale reference — both are common sources of integration bugs discovered late in development.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### Check 2 — Sync cycle detection
|
|
186
|
+
|
|
187
|
+
**What it validates:** The directed graph of synchronous calls contains no cycles.
|
|
188
|
+
|
|
189
|
+
The evaluator builds a directed graph where each node is a module and each edge `A → B` means "A calls B synchronously". It then runs DFS to detect:
|
|
190
|
+
|
|
191
|
+
1. **Direct bidirectional coupling** (`A → B` and `B → A`) — immediate deadlock risk
|
|
192
|
+
2. **Transitive cycles** (`A → B → C → A`) — detected via full DFS path traversal
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
# ❌ CRITICAL — direct bidirectional sync coupling
|
|
196
|
+
sync:
|
|
197
|
+
- caller: orders
|
|
198
|
+
calls: inventory
|
|
199
|
+
- caller: inventory
|
|
200
|
+
calls: orders # ← creates orders ↔ inventory cycle
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Error message:**
|
|
204
|
+
```
|
|
205
|
+
Acoplamiento circular síncrono: 'orders' e 'inventory' se llaman mutuamente
|
|
206
|
+
de forma síncrona. Esto puede causar deadlocks.
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```yaml
|
|
210
|
+
# ❌ CRITICAL — transitive cycle
|
|
211
|
+
sync:
|
|
212
|
+
- caller: A
|
|
213
|
+
calls: B
|
|
214
|
+
- caller: B
|
|
215
|
+
calls: C
|
|
216
|
+
- caller: C
|
|
217
|
+
calls: A # ← A → B → C → A
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Error message:**
|
|
221
|
+
```
|
|
222
|
+
Ciclo síncrono detectado: A → B → C → A
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**If no cycles are found:**
|
|
226
|
+
```
|
|
227
|
+
No se detectaron ciclos ni acoplamiento síncrono bidireccional ✓
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Why this matters:** Synchronous circular dependencies in distributed systems can cause deadlocks under load, timeout cascades, and thread-pool exhaustion. This check catches the problem at design time.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### Check 3 — Module role analysis
|
|
235
|
+
|
|
236
|
+
**What it validates:** Each module has a coherent role — it either exposes endpoints, participates in integrations, or both. Isolated modules are flagged.
|
|
237
|
+
|
|
238
|
+
#### 3a. Completely isolated modules
|
|
239
|
+
|
|
240
|
+
A module with no `exposes[]` AND no participation in `integrations` (neither as producer/consumer nor as caller/callee) in an integration is suspicious.
|
|
241
|
+
|
|
242
|
+
**Warning:**
|
|
243
|
+
```
|
|
244
|
+
Módulo aislado: 'reporting' no tiene endpoints expuestos ni integraciones declaradas
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### 3b. Modules without `exposes[]`
|
|
248
|
+
|
|
249
|
+
A module with integrations but no REST endpoints is noted. This is often intentional (pure consumers, background processors) but is surfaced as a warning for review.
|
|
250
|
+
|
|
251
|
+
**Warning:**
|
|
252
|
+
```
|
|
253
|
+
'notifications' no tiene endpoints expuestos (exposes[] vacío o ausente)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
If `notifications` only consumes events and doesn't expose REST endpoints, it's also marked as a passing note:
|
|
257
|
+
```
|
|
258
|
+
'notifications' es consumidor puro de eventos (correcto: no produce eventos propios) ✓
|
|
259
|
+
'notifications' no expone endpoints REST directamente (módulo de integración) ✓
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### 3c. Autonomous modules
|
|
263
|
+
|
|
264
|
+
Modules with endpoints but no integrations are flagged as autonomous — useful for spotting modules that should be integrated but aren't yet.
|
|
265
|
+
|
|
266
|
+
**Passing:**
|
|
267
|
+
```
|
|
268
|
+
'movies' es un módulo autónomo sin dependencias de integración ✓
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
### Check 4 — Behavior gaps
|
|
274
|
+
|
|
275
|
+
**What it validates:** Every state-mutating endpoint (PUT, POST, PATCH, DELETE) that uses a "scheduler-like" verb has at least one identifiable trigger — an incoming event or a sync call.
|
|
276
|
+
|
|
277
|
+
#### Trigger verbs detected
|
|
278
|
+
|
|
279
|
+
The evaluator looks for these verbs in the `useCase` name:
|
|
280
|
+
|
|
281
|
+
| Verb | Typical pattern |
|
|
282
|
+
|------|----------------|
|
|
283
|
+
| `expire` | `ExpireReservation`, `ExpireSession` |
|
|
284
|
+
| `clean` | `CleanExpiredTokens` |
|
|
285
|
+
| `close` | `CloseBatch` |
|
|
286
|
+
| `archive` | `ArchiveOldOrders` |
|
|
287
|
+
| `timeout` | `TimeoutPendingPayments` |
|
|
288
|
+
| `process` | `ProcessRefund`, `ProcessPendingItems` |
|
|
289
|
+
| `purge` | `PurgeDeletedUsers` |
|
|
290
|
+
| `flush` | `FlushQueue` |
|
|
291
|
+
|
|
292
|
+
#### What constitutes a valid trigger
|
|
293
|
+
|
|
294
|
+
- **Async event:** another module produces an event consumed by this module, and the event name contains the same verb
|
|
295
|
+
- **Sync call:** another module calls this endpoint via `sync[].using`
|
|
296
|
+
|
|
297
|
+
If neither is found, the evaluator raises a warning:
|
|
298
|
+
|
|
299
|
+
**Warning:**
|
|
300
|
+
```
|
|
301
|
+
Gap de comportamiento: 'ExpireReservation' (PUT /reservations/{id}/expire) en 'reservations'
|
|
302
|
+
no tiene ningún evento ni llamada síncrona que lo active.
|
|
303
|
+
Puede necesitar un scheduler o job periódico.
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### How to fix a behavior gap
|
|
307
|
+
|
|
308
|
+
Option A — Add an async trigger event:
|
|
309
|
+
```yaml
|
|
310
|
+
integrations:
|
|
311
|
+
async:
|
|
312
|
+
- event: ReservationExpiredEvent
|
|
313
|
+
producer: reservations # self-triggered via scheduler
|
|
314
|
+
topic: RESERVATION_EXPIRED
|
|
315
|
+
consumers:
|
|
316
|
+
- module: reservations # acts on it
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Option B — Document the scheduler explicitly (informational, suppresses the warning by convention):
|
|
320
|
+
```yaml
|
|
321
|
+
exposes:
|
|
322
|
+
- method: PUT
|
|
323
|
+
path: /reservations/{id}/expire
|
|
324
|
+
useCase: ExpireReservation
|
|
325
|
+
description: "Invocado por Spring @Scheduled cada minuto. No tiene trigger externo."
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Option C — Accept the warning; it's a legitimate scheduler endpoint.
|
|
329
|
+
|
|
330
|
+
**Why this matters:** These gaps represent operations that exist in the design but have no automated trigger. In production they either never run (dead code risk) or require manual invocation (operational risk). Surfacing them early allows deliberate decisions about scheduling strategies.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
### Check 5 — Coupling patterns
|
|
335
|
+
|
|
336
|
+
**What it validates:** The relationship between synchronous calls and asynchronous events to detect asymmetric coupling that increases fragility.
|
|
337
|
+
|
|
338
|
+
#### Asymmetric coupling pattern
|
|
339
|
+
|
|
340
|
+
This pattern occurs when:
|
|
341
|
+
- Module A calls Module B **synchronously**
|
|
342
|
+
- Module B also publishes events that Module A **consumes asynchronously**
|
|
343
|
+
|
|
344
|
+
This creates a hybrid dependency — A depends on B both at request time (sync call) and at event time (async consumer). While not a hard error, it often indicates that the data needed in the sync call could instead travel inside the event, eliminating the coupling entirely.
|
|
345
|
+
|
|
346
|
+
```yaml
|
|
347
|
+
# payments calls reservations synchronously (to get amount)
|
|
348
|
+
sync:
|
|
349
|
+
- caller: payments
|
|
350
|
+
calls: reservations
|
|
351
|
+
port: ReservationService
|
|
352
|
+
using:
|
|
353
|
+
- GET /reservations/{id}
|
|
354
|
+
|
|
355
|
+
# reservations also sends events that payments consumes
|
|
356
|
+
async:
|
|
357
|
+
- event: ReservationCreatedEvent
|
|
358
|
+
producer: reservations
|
|
359
|
+
consumers:
|
|
360
|
+
- module: payments # ← asymmetric: payments ↔ reservations in both directions
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Warning:**
|
|
364
|
+
```
|
|
365
|
+
Acoplamiento asimétrico: 'payments' llama síncronamente a 'reservations',
|
|
366
|
+
mientras 'reservations' responde vía eventos asíncronos (ReservationCreatedEvent,
|
|
367
|
+
ReservationCancelledEvent). Considerar pasar los datos necesarios directamente
|
|
368
|
+
en el evento para eliminar la llamada síncrona.
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
#### How to eliminate asymmetric coupling
|
|
372
|
+
|
|
373
|
+
Embed the needed data in the event payload:
|
|
374
|
+
|
|
375
|
+
```yaml
|
|
376
|
+
# Before (asymmetric): payments calls GET /reservations/{id} to get the amount
|
|
377
|
+
# After (decoupled): amount travels inside the event
|
|
378
|
+
- event: ReservationCreatedEvent
|
|
379
|
+
producer: reservations
|
|
380
|
+
topic: RESERVATION_CREATED
|
|
381
|
+
# Event payload would include: reservationId, customerId, amount, seatCount
|
|
382
|
+
consumers:
|
|
383
|
+
- module: payments # payments now has amount without a sync call
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Once `amount` travels in the event, the sync call from `payments → reservations` becomes unnecessary and can be removed.
|
|
387
|
+
|
|
388
|
+
#### Dual-trigger pattern (intentional — passing)
|
|
389
|
+
|
|
390
|
+
When a module exposes an endpoint that can be triggered both via REST call AND by consuming an event, the evaluator recognizes this as an intentional dual-trigger design and marks it as passing:
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
'screenings' tiene endpoints accesibles tanto síncronamente como vía eventos
|
|
394
|
+
(diseño dual — intencional) ✓
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
This pattern is valid when an operation needs to be invocable both manually (admin REST call) and automatically (event-driven).
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## 5. Score calculation
|
|
402
|
+
|
|
403
|
+
The score is calculated as:
|
|
404
|
+
|
|
405
|
+
```
|
|
406
|
+
score = round((passed_count / (passed_count + errors_count + warnings_count)) * 100)
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Where:
|
|
410
|
+
- `errors_count` — number of critical errors (each error counts as 1)
|
|
411
|
+
- `warnings_count` — number of warnings (each warning counts as 1)
|
|
412
|
+
- `passed_count` — number of passing validations
|
|
413
|
+
|
|
414
|
+
**Score thresholds:**
|
|
415
|
+
|
|
416
|
+
| Score | Color | Interpretation |
|
|
417
|
+
|-------|-------|----------------|
|
|
418
|
+
| > 80% | 🟢 Green | Good architecture — minor issues only |
|
|
419
|
+
| 60–80% | 🟡 Yellow | Moderate issues — review warnings before coding |
|
|
420
|
+
| < 60% | 🔴 Red | Significant problems — resolve errors before proceeding |
|
|
421
|
+
|
|
422
|
+
A score of 100% means zero errors, zero warnings, and at least some passing validations.
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## 6. Report output
|
|
427
|
+
|
|
428
|
+
The command writes a self-contained HTML file (no external dependencies at runtime) and starts a local HTTP server for preview.
|
|
429
|
+
|
|
430
|
+
```
|
|
431
|
+
✔ Analysis complete!
|
|
432
|
+
|
|
433
|
+
📊 Validation Summary
|
|
434
|
+
────────────────────────────────────────
|
|
435
|
+
🔴 Errors: 0
|
|
436
|
+
🟡 Warnings: 5
|
|
437
|
+
🟢 Passed: 17
|
|
438
|
+
📈 Score: 87%
|
|
439
|
+
|
|
440
|
+
🌐 Server running at: http://localhost:3000
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
The HTML report contains three interactive tabs:
|
|
444
|
+
|
|
445
|
+
| Tab | Contents |
|
|
446
|
+
|-----|----------|
|
|
447
|
+
| **Validación** | Score cards, collapsible sections for errors / warnings / passed |
|
|
448
|
+
| **Simulador de flujos** | Step-by-step playback of each async event flow, with sync sub-calls shown inline |
|
|
449
|
+
| **Arquitectura** | Module dependency explorer (click any module), sync dependency cards, Kafka topic map, interactive network diagram (Vis.js) with hover highlights per event group |
|
|
450
|
+
|
|
451
|
+
The HTML is a **single self-contained file** — embeds all data as base64, uses React 18 from CDN. Can be shared as-is without a server.
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## 7. Practical examples with real findings
|
|
456
|
+
|
|
457
|
+
### Example: cinema booking system
|
|
458
|
+
|
|
459
|
+
Running `eva evaluate system` on a cinema booking `system.yaml` with 7 modules and 8 async events produced:
|
|
460
|
+
|
|
461
|
+
**Score: 87% (0 errors, 5 warnings, 17 passed)**
|
|
462
|
+
|
|
463
|
+
| Finding | Type | Check | Recommendation |
|
|
464
|
+
|---------|------|-------|----------------|
|
|
465
|
+
| `notifications` has no `exposes[]` | ⚠️ Warning | Check 3 | Intentional — pure event consumer. Acceptable. |
|
|
466
|
+
| `ExpireReservation` has no trigger | ⚠️ Warning | Check 4 | Add a Spring `@Scheduled` job or a Temporal workflow to call this endpoint every minute |
|
|
467
|
+
| `ProcessRefund` has no trigger | ⚠️ Warning | Check 4 | Add a `ReservationCancelledEvent` consumer in `payments` that calls this endpoint |
|
|
468
|
+
| `payments → reservations` asymmetric | ⚠️ Warning | Check 5 | Embed `amount` in `ReservationCreatedEvent` payload; remove the sync call |
|
|
469
|
+
| `reservations → screenings` asymmetric | ⚠️ Warning | Check 5 | Embed screening data in `PrivateEventReservationCreatedEvent`; remove the sync GET call |
|
|
470
|
+
|
|
471
|
+
None of these required code changes before the score improved — they document deliberate design decisions and explicit technical debt.
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## 8. Common errors and how to fix them
|
|
476
|
+
|
|
477
|
+
### Error: event producer not declared
|
|
478
|
+
|
|
479
|
+
```
|
|
480
|
+
Integridad referencial: el productor 'inventory' del evento 'StockUpdatedEvent'
|
|
481
|
+
no está declarado en modules[]
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**Fix:** Add the missing module to `modules[]`, or correct the producer name typo.
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
### Error: sync endpoint not declared in target module
|
|
489
|
+
|
|
490
|
+
```
|
|
491
|
+
Integridad referencial: el endpoint 'GET /orders/{id}/items' usado por 'shipping'
|
|
492
|
+
no está declarado en el exposes[] de 'orders'
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Fix:** Add the missing endpoint to `orders.exposes[]`:
|
|
496
|
+
|
|
497
|
+
```yaml
|
|
498
|
+
- name: orders
|
|
499
|
+
exposes:
|
|
500
|
+
- method: GET
|
|
501
|
+
path: /orders/{id}/items # ← add this
|
|
502
|
+
useCase: GetOrderItems
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
### Error: synchronous bidirectional cycle
|
|
508
|
+
|
|
509
|
+
```
|
|
510
|
+
Acoplamiento circular síncrono: 'A' y 'B' se llaman mutuamente de forma síncrona.
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**Fix options:**
|
|
514
|
+
1. Remove one of the sync calls and replace it with an event
|
|
515
|
+
2. Extract the shared data into a third module that both A and B can query
|
|
516
|
+
3. Pass the needed data from A to B via the initial request payload, avoiding the reverse call
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
### Warning: behavior gap (scheduler verb with no trigger)
|
|
521
|
+
|
|
522
|
+
```
|
|
523
|
+
Gap de comportamiento: 'ArchiveOldOrders' (PUT /orders/archive) en 'orders'
|
|
524
|
+
no tiene ningún evento ni llamada síncrona que lo active.
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
**Fix:** Document the scheduling strategy. Options:
|
|
528
|
+
- Spring `@Scheduled(cron = "0 0 2 * * *")` in the `orders` module
|
|
529
|
+
- A dedicated `scheduler` module that calls this endpoint via sync
|
|
530
|
+
- A Temporal workflow (`eva add temporal-client` + `eva g temporal-flow orders`)
|
|
531
|
+
- Accept the warning if the endpoint is intentionally manual-only
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
### Warning: asymmetric coupling
|
|
536
|
+
|
|
537
|
+
```
|
|
538
|
+
Acoplamiento asimétrico: 'A' llama síncronamente a 'B', mientras 'B' responde
|
|
539
|
+
vía eventos asíncronos.
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Fix:** Audit the sync call. Ask: "Does A need this data at request time, or could it arrive via the event?" If via event — embed the field in the event payload and delete the sync call entry from `system.yaml`.
|