eva4j 1.0.14 → 1.0.16
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/skills/skill-creator/LICENSE.txt +202 -0
- package/.agents/skills/skill-creator/SKILL.md +485 -0
- package/.agents/skills/skill-creator/agents/analyzer.md +274 -0
- package/.agents/skills/skill-creator/agents/comparator.md +202 -0
- package/.agents/skills/skill-creator/agents/grader.md +223 -0
- package/.agents/skills/skill-creator/assets/eval_review.html +146 -0
- package/.agents/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.agents/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.agents/skills/skill-creator/references/schemas.md +430 -0
- package/.agents/skills/skill-creator/scripts/__init__.py +0 -0
- package/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.agents/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.agents/skills/skill-creator/scripts/improve_description.py +247 -0
- package/.agents/skills/skill-creator/scripts/package_skill.py +136 -0
- package/.agents/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/.agents/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.agents/skills/skill-creator/scripts/run_loop.py +328 -0
- package/.agents/skills/skill-creator/scripts/utils.py +47 -0
- package/AGENTS.md +268 -6
- package/COMMAND_EVALUATION.md +15 -16
- package/DOMAIN_YAML_GUIDE.md +430 -14
- package/FUTURE_FEATURES.md +1627 -1168
- package/README.md +461 -13
- package/bin/eva4j.js +32 -14
- package/config/defaults.json +1 -0
- package/docs/commands/EVALUATE_SYSTEM.md +746 -261
- package/docs/commands/EXPORT_DIAGRAM.md +153 -0
- package/docs/commands/GENERATE_ENTITIES.md +599 -6
- package/docs/commands/INDEX.md +7 -0
- package/examples/domain-events.yaml +166 -20
- package/examples/domain-listeners.yaml +212 -0
- package/examples/domain-one-to-many.yaml +1 -0
- package/examples/domain-one-to-one.yaml +1 -0
- package/examples/domain-ports.yaml +414 -0
- package/examples/domain-soft-delete.yaml +47 -44
- package/examples/system/notification.yaml +147 -0
- package/examples/system/product.yaml +185 -0
- package/examples/system/system.yaml +112 -0
- package/examples/system-report.html +971 -0
- package/examples/system.yaml +46 -3
- package/package.json +2 -1
- package/src/agents/design-reviewer.agent.md +163 -0
- package/src/commands/build.js +714 -0
- package/src/commands/create.js +1 -0
- package/src/commands/detach.js +149 -108
- package/src/commands/evaluate-system.js +234 -8
- package/src/commands/export-diagram.js +522 -0
- package/src/commands/generate-entities.js +685 -66
- package/src/commands/generate-http-exchange.js +2 -0
- package/src/commands/generate-kafka-event.js +43 -10
- package/src/generators/base-generator.js +18 -6
- package/src/generators/postman-generator.js +188 -0
- package/src/generators/shared-generator.js +12 -2
- package/src/skills/build-system-yaml/SKILL.md +323 -0
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +410 -0
- package/src/skills/build-system-yaml/references/module-spec.md +367 -0
- package/src/skills/build-system-yaml/references/system-yaml-spec.md +178 -0
- package/src/utils/config-manager.js +54 -0
- package/src/utils/context-builder.js +1 -0
- package/src/utils/domain-diagram.js +192 -0
- package/src/utils/domain-validator.js +1020 -0
- package/src/utils/fake-data.js +376 -0
- package/src/utils/system-validator.js +319 -199
- package/src/utils/yaml-to-entity.js +272 -7
- package/templates/aggregate/AggregateMapper.java.ejs +3 -2
- package/templates/aggregate/AggregateRepository.java.ejs +3 -2
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +6 -5
- package/templates/aggregate/AggregateRoot.java.ejs +60 -2
- package/templates/aggregate/DomainEventHandler.java.ejs +4 -1
- package/templates/aggregate/DomainEventRecord.java.ejs +24 -8
- package/templates/aggregate/DomainEventSnapshot.java.ejs +46 -0
- package/templates/aggregate/JpaAggregateRoot.java.ejs +6 -0
- package/templates/base/docker/Dockerfile.ejs +21 -0
- package/templates/base/gradle/build.gradle.ejs +3 -2
- package/templates/base/root/AGENTS.md.ejs +306 -45
- package/templates/crud/ApplicationMapper.java.ejs +4 -0
- package/templates/crud/Controller.java.ejs +4 -4
- package/templates/crud/CreateCommand.java.ejs +4 -0
- package/templates/crud/CreateCommandHandler.java.ejs +3 -6
- package/templates/crud/CreateItemDto.java.ejs +4 -0
- package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
- package/templates/crud/DeleteCommandHandler.java.ejs +12 -6
- package/templates/crud/EndpointsController.java.ejs +6 -6
- package/templates/crud/FindByQuery.java.ejs +1 -1
- package/templates/crud/FindByQueryHandler.java.ejs +3 -9
- package/templates/crud/GetQueryHandler.java.ejs +2 -5
- package/templates/crud/ListQuery.java.ejs +1 -1
- package/templates/crud/ListQueryHandler.java.ejs +8 -14
- package/templates/crud/ScaffoldCommandHandler.java.ejs +2 -4
- package/templates/crud/ScaffoldQuery.java.ejs +3 -2
- package/templates/crud/ScaffoldQueryHandler.java.ejs +5 -6
- package/templates/crud/SubEntityAddCommand.java.ejs +4 -0
- package/templates/crud/SubEntityAddCommandHandler.java.ejs +2 -6
- package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +2 -7
- package/templates/crud/TransitionCommandHandler.java.ejs +2 -6
- package/templates/crud/UpdateCommand.java.ejs +4 -0
- package/templates/crud/UpdateCommandHandler.java.ejs +2 -5
- package/templates/evaluate/report.html.ejs +394 -2
- package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
- package/templates/kafka-event/Event.java.ejs +9 -0
- package/templates/kafka-listener/KafkaController.java.ejs +1 -1
- package/templates/kafka-listener/KafkaListenerClass.java.ejs +1 -1
- package/templates/kafka-listener/ListenerClass.java.ejs +65 -0
- package/templates/kafka-listener/ListenerCommand.java.ejs +31 -0
- package/templates/kafka-listener/ListenerCommandHandler.java.ejs +25 -0
- package/templates/kafka-listener/ListenerIntegrationEvent.java.ejs +37 -0
- package/templates/kafka-listener/ListenerMethod.java.ejs +1 -1
- package/templates/kafka-listener/ListenerNestedType.java.ejs +28 -0
- package/templates/mock/MockEvent.java.ejs +10 -0
- package/templates/mock/MockMessageBrokerImpl.java.ejs +35 -0
- package/templates/mock/MockMessageBrokerImplMethod.java.ejs +6 -0
- package/templates/mock/SpringEventListener.java.ejs +61 -0
- package/templates/ports/PortDomainModel.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +67 -0
- package/templates/ports/PortFeignClient.java.ejs +45 -0
- package/templates/ports/PortFeignConfig.java.ejs +24 -0
- package/templates/ports/PortInterface.java.ejs +45 -0
- package/templates/ports/PortNestedType.java.ejs +28 -0
- package/templates/ports/PortRequestDto.java.ejs +30 -0
- package/templates/ports/PortResponseDto.java.ejs +28 -0
- package/templates/postman/Collection.json.ejs +1 -1
- package/templates/postman/UnifiedCollection.json.ejs +185 -0
- package/templates/shared/annotations/LogAfter.java.ejs +2 -0
- package/templates/shared/annotations/LogBefore.java.ejs +2 -0
- package/templates/shared/annotations/LogExceptions.java.ejs +2 -0
- package/templates/shared/annotations/LogLevel.java.ejs +8 -0
- package/templates/shared/annotations/LogTimer.java.ejs +1 -0
- package/templates/shared/annotations/Loggable.java.ejs +17 -0
- package/templates/shared/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
- package/templates/shared/configurations/loggerConfig/HandlerLogs.java.ejs +120 -32
- package/templates/shared/errorMessage/ErrorResponse.java.ejs +23 -0
- package/templates/shared/handlerException/HandlerExceptions.java.ejs +99 -79
- package/src/commands/generate-system.js +0 -243
- package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +0 -1103
- package/templates/base/root/skill-build-domain-yaml.ejs +0 -292
- package/templates/base/root/skill-build-system-yaml.ejs +0 -252
- package/templates/shared/errorMessage/ErrorMessage.java.ejs +0 -5
- package/templates/shared/errorMessage/FullErrorMessage.java.ejs +0 -9
- package/templates/shared/errorMessage/ShortErrorMessage.java.ejs +0 -6
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# Especificaciones de documentación del sistema
|
|
2
|
+
|
|
3
|
+
Referencia para generar `system/system.md` y `system/{module}.md`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Parte 1 — system.md (especificación narrativa global)
|
|
8
|
+
|
|
9
|
+
### Ubicación: `system/system.md`
|
|
10
|
+
|
|
11
|
+
Especificación técnica narrativa del sistema completo. Una sección `##` por cada módulo con subsecciones detalladas.
|
|
12
|
+
|
|
13
|
+
### Estructura obligatoria
|
|
14
|
+
|
|
15
|
+
```markdown
|
|
16
|
+
# system.md — Technical Specification
|
|
17
|
+
|
|
18
|
+
## {module-name}
|
|
19
|
+
|
|
20
|
+
### Module Role
|
|
21
|
+
[3–5 detailed paragraphs]
|
|
22
|
+
- What business problem it solves and what entities/concepts it manages
|
|
23
|
+
- Its exclusive responsibilities (bounded context boundaries)
|
|
24
|
+
- What is NOT this module's responsibility
|
|
25
|
+
- How it collaborates with other modules
|
|
26
|
+
- Business invariants it protects
|
|
27
|
+
|
|
28
|
+
### Use Cases
|
|
29
|
+
[One `####` section per useCase in exposes: and consumers[].useCase]
|
|
30
|
+
|
|
31
|
+
#### {UseCaseName}
|
|
32
|
+
**What it does:** [detailed business logic description]
|
|
33
|
+
**Preconditions:** [required state, entities that must exist]
|
|
34
|
+
**Postconditions:** [system state after successful execution]
|
|
35
|
+
**Validations and errors:** [exception conditions and error types]
|
|
36
|
+
**Events emitted:** [DomainEvent name and trigger condition, or "none"]
|
|
37
|
+
|
|
38
|
+
### Exposed Endpoints
|
|
39
|
+
[One `####` per endpoint in exposes:]
|
|
40
|
+
|
|
41
|
+
#### {METHOD} {/path}
|
|
42
|
+
**Purpose:** [endpoint description and usage context]
|
|
43
|
+
**Path params / Query params:** [each parameter described]
|
|
44
|
+
**Request body:** [expected fields, types, validation constraints]
|
|
45
|
+
**Response:** [returned fields and their business meaning]
|
|
46
|
+
**Errors:** [HTTP status codes and when they occur]
|
|
47
|
+
|
|
48
|
+
### Emitted Events
|
|
49
|
+
[Only if module is producer in integrations.async]
|
|
50
|
+
|
|
51
|
+
#### {EventName}
|
|
52
|
+
**When:** [exact business condition that fires the event]
|
|
53
|
+
**Payload:** [event fields with description]
|
|
54
|
+
**Consumers and their actions:**
|
|
55
|
+
- `{module}` → `{useCase}`: [what the consumer does]
|
|
56
|
+
|
|
57
|
+
### Ports (outbound sync calls)
|
|
58
|
+
[Only if module is caller in integrations.sync]
|
|
59
|
+
|
|
60
|
+
#### {PortName} → {target-module}
|
|
61
|
+
**When called:** [in which use case and under what condition]
|
|
62
|
+
**Endpoints used:** [METHOD /path list]
|
|
63
|
+
**Data obtained and how it's used:** [detailed description]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Reglas del system.md
|
|
67
|
+
|
|
68
|
+
- **Ser SUMAMENTE específico**: estados de entidades, validaciones concretas, campos relevantes. Nunca "gestiona los datos".
|
|
69
|
+
- **Flujos de extremo a extremo**: si `ConfirmReservation` es disparado por `PaymentApprovedEvent`, explicarlo en ambas secciones.
|
|
70
|
+
- **Incluir useCases de consumers** como casos de uso del módulo consumidor.
|
|
71
|
+
- **Referenciar módulos por nombre**.
|
|
72
|
+
- **Máquinas de estado** cuando hay ciclos de vida.
|
|
73
|
+
- Omitir secciones no aplicables.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Parte 2 — {module}.md (especificación técnica por módulo)
|
|
78
|
+
|
|
79
|
+
### Ubicación: `system/{module-name}.md`
|
|
80
|
+
|
|
81
|
+
Especificación completa e independiente para cada módulo. Un desarrollador puede leerla sin conocer el sistema completo.
|
|
82
|
+
|
|
83
|
+
### Estructura obligatoria
|
|
84
|
+
|
|
85
|
+
```markdown
|
|
86
|
+
# {module-name} — Technical Specification
|
|
87
|
+
|
|
88
|
+
## Module Role
|
|
89
|
+
[3–5 VERY detailed paragraphs]
|
|
90
|
+
- What business problem it solves and what entities/concepts it manages
|
|
91
|
+
- Exclusive responsibilities (bounded context boundaries)
|
|
92
|
+
- What is NOT this module's responsibility
|
|
93
|
+
- How it collaborates with other modules
|
|
94
|
+
|
|
95
|
+
## Invariants
|
|
96
|
+
|
|
97
|
+
> Invariants are conditions that must be **always true** within this bounded context.
|
|
98
|
+
> Violating an invariant is a **critical business error** that must throw an exception.
|
|
99
|
+
|
|
100
|
+
| ID | Invariant | Violation consequence |
|
|
101
|
+
|----|-----------|----------------------|
|
|
102
|
+
| INV-01 | [condition] | [exception / what it prevents] |
|
|
103
|
+
| INV-02 | ... | ... |
|
|
104
|
+
|
|
105
|
+
> Each use case must verify relevant invariants **before** persisting changes.
|
|
106
|
+
|
|
107
|
+
## State Machine
|
|
108
|
+
[ONLY if the module manages entity lifecycle — omit otherwise]
|
|
109
|
+
|
|
110
|
+
```mermaid
|
|
111
|
+
stateDiagram-v2
|
|
112
|
+
[*] --> INITIAL_STATE
|
|
113
|
+
INITIAL_STATE --> NEXT_STATE : event / action
|
|
114
|
+
NEXT_STATE --> FINAL_STATE : event / action
|
|
115
|
+
FINAL_STATE --> [*]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> Transition restrictions: [explain forbidden transitions — these are implicit state machine invariants]
|
|
119
|
+
|
|
120
|
+
## Interaction Diagram
|
|
121
|
+
|
|
122
|
+
> Shows the complete flow: what arrives at the module (HTTP or async event),
|
|
123
|
+
> which use case executes, and what event is emitted.
|
|
124
|
+
|
|
125
|
+
```mermaid
|
|
126
|
+
flowchart TD
|
|
127
|
+
subgraph HTTP["REST Endpoints"]
|
|
128
|
+
EP1["METHOD /path"]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
subgraph ASYNC_IN["Incoming Events"]
|
|
132
|
+
EI1["📥 EventName"]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
subgraph USE_CASES["Use Cases"]
|
|
136
|
+
UC1["UseCaseName"]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
subgraph EVENTS_OUT["Emitted Events"]
|
|
140
|
+
EO1["📤 EmittedEventName"]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
EP1 --> UC1
|
|
144
|
+
EI1 --> UC2["AnotherUseCase"]
|
|
145
|
+
UC1 --> EO1
|
|
146
|
+
UC2 -->|"no event"| FIN([end])
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> Node conventions:
|
|
150
|
+
> - HTTP endpoints → rectangles with method and path
|
|
151
|
+
> - Incoming events → `📥` prefix
|
|
152
|
+
> - Use cases → rectangles with handler name
|
|
153
|
+
> - Emitted events → `📤` prefix
|
|
154
|
+
> - No output event → connect to FIN([end])
|
|
155
|
+
> - Omit ASYNC_IN if no events consumed; omit EVENTS_OUT if no events produced
|
|
156
|
+
|
|
157
|
+
## Sequence Diagram
|
|
158
|
+
[Chronological interactions between actors and components for main flows.
|
|
159
|
+
One diagram per complex flow or flow with significant branching.]
|
|
160
|
+
|
|
161
|
+
```mermaid
|
|
162
|
+
sequenceDiagram
|
|
163
|
+
actor Client as Client
|
|
164
|
+
participant API as REST API
|
|
165
|
+
participant Handler as CommandHandler
|
|
166
|
+
participant Domain as Domain Entity
|
|
167
|
+
participant Repo as Repository
|
|
168
|
+
participant Broker as Message Broker
|
|
169
|
+
participant ExtSvc as External Service
|
|
170
|
+
|
|
171
|
+
Client->>API: POST /resource {payload}
|
|
172
|
+
API->>Handler: CreateResourceCommand
|
|
173
|
+
Handler->>Repo: findById(id)
|
|
174
|
+
Repo-->>Handler: entity (or 404)
|
|
175
|
+
Handler->>Domain: businessMethod()
|
|
176
|
+
Domain-->>Handler: updated entity
|
|
177
|
+
Handler->>Repo: save(entity)
|
|
178
|
+
Handler->>Broker: publish(ResourceCreatedEvent)
|
|
179
|
+
Broker-->>Handler: ack
|
|
180
|
+
Handler-->>API: resourceId
|
|
181
|
+
API-->>Client: 201 Created {resourceId}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
> Actor conventions:
|
|
185
|
+
> - `actor Client` → human users or external systems initiating the flow
|
|
186
|
+
> - `participant API` → module REST controller
|
|
187
|
+
> - `participant Handler` → CommandHandler / QueryHandler
|
|
188
|
+
> - `participant Domain` → domain entity (include when business logic is relevant)
|
|
189
|
+
> - `participant Repo` → repository abstraction
|
|
190
|
+
> - `participant Broker` → message broker — omit if no events
|
|
191
|
+
> - `participant ExtSvc` → external service via ports — use real port name
|
|
192
|
+
> - Sync responses: `-->>` (dotted line with arrow)
|
|
193
|
+
> - Async messages: `--)` (dotted line without immediate return)
|
|
194
|
+
> - One diagram per main flow; skip trivial read-only flows
|
|
195
|
+
|
|
196
|
+
## Use Cases
|
|
197
|
+
[One `###` per useCase in exposes: and consumers[].useCase]
|
|
198
|
+
|
|
199
|
+
### {UseCaseName}
|
|
200
|
+
**Type:** `HTTP` | `Incoming Event` (specify which)
|
|
201
|
+
**What it does:** [detailed business logic description]
|
|
202
|
+
**Preconditions:** [valid states, entities that must exist]
|
|
203
|
+
**Postconditions:** [system state after successful execution]
|
|
204
|
+
**Invariants verified:** [ID list — e.g., INV-01, INV-03]
|
|
205
|
+
**Validations and errors:** [exception conditions, error type, HTTP status]
|
|
206
|
+
**Events emitted:** [DomainEvent name and condition, or "none"]
|
|
207
|
+
|
|
208
|
+
**Flow diagram:**
|
|
209
|
+
```mermaid
|
|
210
|
+
flowchart TD
|
|
211
|
+
IN["🔵 Trigger: METHOD /path · or EventName"] --> UC["⚙️ {UseCaseName}"]
|
|
212
|
+
UC --> INV{"Check invariants"}
|
|
213
|
+
INV -->|"violated"| ERR[/"Exception / Error Response"/]
|
|
214
|
+
INV -->|"passed"| BIZ["Execute business logic"]
|
|
215
|
+
BIZ --> SAVE["Persist changes"]
|
|
216
|
+
SAVE -->|"if applicable"| EV["📤 DomainEvent emitted"]
|
|
217
|
+
SAVE -->|"no event"| FIN([end])
|
|
218
|
+
```
|
|
219
|
+
> Adapt to real flow: include relevant business nodes and branching.
|
|
220
|
+
|
|
221
|
+
## Exposed Endpoints
|
|
222
|
+
[Only if module has exposes: entries]
|
|
223
|
+
|
|
224
|
+
### {METHOD} {/path}
|
|
225
|
+
**Use case:** `{UseCase}`
|
|
226
|
+
**Purpose:** [description]
|
|
227
|
+
**Path params / Query params:** [each parameter]
|
|
228
|
+
**Request body:** [fields, types, validations]
|
|
229
|
+
**Response:** [fields and business meaning]
|
|
230
|
+
**Errors:** [HTTP status codes and conditions]
|
|
231
|
+
|
|
232
|
+
## Emitted Events
|
|
233
|
+
[Only if module is producer in integrations.async]
|
|
234
|
+
|
|
235
|
+
### {EventName}
|
|
236
|
+
**When:** [exact business condition]
|
|
237
|
+
**Payload:** [fields with description]
|
|
238
|
+
**Consumers and their actions:**
|
|
239
|
+
- `{module}` → `{useCase}`: [what consumer does]
|
|
240
|
+
|
|
241
|
+
## Ports (outbound sync calls)
|
|
242
|
+
[Only if module is caller in integrations.sync]
|
|
243
|
+
|
|
244
|
+
### {PortName} → {target-module}
|
|
245
|
+
**When called:** [in which use case and condition]
|
|
246
|
+
**Endpoints used:** [METHOD /path list]
|
|
247
|
+
**Data obtained and how it's used:** [detailed description]
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
### Reglas de los archivos de módulo
|
|
253
|
+
|
|
254
|
+
- **Todo en inglés**: títulos, secciones, descripciones, invariantes, use cases.
|
|
255
|
+
- **INVARIANTES obligatorias**: al menos 2–3 por módulo. Analizar unicidad, estados válidos, rangos, precondiciones de transición.
|
|
256
|
+
- **Diagrama de interacciones obligatorio**: todos los endpoints y eventos entrantes. Sin entradas → nodo `[[passive module]]`.
|
|
257
|
+
- **Diagrama de secuencia obligatorio**: al menos un `sequenceDiagram` cubriendo el happy path. Diagramas adicionales para bifurcaciones (error, compensación). Modelar todos los actores reales.
|
|
258
|
+
- **Diagrama de flujo por caso de uso**: `flowchart TD` dentro de cada `### {UseCase}` con trigger, invariantes, lógica y eventos.
|
|
259
|
+
- **Máquina de estados condicional**: solo si hay entidades con ciclo de vida. Restricciones de transición son invariantes implícitas.
|
|
260
|
+
- **Referenciar invariantes** en cada caso de uso (INV-01, INV-02...).
|
|
261
|
+
- **No duplicar** system.md — el `.md` del módulo es la especificación completa.
|
|
262
|
+
- Archivos en `system/{module-name}.md`.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### Ejemplo condensado — `system/payments.md`
|
|
267
|
+
|
|
268
|
+
```markdown
|
|
269
|
+
# payments — Technical Specification
|
|
270
|
+
|
|
271
|
+
## Module Role
|
|
272
|
+
|
|
273
|
+
The `payments` module is solely responsible for the lifecycle of payments linked
|
|
274
|
+
to reservations. It manages communication with the external payment gateway,
|
|
275
|
+
records each transaction's state, and decides when to emit events that trigger
|
|
276
|
+
changes in other modules. It has no knowledge of reservation logic or notifications.
|
|
277
|
+
|
|
278
|
+
Confirming reservations and sending notifications are NOT this module's responsibility;
|
|
279
|
+
those flows are initiated by events that payments emits.
|
|
280
|
+
|
|
281
|
+
## Invariants
|
|
282
|
+
|
|
283
|
+
| ID | Invariant | Violation consequence |
|
|
284
|
+
|----|-----------|----------------------|
|
|
285
|
+
| INV-01 | Only one active payment (PENDING or APPROVED) per reservation at any time | Throws `409 Conflict` — prevents double charging |
|
|
286
|
+
| INV-02 | A CANCELLED or FAILED payment cannot transition to APPROVED | Throws `InvalidStateTransitionException` |
|
|
287
|
+
| INV-03 | Payment amount must be > 0 | Throws `400 Bad Request` |
|
|
288
|
+
| INV-04 | A payment can only be refunded if in APPROVED state | Throws `409 Conflict` |
|
|
289
|
+
|
|
290
|
+
## State Machine
|
|
291
|
+
|
|
292
|
+
```mermaid
|
|
293
|
+
stateDiagram-v2
|
|
294
|
+
[*] --> PENDING : CreatePayment
|
|
295
|
+
PENDING --> APPROVED : ApprovePayment (gateway confirms)
|
|
296
|
+
PENDING --> FAILED : FailPayment (gateway rejects)
|
|
297
|
+
APPROVED --> REFUNDED : RefundPayment
|
|
298
|
+
FAILED --> [*]
|
|
299
|
+
REFUNDED --> [*]
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
> Restrictions: CANCELLED and FAILED are terminal states (INV-02). Only APPROVED can transition to REFUNDED (INV-04).
|
|
303
|
+
|
|
304
|
+
## Interaction Diagram
|
|
305
|
+
|
|
306
|
+
```mermaid
|
|
307
|
+
flowchart TD
|
|
308
|
+
subgraph HTTP["REST Endpoints"]
|
|
309
|
+
EP1["POST /payments"]
|
|
310
|
+
EP2["GET /payments/{id}"]
|
|
311
|
+
EP3["POST /payments/{id}/refund"]
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
subgraph ASYNC_IN["Incoming Events"]
|
|
315
|
+
EI1["📥 ReservationCreatedEvent"]
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
subgraph USE_CASES["Use Cases"]
|
|
319
|
+
UC1["CreatePayment"]
|
|
320
|
+
UC2["GetPayment"]
|
|
321
|
+
UC3["RefundPayment"]
|
|
322
|
+
UC4["HandleReservationCreated"]
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
subgraph EVENTS_OUT["Emitted Events"]
|
|
326
|
+
EO1["📤 PaymentApprovedEvent"]
|
|
327
|
+
EO2["📤 PaymentFailedEvent"]
|
|
328
|
+
EO3["📤 PaymentRefundedEvent"]
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
EP1 --> UC1
|
|
332
|
+
EP2 --> UC2
|
|
333
|
+
EP3 --> UC3
|
|
334
|
+
EI1 --> UC4
|
|
335
|
+
UC4 --> UC1
|
|
336
|
+
UC1 --> EO1
|
|
337
|
+
UC1 --> EO2
|
|
338
|
+
UC3 --> EO3
|
|
339
|
+
UC2 -->|"read-only"| FIN([end])
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Use Cases
|
|
343
|
+
|
|
344
|
+
### HandleReservationCreated
|
|
345
|
+
**Type:** Incoming Event (`ReservationCreatedEvent`)
|
|
346
|
+
**What it does:** Extracts `reservationId` and `totalAmount` from payload and triggers
|
|
347
|
+
`CreatePayment` internally to start charging automatically.
|
|
348
|
+
**Preconditions:** Payload contains valid `reservationId` and `totalAmount > 0`.
|
|
349
|
+
**Postconditions:** Payment created in PENDING state.
|
|
350
|
+
**Invariants verified:** INV-01, INV-03
|
|
351
|
+
**Validations and errors:** If INV-01 violated (active payment exists), discard event
|
|
352
|
+
and log warning. If `totalAmount ≤ 0`, throw `400`.
|
|
353
|
+
**Events emitted:** none directly; approval arrives asynchronously.
|
|
354
|
+
|
|
355
|
+
**Flow diagram:**
|
|
356
|
+
```mermaid
|
|
357
|
+
flowchart TD
|
|
358
|
+
IN["📥 ReservationCreatedEvent"] --> UC["⚙️ HandleReservationCreated"]
|
|
359
|
+
UC --> INV1{"INV-01: active payment?"}
|
|
360
|
+
INV1 -->|"Yes"| WARN[/"warn — discard event"/]
|
|
361
|
+
INV1 -->|"No"| INV2{"INV-03: amount > 0?"}
|
|
362
|
+
INV2 -->|"No"| ERR[/"400 Bad Request"/]
|
|
363
|
+
INV2 -->|"Yes"| CREATE["Create Payment PENDING"]
|
|
364
|
+
CREATE --> SAVE["Persist Payment"]
|
|
365
|
+
SAVE --> FIN([end — approval arrives async])
|
|
366
|
+
```
|
|
367
|
+
```
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# system.yaml — Especificación completa
|
|
2
|
+
|
|
3
|
+
Referencia técnica para construir y validar el archivo `system/system.yaml`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Estructura completa
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
system:
|
|
11
|
+
name: project-name # kebab-case
|
|
12
|
+
groupId: com.example
|
|
13
|
+
javaVersion: 21
|
|
14
|
+
springBootVersion: 3.5.5
|
|
15
|
+
database: postgresql # h2 | postgresql | mysql
|
|
16
|
+
|
|
17
|
+
messaging: # Omitir sección completa si no hay mensajería
|
|
18
|
+
enabled: true
|
|
19
|
+
broker: kafka # kafka | rabbitmq | sns-sqs (solo kafka soportado hoy)
|
|
20
|
+
kafka:
|
|
21
|
+
bootstrapServers: localhost:9092
|
|
22
|
+
defaultGroupId: project-name
|
|
23
|
+
topicPrefix: project-name # opcional — prefixa todos los topics
|
|
24
|
+
|
|
25
|
+
modules:
|
|
26
|
+
- name: orders # plural, kebab-case
|
|
27
|
+
description: "Order lifecycle management"
|
|
28
|
+
exposes:
|
|
29
|
+
- method: GET # GET | POST | PUT | PATCH | DELETE
|
|
30
|
+
path: /orders/{id}
|
|
31
|
+
useCase: GetOrder # PascalCase — alimenta endpoints: en domain.yaml
|
|
32
|
+
description: "Get order by ID"
|
|
33
|
+
- method: GET
|
|
34
|
+
path: /orders
|
|
35
|
+
useCase: FindAllOrders
|
|
36
|
+
description: "List orders with filters and pagination"
|
|
37
|
+
- method: POST
|
|
38
|
+
path: /orders
|
|
39
|
+
useCase: CreateOrder
|
|
40
|
+
description: "Create a new order"
|
|
41
|
+
- method: PUT
|
|
42
|
+
path: /orders/{id}/confirm
|
|
43
|
+
useCase: ConfirmOrder
|
|
44
|
+
description: "Confirm a pending order"
|
|
45
|
+
|
|
46
|
+
- name: notifications
|
|
47
|
+
description: "Notification delivery service"
|
|
48
|
+
# Sin endpoints REST — solo consume eventos
|
|
49
|
+
|
|
50
|
+
integrations:
|
|
51
|
+
async:
|
|
52
|
+
- event: OrderPlacedEvent # PascalCase, tiempo pasado, sufijo Event
|
|
53
|
+
producer: orders
|
|
54
|
+
topic: ORDER_PLACED # SCREAMING_SNAKE_CASE
|
|
55
|
+
consumers:
|
|
56
|
+
- module: payments
|
|
57
|
+
useCase: HandleOrderPlaced # acción que payments ejecuta
|
|
58
|
+
- module: notifications
|
|
59
|
+
useCase: NotifyOrderPlaced # acción que notifications ejecuta
|
|
60
|
+
|
|
61
|
+
sync:
|
|
62
|
+
- caller: orders # módulo que hace la llamada
|
|
63
|
+
calls: customers # módulo destino
|
|
64
|
+
port: OrderCustomerService # PascalCase + Service — prefijado con caller
|
|
65
|
+
using:
|
|
66
|
+
- GET /customers/{id} # debe existir en exposes: de 'customers'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Convenciones de nombres
|
|
72
|
+
|
|
73
|
+
| Elemento | Convención | Ejemplo válido | Ejemplo inválido |
|
|
74
|
+
|---|---|---|---|
|
|
75
|
+
| Módulos | plural, kebab-case | `orders`, `order-items` | `Order`, `order_items` |
|
|
76
|
+
| Eventos | PascalCase + pasado + sufijo `Event` | `OrderPlacedEvent` | `PlaceOrderEvent` |
|
|
77
|
+
| Topics Kafka | SCREAMING_SNAKE_CASE — **sin `topicPrefix`** | `ORDER_PLACED` | `test-eva.ORDER_PLACED` |
|
|
78
|
+
| Port names | PascalCase + sufijo `Service` — **único por módulo** | `OrderCustomerService` | `CustomerService` (compartido) |
|
|
79
|
+
| useCases | PascalCase, verbo + sustantivo | `CreateOrder`, `FindAllOrders` | `createOrder`, `orders` |
|
|
80
|
+
| `consumers[].useCase` | PascalCase, verbo + sustantivo | `HandleOrderPlaced` | `orderPlaced` |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Restricciones estructurales
|
|
85
|
+
|
|
86
|
+
- ❌ **Sin dependencias circulares síncronas** — si `A` llama a `B`, `B` no puede llamar a `A`
|
|
87
|
+
- ❌ **Sin campos de dominio** — entidades, campos, enums → van en `domain.yaml`
|
|
88
|
+
- ❌ **Sin nombres de `port` genéricos compartidos** — si `orders` y `deliveries` llaman a `customers`, usar `OrderCustomerService` y `DeliveryCustomerService`; **nunca** `CustomerService` en ambos (causa `ConflictingBeanDefinitionException`)
|
|
89
|
+
- ✅ Cada módulo tiene **una sola responsabilidad**
|
|
90
|
+
- ✅ `calls.using:` solo referencia endpoints declarados en `exposes:` del módulo destino
|
|
91
|
+
- ✅ `consumers[].module` debe existir en `modules:`
|
|
92
|
+
- ✅ Módulos pasivos (notificaciones, auditoría) son **consumidores**, nunca `caller`
|
|
93
|
+
- ℹ️ Varios módulos pueden consumir el mismo evento sin riesgo de colisión de beans
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## useCases — patrones de nombres
|
|
98
|
+
|
|
99
|
+
### Verbos por tipo de operación
|
|
100
|
+
|
|
101
|
+
| Tipo de operación | Verbos recomendados | Ejemplo |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| Crear recurso | `Create` | `CreateOrder` |
|
|
104
|
+
| Actualizar | `Update` | `UpdateOrder` |
|
|
105
|
+
| Eliminar | `Delete` | `DeleteOrder` |
|
|
106
|
+
| Obtener por ID | `Get` | `GetOrder` |
|
|
107
|
+
| Listar con paginación | `FindAll` | `FindAllOrders` |
|
|
108
|
+
| Transición de estado | `Confirm`, `Cancel`, `Approve`, `Reject`, `Activate`, `Close`, `Complete`, `Submit`, `Publish` | `ConfirmOrder`, `CancelPayment` |
|
|
109
|
+
| Acción puntual | `Send`, `Process`, `Calculate`, `Generate`, `Assign`, `Transfer`, `Notify` | `SendNotification`, `ProcessPayment` |
|
|
110
|
+
| Búsqueda | `Search`, `Find`, `Lookup` | `FindOrdersByCustomer` |
|
|
111
|
+
|
|
112
|
+
### CRUD vs negocio — regla de generación
|
|
113
|
+
|
|
114
|
+
eva4j distingue dos categorías:
|
|
115
|
+
|
|
116
|
+
**CRUD estándar** — genera implementación completa del handler:
|
|
117
|
+
|
|
118
|
+
| Patrón | HTTP | Implementación |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `Create{Aggregate}` | POST `/resource` | Handler completo |
|
|
121
|
+
| `Update{Aggregate}` | PUT `/resource/{id}` | Handler completo |
|
|
122
|
+
| `Delete{Aggregate}` | DELETE `/resource/{id}` | Handler completo |
|
|
123
|
+
| `Get{Aggregate}` | GET `/resource/{id}` | Handler completo |
|
|
124
|
+
| `FindAll{PluralAggregate}` | GET `/resource` | Handler completo |
|
|
125
|
+
|
|
126
|
+
**Negocio** — genera scaffold con `throw new UnsupportedOperationException(...)`:
|
|
127
|
+
|
|
128
|
+
```java
|
|
129
|
+
public class ConfirmOrderCommandHandler implements CommandHandler<ConfirmOrderCommand, Void> {
|
|
130
|
+
@Override
|
|
131
|
+
public Void handle(ConfirmOrderCommand command) {
|
|
132
|
+
throw new UnsupportedOperationException("ConfirmOrderCommandHandler not implemented yet");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### useCase en consumers
|
|
138
|
+
|
|
139
|
+
Cada `consumers[]` **debe** declarar `useCase`: la acción que el consumidor ejecuta al recibir el evento.
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
consumers:
|
|
143
|
+
- module: payments
|
|
144
|
+
useCase: HandleReservationCreated # payments inicia cobro
|
|
145
|
+
- module: notifications
|
|
146
|
+
useCase: NotifyReservationCreated # envía email de confirmación
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Reglas:**
|
|
150
|
+
- PascalCase, `Verbo + Sustantivo`
|
|
151
|
+
- Describe la acción del consumidor, no repite el evento
|
|
152
|
+
- Se mapea a `listeners[].useCase` del `domain.yaml` del consumidor
|
|
153
|
+
- Verbos típicos: `Handle`, `Process`, `Confirm`, `Cancel`, `Notify`, `Accumulate`, `Release`, `Update`
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Mensajería
|
|
158
|
+
|
|
159
|
+
- Solo `kafka` está implementado; `rabbitmq` y `sns-sqs` generan warning
|
|
160
|
+
- Los **campos** de eventos NO van en `system.yaml` → se declaran en `domain.yaml → events[].fields`
|
|
161
|
+
- ❌ `listeners[].topic` NUNCA lleva el `topicPrefix` — usar solo nombre base SCREAMING_SNAKE_CASE
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Checklist de validación completa
|
|
166
|
+
|
|
167
|
+
- [ ] Módulos en plural kebab-case
|
|
168
|
+
- [ ] Eventos en tiempo pasado con sufijo `Event`
|
|
169
|
+
- [ ] Topics en SCREAMING_SNAKE_CASE sin topicPrefix
|
|
170
|
+
- [ ] Sin dependencias circulares síncronas
|
|
171
|
+
- [ ] Todos los `consumers[].module` existen en `modules:`
|
|
172
|
+
- [ ] Todos los `consumers[].useCase` presentes y en PascalCase
|
|
173
|
+
- [ ] Todos los `calls.using:` existen en `exposes:` del destino
|
|
174
|
+
- [ ] Módulos pasivos no son `caller`
|
|
175
|
+
- [ ] `useCases` en PascalCase
|
|
176
|
+
- [ ] Port names únicos por módulo (prefijados con bounded context del caller)
|
|
177
|
+
- [ ] Todo el contenido en inglés
|
|
178
|
+
- [ ] Archivo guardado en `system/system.yaml`
|
|
@@ -151,6 +151,60 @@ class ConfigManager {
|
|
|
151
151
|
|
|
152
152
|
return config;
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Persist mock backup data into .eva4j.json so it survives process restarts.
|
|
157
|
+
* @param {Object} backups - Map of { key: { path, content } }
|
|
158
|
+
*/
|
|
159
|
+
async saveMockBackup(backups, opts = {}) {
|
|
160
|
+
const config = await this.loadProjectConfig();
|
|
161
|
+
if (!config) throw new Error('Project configuration not found.');
|
|
162
|
+
|
|
163
|
+
config._mockBackup = {};
|
|
164
|
+
for (const [key, { path: filePath, content }] of Object.entries(backups)) {
|
|
165
|
+
config._mockBackup[key] = { path: filePath, content };
|
|
166
|
+
}
|
|
167
|
+
config._mockActive = true;
|
|
168
|
+
if (opts.onlyBroker) {
|
|
169
|
+
config._mockOnlyBroker = true;
|
|
170
|
+
}
|
|
171
|
+
config.updatedAt = new Date().toISOString();
|
|
172
|
+
await fs.writeJson(this.configFile, config, { spaces: 2 });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Read and clear mock backup from .eva4j.json.
|
|
177
|
+
* @returns {Object|null} backups map or null if no mock backup stored
|
|
178
|
+
*/
|
|
179
|
+
async popMockBackup() {
|
|
180
|
+
const config = await this.loadProjectConfig();
|
|
181
|
+
if (!config || !config._mockBackup) return null;
|
|
182
|
+
|
|
183
|
+
const backups = config._mockBackup;
|
|
184
|
+
delete config._mockBackup;
|
|
185
|
+
delete config._mockActive;
|
|
186
|
+
delete config._mockOnlyBroker;
|
|
187
|
+
config.updatedAt = new Date().toISOString();
|
|
188
|
+
await fs.writeJson(this.configFile, config, { spaces: 2 });
|
|
189
|
+
return backups;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Returns true if a mock backup is currently stored in .eva4j.json.
|
|
194
|
+
*/
|
|
195
|
+
async hasMockBackup() {
|
|
196
|
+
const config = await this.loadProjectConfig();
|
|
197
|
+
return !!(config && config._mockActive);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Returns true if the active mock was started with --only-broker
|
|
202
|
+
* (i.e. only the broker was swapped, DB config was kept unchanged).
|
|
203
|
+
*/
|
|
204
|
+
async hasMockOnlyBroker() {
|
|
205
|
+
const config = await this.loadProjectConfig();
|
|
206
|
+
return !!(config && config._mockOnlyBroker);
|
|
207
|
+
}
|
|
154
208
|
}
|
|
155
209
|
|
|
156
210
|
module.exports = ConfigManager;
|
|
@@ -30,6 +30,7 @@ function buildBaseContext(answers) {
|
|
|
30
30
|
dependencyManagementVersion: defaults.dependencyManagementVersion,
|
|
31
31
|
springModulithVersion: defaults.springModulithVersion,
|
|
32
32
|
springCloudVersion: defaults.springCloudVersion,
|
|
33
|
+
springdocVersion: defaults.springdocVersion,
|
|
33
34
|
gradleVersion: defaults.gradleVersion,
|
|
34
35
|
dependencies: answers.dependencies || [],
|
|
35
36
|
author: answers.author || 'Generated by eva4j',
|