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
package/README.md
CHANGED
|
@@ -37,23 +37,124 @@ eva g entities product
|
|
|
37
37
|
|
|
38
38
|
## 💎 Why eva4j?
|
|
39
39
|
|
|
40
|
-
### The
|
|
40
|
+
### The real problem: AI agents cannot generate production-quality code directly
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
The biggest bottleneck is no longer writing repetitive code — it's the gap between **business requirements** and **production code with the right architecture**.
|
|
43
|
+
|
|
44
|
+
Teams today try to solve this in two ways, both with serious problems:
|
|
45
|
+
|
|
46
|
+
**Option A — Direct prompt to the agent:** _"Create an inventory system with Spring Boot, PostgreSQL, Kafka, hexagonal architecture, CQRS..."_
|
|
47
|
+
|
|
48
|
+
The agent generates something... but:
|
|
49
|
+
- ❌ Architecture varies between modules — inconsistent
|
|
50
|
+
- ❌ Hexagonal patterns are applied partially or incorrectly
|
|
51
|
+
- ❌ No convention on where each class belongs
|
|
52
|
+
- ❌ The code doesn't compile as a whole
|
|
53
|
+
- ❌ Each regeneration produces something different
|
|
54
|
+
- ❌ Impossible to iterate incrementally
|
|
55
|
+
|
|
56
|
+
**Option B — Spec Driven Development (SDD):** _"Given this functional specification document, generate the code following these patterns..."_
|
|
57
|
+
|
|
58
|
+
One step ahead of the free prompt: instead of an informal description, the agent receives a more structured functional specification — use cases, flows, business rules. The agent generates more organized code, but the underlying problem remains:
|
|
59
|
+
|
|
60
|
+
- ⚠️ The agent must simultaneously focus on **two different planes**: interpreting the functional domain and producing code with the right architecture — and it tends to lose one when it digs into the other
|
|
61
|
+
- ⚠️ Functional specifications don't dictate technical structure: the agent makes architecture decisions each session, producing different results
|
|
62
|
+
- ⚠️ Ensuring consistency across modules requires ever more exhaustive specifications — and even then the generated code needs deep review
|
|
63
|
+
- ⚠️ Every requirement change means regenerating and manually auditing the code to detect accumulated regressions or inconsistencies
|
|
64
|
+
- ⚠️ **A change in the system definition translates into a code refactoring** — with hard-to-measure impact, prone to integration errors, and potentially affecting modules that appear unrelated to the change
|
|
65
|
+
- ⚠️ Before working on business logic, the team must stand up the entire infrastructure (database, messaging broker)
|
|
66
|
+
|
|
67
|
+
SDD improves the situation, but **still sends the agent to the wrong plane**: from the functional specification straight to code — skipping a fundamental intermediate layer.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### The new vision: technology-agnostic technical specifications as an intermediate step
|
|
72
|
+
|
|
73
|
+
The problem with SDD is not the specification itself — it's that the **functional** specification is converted directly into code. The solution is to introduce an intermediate step: transform functional requirements into **technology-agnostic technical specifications** before generating a single line of code.
|
|
74
|
+
|
|
75
|
+
These technical specifications don't talk about Spring Boot, JPA or Kafka. They talk about **domain entities, lifecycles, events, relationships between modules and API contracts**. They are understandable by the agent, the business team, and the generator — and can be reviewed, discussed, and iterated without touching code.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
79
|
+
│ LAYER 1 — Functional requirements │
|
|
80
|
+
│ (business describes what the system must do) │
|
|
81
|
+
└───────────────────────────────┬──────────────────────────────────┘
|
|
82
|
+
│ AI agent
|
|
83
|
+
│ (translates domain into structure)
|
|
84
|
+
┌───────────────────────────────▼──────────────────────────────────┐
|
|
85
|
+
│ LAYER 2 — Technology-agnostic technical specification │
|
|
86
|
+
│ (agent + team iterate here — no code) │
|
|
87
|
+
│ │
|
|
88
|
+
│ system.yaml + {module}.yaml │
|
|
89
|
+
│ • Which modules (bounded contexts) make up the system? │
|
|
90
|
+
│ • How many aggregates does each module have? │
|
|
91
|
+
│ • Which entities form each aggregate? Which is the root? │
|
|
92
|
+
│ • What fields and types does each entity have? │
|
|
93
|
+
│ • What lifecycle do entities have (states/transitions)? │
|
|
94
|
+
│ • What events occur? Who produces them and who consumes them? │
|
|
95
|
+
│ • How do modules communicate with each other? │
|
|
96
|
+
│ • What endpoints does each module expose and what use case? │
|
|
97
|
+
│ │
|
|
98
|
+
│ Reviewable · Versionable · Evaluable · Iterable │
|
|
99
|
+
│ without needing to generate any code │
|
|
100
|
+
└───────────────────────────────┬──────────────────────────────────┘
|
|
101
|
+
│ eva build
|
|
102
|
+
│ (when the team validates
|
|
103
|
+
│ that the specification is correct)
|
|
104
|
+
┌───────────────────────────────▼──────────────────────────────────┐
|
|
105
|
+
│ LAYER 3 — Code (eva4j generates, team completes) │
|
|
106
|
+
│ │
|
|
107
|
+
│ technical specification ──▶ functional Spring Boot prototype │
|
|
108
|
+
│ • Compiles from the first build │
|
|
109
|
+
│ • Endpoints respond immediately │
|
|
110
|
+
│ • No infrastructure required │
|
|
111
|
+
│ (--mock: H2 + Spring Events) │
|
|
112
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**The agent does what it does well:** translating functional requirements into technology-agnostic technical specifications — modeling the domain, identifying entities, defining lifecycles, designing contracts between modules. The result is a precise and verifiable YAML, not code.
|
|
116
|
+
|
|
117
|
+
**The team validates the specification** before a single line of Java exists — discussing entities, relationships and flows in a format readable by everyone.
|
|
118
|
+
|
|
119
|
+
**eva4j does what the agent cannot do reliably:** converting that specification into code with correct hexagonal architecture, CQRS, DDD patterns and multi-environment configuration — identically every time, no matter how many times it runs.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### From prototype to production with minimal friction
|
|
124
|
+
|
|
125
|
+
Eva4j does not generate a production-ready complete system in a single step — it generates a **functional and correct prototype** that the team can complement and iterate from day one:
|
|
126
|
+
|
|
127
|
+
| What `eva build` delivers | What the team completes |
|
|
128
|
+
|---|---|
|
|
129
|
+
| Domain entities with constructors, getters and lifecycle methods | Domain-specific business logic |
|
|
130
|
+
| Correctly structured CQRS handlers | The body of each handler (`UnsupportedOperationException` as a visible placeholder) |
|
|
131
|
+
| JSR-303 validations, DTOs, Application↔Domain↔JPA mappers | Complex business validations and cross-aggregate rules |
|
|
132
|
+
| Kafka, Feign, JPA multi-environment configuration | Specific queries, indexes, performance tuning |
|
|
133
|
+
| REST endpoints that respond from day 1 | Edge cases, business error handling |
|
|
134
|
+
| Optional mock infrastructure (H2 + Spring Events) | Integration with real infrastructure when the model is validated |
|
|
135
|
+
|
|
136
|
+
**The full cycle:**
|
|
137
|
+
|
|
138
|
+
1. The agent generates specifications from business requirements (`system.yaml` + `{module}.yaml`)
|
|
139
|
+
2. The team reviews and validates that the specification correctly reflects the domain — without seeing code
|
|
140
|
+
3. `eva build` → the prototype starts; endpoints respond; the team completes business logic **from day 1 without infrastructure**
|
|
141
|
+
4. Requirements evolve → update the YAML → `eva build` regenerates safely (checksums protect manual modifications)
|
|
142
|
+
5. When the prototype meets all verified requirements, moving to production is a configuration change — not a rewrite
|
|
143
|
+
|
|
144
|
+
**A change in the system definition is a YAML diff — not a code refactoring.** The impact is always measurable: exactly the files that correspond to what changed in the specification. The new build faithfully reflects the new design, with no residue from the previous one.
|
|
145
|
+
|
|
146
|
+
---
|
|
48
147
|
|
|
49
148
|
### The Solution
|
|
50
149
|
|
|
51
150
|
eva4j provides:
|
|
52
|
-
- ✅ **
|
|
53
|
-
- ✅ **
|
|
54
|
-
- ✅ **
|
|
55
|
-
- ✅ **
|
|
56
|
-
- ✅ **
|
|
151
|
+
- ✅ **Specification as contract** - The YAML is the artifact that the agent, the team and the generator share — reviewable, versionable, evaluable before generating a single line of code
|
|
152
|
+
- ✅ **Deterministic generation** - The same YAML always produces the same code, with no session-to-session variations
|
|
153
|
+
- ✅ **Functional prototype from day 1** - The team works on business logic while the specification continues to be refined
|
|
154
|
+
- ✅ **No infrastructure from the start** - `--mock` replaces Kafka and the DB with in-memory equivalents
|
|
155
|
+
- ✅ **Safe iteration** - Checksums prevent overwriting manual modifications on regeneration
|
|
156
|
+
- ✅ **Consistent architecture** - Same patterns across all modules, no architectural drift
|
|
157
|
+
- ✅ **Technology-agnostic specification** - The YAML describes domain structure, not framework details — while the current generator targets Spring Boot, the same specification is designed to power generators for other stacks and languages
|
|
57
158
|
|
|
58
159
|
---
|
|
59
160
|
|
|
@@ -195,7 +296,211 @@ Eva4j follows a **pragmatic approach** to microservices architecture:
|
|
|
195
296
|
|
|
196
297
|
---
|
|
197
298
|
|
|
198
|
-
##
|
|
299
|
+
## 📐 Specification Files: `system/`
|
|
300
|
+
|
|
301
|
+
eva4j projects store their specifications in the `system/` directory. These are the files an AI agent produces when designing a system: declarative structure, no side effects, verifiable before any code generation runs.
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
system/
|
|
305
|
+
├── system.yaml # Global architecture: modules, database, messaging, integrations
|
|
306
|
+
├── product.yaml # Domain model for the product module
|
|
307
|
+
├── notification.yaml # Domain model for the notification module
|
|
308
|
+
└── order.yaml # Domain model for the order module
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### `system.yaml` — The System Architecture
|
|
312
|
+
|
|
313
|
+
Defines which modules exist, how they communicate, and what infrastructure they use.
|
|
314
|
+
|
|
315
|
+
```yaml
|
|
316
|
+
system:
|
|
317
|
+
name: product-catalog
|
|
318
|
+
groupId: com.example
|
|
319
|
+
javaVersion: 21
|
|
320
|
+
springBootVersion: 3.5.5
|
|
321
|
+
database: postgresql # postgresql | mysql | h2
|
|
322
|
+
|
|
323
|
+
messaging:
|
|
324
|
+
enabled: true
|
|
325
|
+
broker: kafka
|
|
326
|
+
kafka:
|
|
327
|
+
bootstrapServers: localhost:9092
|
|
328
|
+
defaultGroupId: product-catalog
|
|
329
|
+
|
|
330
|
+
modules:
|
|
331
|
+
- name: product
|
|
332
|
+
description: "Product catalogue. Lifecycle: DRAFT → PUBLISHED → DISCONTINUED."
|
|
333
|
+
exposes:
|
|
334
|
+
- method: POST
|
|
335
|
+
path: /products
|
|
336
|
+
useCase: CreateProduct
|
|
337
|
+
- method: GET
|
|
338
|
+
path: /products/{id}
|
|
339
|
+
useCase: GetProduct
|
|
340
|
+
- method: PUT
|
|
341
|
+
path: /products/{id}/publish
|
|
342
|
+
useCase: PublishProduct
|
|
343
|
+
description: "Transition DRAFT → PUBLISHED. Emits ProductPublishedEvent."
|
|
344
|
+
|
|
345
|
+
- name: notification
|
|
346
|
+
description: "Notifications via EMAIL, SMS or PUSH. Reacts to product domain events."
|
|
347
|
+
exposes:
|
|
348
|
+
- method: PUT
|
|
349
|
+
path: /notifications/{id}/read
|
|
350
|
+
useCase: MarkNotificationRead
|
|
351
|
+
|
|
352
|
+
integrations:
|
|
353
|
+
async:
|
|
354
|
+
# Which module produces each event and which module consumes it
|
|
355
|
+
- event: ProductPublishedEvent
|
|
356
|
+
producer: product
|
|
357
|
+
topic: PRODUCT_PUBLISHED
|
|
358
|
+
consumers:
|
|
359
|
+
- module: notification
|
|
360
|
+
useCase: SendProductPublishedNotification
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
`eva build` reads this file and generates: modules with complete hexagonal architecture, Kafka dependencies, `KafkaConfig.java`, REST endpoints with their use cases, Integration Events, KafkaListeners and consumer CommandHandlers.
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
### `{module}.yaml` — The Domain Model
|
|
368
|
+
|
|
369
|
+
Each module has its own YAML that defines the complete domain model: entities, value objects, enums with lifecycle transitions, relationships, events and ports.
|
|
370
|
+
|
|
371
|
+
```yaml
|
|
372
|
+
# system/product.yaml
|
|
373
|
+
aggregates:
|
|
374
|
+
- name: Product
|
|
375
|
+
entities:
|
|
376
|
+
- name: Product
|
|
377
|
+
isRoot: true
|
|
378
|
+
tableName: products
|
|
379
|
+
audit:
|
|
380
|
+
enabled: true
|
|
381
|
+
trackUser: true
|
|
382
|
+
fields:
|
|
383
|
+
- name: id
|
|
384
|
+
type: String
|
|
385
|
+
- name: name
|
|
386
|
+
type: String
|
|
387
|
+
validations:
|
|
388
|
+
- type: NotBlank
|
|
389
|
+
message: "Product name is required"
|
|
390
|
+
- name: price
|
|
391
|
+
type: Price # Value Object defined below
|
|
392
|
+
- name: status
|
|
393
|
+
type: ProductStatus # Enum with lifecycle transitions
|
|
394
|
+
readOnly: true # Excluded from CreateDto and business constructor
|
|
395
|
+
|
|
396
|
+
valueObjects:
|
|
397
|
+
- name: Price
|
|
398
|
+
fields:
|
|
399
|
+
- name: amount
|
|
400
|
+
type: BigDecimal
|
|
401
|
+
- name: currency
|
|
402
|
+
type: String
|
|
403
|
+
methods:
|
|
404
|
+
- name: isPositive
|
|
405
|
+
returnType: boolean
|
|
406
|
+
parameters: []
|
|
407
|
+
body: "return this.amount.compareTo(BigDecimal.ZERO) > 0;"
|
|
408
|
+
|
|
409
|
+
enums:
|
|
410
|
+
- name: ProductStatus
|
|
411
|
+
initialValue: DRAFT
|
|
412
|
+
transitions:
|
|
413
|
+
- from: DRAFT
|
|
414
|
+
to: PUBLISHED
|
|
415
|
+
method: publish
|
|
416
|
+
- from: [DRAFT, PUBLISHED]
|
|
417
|
+
to: DISCONTINUED
|
|
418
|
+
method: discontinue
|
|
419
|
+
values: [DRAFT, PUBLISHED, DISCONTINUED]
|
|
420
|
+
|
|
421
|
+
events:
|
|
422
|
+
- name: ProductPublishedEvent
|
|
423
|
+
triggers: [publish] # raise() automatically injected inside publish()
|
|
424
|
+
fields:
|
|
425
|
+
- name: productId
|
|
426
|
+
type: String
|
|
427
|
+
- name: publishedAt
|
|
428
|
+
type: LocalDateTime
|
|
429
|
+
|
|
430
|
+
# Consuming external events
|
|
431
|
+
listeners:
|
|
432
|
+
- event: OrderPlacedEvent
|
|
433
|
+
producer: orders
|
|
434
|
+
topic: ORDER_PLACED
|
|
435
|
+
useCase: UpdateProductStock
|
|
436
|
+
fields:
|
|
437
|
+
- name: orderId
|
|
438
|
+
type: String
|
|
439
|
+
- name: quantity
|
|
440
|
+
type: Integer
|
|
441
|
+
|
|
442
|
+
# Synchronous HTTP clients
|
|
443
|
+
ports:
|
|
444
|
+
- name: findCategoryById
|
|
445
|
+
service: CategoryService
|
|
446
|
+
target: categories
|
|
447
|
+
baseUrl: http://localhost:8040
|
|
448
|
+
http: GET /categories/{id}
|
|
449
|
+
fields:
|
|
450
|
+
- name: id
|
|
451
|
+
type: String
|
|
452
|
+
- name: name
|
|
453
|
+
type: String
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
`eva g entities product` generates from this single YAML: domain entity (no setters, no empty constructor), JPA entity, repository, lifecycle methods (`publish()`, `discontinue()`, `canPublish()`, `isPublished()`), Integration Event, `MessageBroker` port, `KafkaMessageBroker` adapter, consumer `KafkaListener`, `FeignClient` with ACL, `CreateProductCommand`, `ProductResponseDto`, Application↔Domain↔JPA mappers and `ProductController`.
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
### The AI → YAML → Code cycle
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
# 1. The AI agent generates the YAMLs from business requirements
|
|
464
|
+
# → system/system.yaml, system/product.yaml, system/notification.yaml
|
|
465
|
+
|
|
466
|
+
# 2. One command turns the entire specification into code
|
|
467
|
+
eva build
|
|
468
|
+
# ✅ Modules created with full hexagonal architecture
|
|
469
|
+
# ✅ Kafka configured and wired
|
|
470
|
+
# ✅ Entities, handlers, DTOs, mappers generated
|
|
471
|
+
# ✅ The project compiles and starts immediately
|
|
472
|
+
|
|
473
|
+
./gradlew bootRun # Endpoints already respond
|
|
474
|
+
|
|
475
|
+
# 3. The developer implements only the domain-specific business logic
|
|
476
|
+
# Handlers have UnsupportedOperationException as a visible placeholder
|
|
477
|
+
# Entities have the correct structure ready to be completed
|
|
478
|
+
|
|
479
|
+
# 4. When the domain evolves, update the YAMLs and regenerate
|
|
480
|
+
eva build # Checksums protect manual modifications
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### `eva build --mock` — Iterate without external infrastructure
|
|
484
|
+
|
|
485
|
+
Develop without needing to start Kafka or a real database:
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
eva build --mock # DB → H2 in-memory + Kafka → Spring Event bus
|
|
489
|
+
eva build --mock --only-broker # Broker only, keeps the configured database
|
|
490
|
+
eva build # Restores the original configuration
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
| Flag | Database | Broker |
|
|
494
|
+
|---|---|---|
|
|
495
|
+
| `eva build --mock` | H2 in-memory | Spring Event bus |
|
|
496
|
+
| `eva build --mock --only-broker` | Unchanged (PostgreSQL/MySQL) | Spring Event bus |
|
|
497
|
+
| `eva build` (restore) | Original | Kafka |
|
|
498
|
+
|
|
499
|
+
The original configuration is saved to `.eva4j.json` and restored automatically.
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## �📥 Installation
|
|
199
504
|
|
|
200
505
|
```bash
|
|
201
506
|
npm install -g eva4j
|
|
@@ -1147,6 +1452,149 @@ resources/parameters/
|
|
|
1147
1452
|
|
|
1148
1453
|
---
|
|
1149
1454
|
|
|
1455
|
+
## 📋 Centralized Logging (AOP)
|
|
1456
|
+
|
|
1457
|
+
eva4j generates a **cross-cutting logging system** based on Spring AOP. Instead of scattering log statements across your codebase, you annotate methods with the desired logging behavior. All logging logic lives in a single aspect (`HandlerLogs`), keeping your business code clean.
|
|
1458
|
+
|
|
1459
|
+
### Generated Artifacts
|
|
1460
|
+
|
|
1461
|
+
```
|
|
1462
|
+
shared/
|
|
1463
|
+
├── domain/annotations/
|
|
1464
|
+
│ ├── LogBefore.java # Log before method execution
|
|
1465
|
+
│ ├── LogAfter.java # Log after successful execution (includes return value)
|
|
1466
|
+
│ ├── LogExceptions.java # Log on exception (includes error message)
|
|
1467
|
+
│ ├── LogTimer.java # Measure execution time
|
|
1468
|
+
│ ├── Loggable.java # Unified annotation (combines all of the above)
|
|
1469
|
+
│ └── LogLevel.java # Enum: TRACE, DEBUG, INFO, WARN
|
|
1470
|
+
└── infrastructure/configurations/loggerConfig/
|
|
1471
|
+
└── HandlerLogs.java # Single aspect that handles all log annotations
|
|
1472
|
+
```
|
|
1473
|
+
|
|
1474
|
+
### Individual Annotations
|
|
1475
|
+
|
|
1476
|
+
#### `@LogBefore` — Log method entry
|
|
1477
|
+
|
|
1478
|
+
```java
|
|
1479
|
+
@LogBefore
|
|
1480
|
+
public User createUser(String username, String email) {
|
|
1481
|
+
return new User(username, email);
|
|
1482
|
+
}
|
|
1483
|
+
```
|
|
1484
|
+
```
|
|
1485
|
+
INFO ▶ Entering createUser with args: [username=john, email=john@mail.com]
|
|
1486
|
+
```
|
|
1487
|
+
|
|
1488
|
+
#### `@LogAfter` — Log successful completion with return value
|
|
1489
|
+
|
|
1490
|
+
```java
|
|
1491
|
+
@LogAfter
|
|
1492
|
+
public Order findOrder(String orderId) {
|
|
1493
|
+
return orderRepository.findById(orderId).orElseThrow();
|
|
1494
|
+
}
|
|
1495
|
+
```
|
|
1496
|
+
```
|
|
1497
|
+
INFO ◀ Completed findOrder with args: [orderId=ORD-123] | return: Order{id=ORD-123, status=CONFIRMED}
|
|
1498
|
+
```
|
|
1499
|
+
|
|
1500
|
+
#### `@LogExceptions` — Log failures with exception details
|
|
1501
|
+
|
|
1502
|
+
```java
|
|
1503
|
+
@LogExceptions
|
|
1504
|
+
public void processPayment(String orderId, BigDecimal amount) {
|
|
1505
|
+
// throws InsufficientFundsException
|
|
1506
|
+
}
|
|
1507
|
+
```
|
|
1508
|
+
```
|
|
1509
|
+
WARN ✖ Method processPayment failed with args: [orderId=ORD-123, amount=500.00] | exception: InsufficientFundsException | message: Balance insuficiente para procesar el pago
|
|
1510
|
+
```
|
|
1511
|
+
|
|
1512
|
+
#### `@LogTimer` — Measure execution time
|
|
1513
|
+
|
|
1514
|
+
```java
|
|
1515
|
+
@LogTimer
|
|
1516
|
+
public List<Product> searchProducts(String query) {
|
|
1517
|
+
return productRepository.fullTextSearch(query);
|
|
1518
|
+
}
|
|
1519
|
+
```
|
|
1520
|
+
```
|
|
1521
|
+
INFO ⏱ Method searchProducts executed in 342 ms
|
|
1522
|
+
```
|
|
1523
|
+
|
|
1524
|
+
### Protecting Sensitive Data with `excludeArgs`
|
|
1525
|
+
|
|
1526
|
+
All annotations (except `@LogTimer`) support `excludeArgs` to mask sensitive parameters. **By default all arguments are shown** — you only specify which ones to hide:
|
|
1527
|
+
|
|
1528
|
+
```java
|
|
1529
|
+
@LogBefore(excludeArgs = {"password", "token"})
|
|
1530
|
+
public void register(String username, String password, String token) {
|
|
1531
|
+
// ...
|
|
1532
|
+
}
|
|
1533
|
+
```
|
|
1534
|
+
```
|
|
1535
|
+
INFO ▶ Entering register with args: [username=john, password=[PROTECTED], token=[PROTECTED]]
|
|
1536
|
+
```
|
|
1537
|
+
|
|
1538
|
+
### Configurable Log Level
|
|
1539
|
+
|
|
1540
|
+
All annotations default to `INFO` (except `@LogExceptions` which defaults to `WARN`). Override with the `level` attribute:
|
|
1541
|
+
|
|
1542
|
+
```java
|
|
1543
|
+
@LogBefore(level = LogLevel.DEBUG)
|
|
1544
|
+
public void syncInventory(String warehouseId) {
|
|
1545
|
+
// frequent operation you don't want at INFO in production
|
|
1546
|
+
}
|
|
1547
|
+
```
|
|
1548
|
+
```
|
|
1549
|
+
DEBUG ▶ Entering syncInventory with args: [warehouseId=WH-001]
|
|
1550
|
+
```
|
|
1551
|
+
|
|
1552
|
+
### `@Loggable` — Unified Annotation
|
|
1553
|
+
|
|
1554
|
+
When you need multiple logging behaviors on a single method, use `@Loggable` instead of stacking individual annotations:
|
|
1555
|
+
|
|
1556
|
+
```java
|
|
1557
|
+
@Loggable(timer = true, excludeArgs = {"creditCard"})
|
|
1558
|
+
public PaymentResult checkout(String orderId, String creditCard) {
|
|
1559
|
+
return new PaymentResult("PAY-789", "APPROVED");
|
|
1560
|
+
}
|
|
1561
|
+
```
|
|
1562
|
+
|
|
1563
|
+
**Successful execution:**
|
|
1564
|
+
```
|
|
1565
|
+
INFO ▶ Entering checkout with args: [orderId=ORD-123, creditCard=[PROTECTED]]
|
|
1566
|
+
INFO ◀ Completed checkout with args: [orderId=ORD-123, creditCard=[PROTECTED]] | return: PaymentResult{id=PAY-789, status=APPROVED}
|
|
1567
|
+
INFO ⏱ Method checkout executed in 1205 ms
|
|
1568
|
+
```
|
|
1569
|
+
|
|
1570
|
+
**On failure:**
|
|
1571
|
+
```
|
|
1572
|
+
INFO ▶ Entering checkout with args: [orderId=ORD-123, creditCard=[PROTECTED]]
|
|
1573
|
+
WARN ✖ Method checkout failed with args: [orderId=ORD-123, creditCard=[PROTECTED]] | exception: PaymentDeclinedException | message: Card declined by issuer
|
|
1574
|
+
```
|
|
1575
|
+
|
|
1576
|
+
`@Loggable` attributes and their defaults:
|
|
1577
|
+
|
|
1578
|
+
| Attribute | Default | Description |
|
|
1579
|
+
|-----------|---------|-------------|
|
|
1580
|
+
| `before` | `true` | Log method entry |
|
|
1581
|
+
| `after` | `true` | Log successful return |
|
|
1582
|
+
| `exceptions` | `true` | Log on exception |
|
|
1583
|
+
| `timer` | `false` | Measure execution time |
|
|
1584
|
+
| `level` | `INFO` | Log level for before/after/timer |
|
|
1585
|
+
| `excludeArgs` | `{}` | Parameter names to mask as `[PROTECTED]` |
|
|
1586
|
+
|
|
1587
|
+
**Silent mode — only log failures and duration:**
|
|
1588
|
+
|
|
1589
|
+
```java
|
|
1590
|
+
@Loggable(before = false, after = false, exceptions = true, timer = true)
|
|
1591
|
+
public void importBulkData(List<String> records) {
|
|
1592
|
+
// silent batch process: only logs on failure or to measure duration
|
|
1593
|
+
}
|
|
1594
|
+
```
|
|
1595
|
+
|
|
1596
|
+
---
|
|
1597
|
+
|
|
1150
1598
|
## 🧪 Testing
|
|
1151
1599
|
|
|
1152
1600
|
```bash
|
package/bin/eva4j.js
CHANGED
|
@@ -16,10 +16,11 @@ const generateRecordCommand = require('../src/commands/generate-record');
|
|
|
16
16
|
const generateEntitiesCommand = require('../src/commands/generate-entities');
|
|
17
17
|
const generateTemporalFlowCommand = require('../src/commands/generate-temporal-flow');
|
|
18
18
|
const generateTemporalActivityCommand = require('../src/commands/generate-temporal-activity');
|
|
19
|
-
const
|
|
19
|
+
const buildCommand = require('../src/commands/build');
|
|
20
20
|
const evaluateSystemCommand = require('../src/commands/evaluate-system');
|
|
21
21
|
const infoCommand = require('../src/commands/info');
|
|
22
22
|
const detachCommand = require('../src/commands/detach');
|
|
23
|
+
const exportDiagramCommand = require('../src/commands/export-diagram');
|
|
23
24
|
|
|
24
25
|
const program = new Command();
|
|
25
26
|
|
|
@@ -256,16 +257,6 @@ program
|
|
|
256
257
|
return;
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
if (type === 'system') {
|
|
260
|
-
try {
|
|
261
|
-
await generateSystemCommand();
|
|
262
|
-
} catch (error) {
|
|
263
|
-
console.error(chalk.red('Error:'), error.message);
|
|
264
|
-
process.exit(1);
|
|
265
|
-
}
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
260
|
console.error(chalk.red(`❌ Unknown type: ${type}`));
|
|
270
261
|
console.log(chalk.yellow('\nUsage:'));
|
|
271
262
|
console.log(chalk.gray(' eva4j generate usecase <name> <module>'));
|
|
@@ -277,7 +268,6 @@ program
|
|
|
277
268
|
console.log(chalk.gray(' eva4j generate resource <module>'));
|
|
278
269
|
console.log(chalk.gray(' eva4j generate record'));
|
|
279
270
|
console.log(chalk.gray(' eva4j generate entities <module>'));
|
|
280
|
-
console.log(chalk.gray(' eva4j generate system'));
|
|
281
271
|
console.log(chalk.gray('\nExamples:'));
|
|
282
272
|
console.log(chalk.gray(' eva4j generate usecase create-provider provider'));
|
|
283
273
|
console.log(chalk.gray(' eva4j g http-exchange user-service-port user'));
|
|
@@ -287,11 +277,26 @@ program
|
|
|
287
277
|
console.log(chalk.gray(' eva4j g temporal-activity order register-order'));
|
|
288
278
|
console.log(chalk.gray(' eva4j g resource product'));
|
|
289
279
|
console.log(chalk.gray(' eva4j g record # Reads JSON from clipboard'));
|
|
290
|
-
console.log(chalk.gray(' eva4j g entities order # Generates from domain.yaml'));
|
|
291
|
-
console.log(chalk.gray(' eva4j g system # Bootstrap from system.yaml\n'));
|
|
280
|
+
console.log(chalk.gray(' eva4j g entities order # Generates from domain.yaml\n'));
|
|
292
281
|
process.exit(1);
|
|
293
282
|
});
|
|
294
283
|
|
|
284
|
+
// Build command
|
|
285
|
+
program
|
|
286
|
+
.command('build')
|
|
287
|
+
.description('Bootstrap project from system/system.yaml: create modules, install broker client, copy domain.yaml files, and generate entities')
|
|
288
|
+
.option('--force', 'Overwrite files even if they were manually modified (bypasses safe mode)')
|
|
289
|
+
.option('--mock', 'Replace database config with H2 in-memory before building (restored automatically after build)')
|
|
290
|
+
.option('--only-broker', 'Only switch the broker to Spring Event bus, keeping the current database unchanged (requires --mock)')
|
|
291
|
+
.action(async (options) => {
|
|
292
|
+
try {
|
|
293
|
+
await buildCommand(options);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error(chalk.red('Error:'), error.message);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
295
300
|
// Evaluate command
|
|
296
301
|
program
|
|
297
302
|
.command('evaluate <type>')
|
|
@@ -333,6 +338,19 @@ program
|
|
|
333
338
|
}
|
|
334
339
|
});
|
|
335
340
|
|
|
341
|
+
// Export command
|
|
342
|
+
program
|
|
343
|
+
.command('export <type>')
|
|
344
|
+
.description('Export system artifacts. type: diagram (generates .drawio from C4 .mmd files)')
|
|
345
|
+
.action(async (type, options) => {
|
|
346
|
+
try {
|
|
347
|
+
await exportDiagramCommand(type, options);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.error(chalk.red('Error:'), error.message);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
336
354
|
// Help command
|
|
337
355
|
program.on('--help', () => {
|
|
338
356
|
console.log('');
|