eva4j 1.0.12 → 1.0.13
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 +426 -9
- package/DOMAIN_YAML_GUIDE.md +374 -21
- package/FUTURE_FEATURES.md +94 -1
- package/docs/commands/GENERATE_ENTITIES.md +252 -2404
- package/docs/commands/GENERATE_RECORD.md +335 -311
- package/examples/doctor-evaluation.yaml +3 -3
- package/examples/domain-audit-complete.yaml +2 -2
- package/examples/domain-collections.yaml +2 -2
- package/examples/domain-ecommerce.yaml +2 -2
- package/examples/domain-events.yaml +1 -1
- package/examples/domain-field-visibility.yaml +11 -5
- package/examples/domain-multi-aggregate.yaml +6 -6
- package/examples/domain-one-to-many.yaml +1 -1
- package/examples/domain-one-to-one.yaml +1 -1
- package/examples/domain-secondary-onetomany.yaml +1 -1
- package/examples/domain-secondary-onetoone.yaml +1 -1
- package/examples/domain-simple.yaml +1 -1
- package/examples/domain-soft-delete.yaml +3 -3
- package/examples/domain-transitions.yaml +1 -1
- package/examples/domain-value-objects.yaml +1 -1
- package/package.json +1 -1
- package/src/utils/yaml-to-entity.js +69 -2
- package/templates/aggregate/AggregateRoot.java.ejs +5 -1
- package/templates/aggregate/DomainEntity.java.ejs +5 -1
- package/templates/aggregate/JpaAggregateRoot.java.ejs +2 -1
- package/templates/aggregate/JpaEntity.java.ejs +2 -1
- package/templates/base/root/AGENTS.md.ejs +916 -51
package/DOMAIN_YAML_GUIDE.md
CHANGED
|
@@ -49,9 +49,9 @@ Las entidades de dominio generadas siguen estrictamente los principios de Domain
|
|
|
49
49
|
- ✅ Protección de invariantes del dominio
|
|
50
50
|
|
|
51
51
|
**✅ Constructores sin Validaciones Automáticas:**
|
|
52
|
-
- Los constructores asignan valores directamente sin validaciones
|
|
53
|
-
- Las validaciones se
|
|
54
|
-
-
|
|
52
|
+
- Los constructores asignan valores directamente sin validaciones de Bean Validation
|
|
53
|
+
- Las validaciones JSR-303 se declaran en `domain.yaml` y se aplican en la capa de aplicación (Command y CreateDto), no en el dominio
|
|
54
|
+
- Las reglas de invariantes de dominio deben implementarse manualmente en los métodos de negocio
|
|
55
55
|
|
|
56
56
|
**📦 Inmutabilidad de Value Objects:**
|
|
57
57
|
- Campos declarados como `final`
|
|
@@ -132,17 +132,28 @@ aggregates:
|
|
|
132
132
|
# Enumeraciones del dominio
|
|
133
133
|
- name: EnumName
|
|
134
134
|
values: []
|
|
135
|
+
|
|
136
|
+
events:
|
|
137
|
+
# Eventos de dominio que emite este agregado
|
|
138
|
+
- name: NombreEventoOcurrido
|
|
139
|
+
fields: []
|
|
140
|
+
# kafka: true # opcional — genera publicación a Kafka vía MessageBroker
|
|
135
141
|
```
|
|
136
142
|
|
|
137
143
|
### Ubicación del archivo
|
|
138
144
|
|
|
139
145
|
```
|
|
140
146
|
tu-proyecto/
|
|
141
|
-
└──
|
|
142
|
-
└──
|
|
143
|
-
└──
|
|
147
|
+
└── src/
|
|
148
|
+
└── main/
|
|
149
|
+
└── java/
|
|
150
|
+
└── com/example/myapp/ ← packagePath (ej: com.example.myapp)
|
|
151
|
+
└── orders/ ← moduleName
|
|
152
|
+
└── domain.yaml ← Aquí
|
|
144
153
|
```
|
|
145
154
|
|
|
155
|
+
Eva4j espera el `domain.yaml` dentro de la carpeta del módulo, que se encuentra bajo la ruta del package Java. Esta ubicación es creada automáticamente al ejecutar `eva add module <nombre>`.
|
|
156
|
+
|
|
146
157
|
---
|
|
147
158
|
|
|
148
159
|
## Definición de Agregados
|
|
@@ -257,6 +268,13 @@ fields:
|
|
|
257
268
|
**Propiedades soportadas:**
|
|
258
269
|
- `name`: Nombre del campo (obligatorio)
|
|
259
270
|
- `type`: Tipo de dato Java (obligatorio)
|
|
271
|
+
- `readOnly`: Excluye del constructor de negocio y CreateDto (ver [Control de Visibilidad](#control-de-visibilidad-de-campos))
|
|
272
|
+
- `hidden`: Excluye del ResponseDto
|
|
273
|
+
- `defaultValue`: Valor inicial para campos `readOnly` (ver [Ó `defaultValue`](#-defaultvalue-valor-inicial-para-campos-readonly))
|
|
274
|
+
- `validations`: Anotaciones JSR-303 en Command y CreateDto
|
|
275
|
+
- `reference`: Referencia semántica a otro agregado
|
|
276
|
+
- `annotations`: Anotaciones JPA personalizadas
|
|
277
|
+
- `isValueObject` / `isEmbedded`: Marcas explícitas de Value Object
|
|
260
278
|
|
|
261
279
|
#### Detección automática de tipos
|
|
262
280
|
|
|
@@ -407,6 +425,7 @@ Eva4j permite controlar qué campos participan en constructores, DTOs de creaci
|
|
|
407
425
|
|-------|---------------------|----------------------|-----------|-------------|
|
|
408
426
|
| **Normal** | ✅ Incluido | ✅ Incluido | ✅ Incluido | ✅ Incluido |
|
|
409
427
|
| **`readOnly: true`** | ❌ Excluido | ✅ Incluido | ❌ Excluido | ✅ Incluido |
|
|
428
|
+
| **`readOnly` + `defaultValue`** | ⚡ Asignado con default | ✅ Incluido | ❌ Excluido | ✅ Incluido |
|
|
410
429
|
| **`hidden: true`** | ✅ Incluido | ✅ Incluido | ✅ Incluido | ❌ Excluido |
|
|
411
430
|
| **Ambos flags** | ❌ Excluido | ✅ Incluido | ❌ Excluido | ❌ Excluido |
|
|
412
431
|
|
|
@@ -488,6 +507,98 @@ public record OrderResponseDto(
|
|
|
488
507
|
) {}
|
|
489
508
|
```
|
|
490
509
|
|
|
510
|
+
#### 🎯 `defaultValue` - Valor Inicial para campos `readOnly`
|
|
511
|
+
|
|
512
|
+
Permite asignar un valor inicial predecible a un campo `readOnly` directamente en `domain.yaml`. El generador emite la asignación en el **constructor de creación** de la entidad de dominio y añade `@Builder.Default` en la entidad JPA.
|
|
513
|
+
|
|
514
|
+
**Restricción:** Solo aplica a campos `readOnly: true`. Si se usa sin `readOnly`, eva4j emite un warning y lo ignora.
|
|
515
|
+
|
|
516
|
+
**Casos de uso típicos:**
|
|
517
|
+
- Estado inicial de enums (`PENDING`, `ACTIVE`, `DRAFT`)
|
|
518
|
+
- Contadores que comienzan en cero
|
|
519
|
+
- Totales/acumuladores antes de cálculos
|
|
520
|
+
- Flags booleanos con estado conocido al crear
|
|
521
|
+
|
|
522
|
+
**Sintaxis:**
|
|
523
|
+
```yaml
|
|
524
|
+
fields:
|
|
525
|
+
- name: totalAmount
|
|
526
|
+
type: BigDecimal
|
|
527
|
+
readOnly: true
|
|
528
|
+
defaultValue: "0.00" # ✅ BigDecimal literal
|
|
529
|
+
|
|
530
|
+
- name: itemCount
|
|
531
|
+
type: Integer
|
|
532
|
+
readOnly: true
|
|
533
|
+
defaultValue: 0 # ✅ Integer literal
|
|
534
|
+
|
|
535
|
+
- name: status
|
|
536
|
+
type: OrderStatus
|
|
537
|
+
readOnly: true
|
|
538
|
+
defaultValue: PENDING # ✅ Enum value (sin comillas)
|
|
539
|
+
|
|
540
|
+
- name: isActive
|
|
541
|
+
type: Boolean
|
|
542
|
+
readOnly: true
|
|
543
|
+
defaultValue: true # ✅ Boolean literal
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
**Código generado en la entidad de dominio:**
|
|
547
|
+
```java
|
|
548
|
+
// Constructor de creación — defaultValues asignados automáticamente
|
|
549
|
+
public Order(String orderNumber, String customerId) {
|
|
550
|
+
this.orderNumber = orderNumber;
|
|
551
|
+
this.customerId = customerId;
|
|
552
|
+
this.totalAmount = new BigDecimal("0.00"); // ← defaultValue
|
|
553
|
+
this.itemCount = 0; // ← defaultValue
|
|
554
|
+
this.status = OrderStatus.PENDING; // ← defaultValue
|
|
555
|
+
this.isActive = true; // ← defaultValue
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Constructor completo (reconstrucción desde BD) — sin defaultValues
|
|
559
|
+
public Order(String id, String orderNumber, String customerId,
|
|
560
|
+
BigDecimal totalAmount, Integer itemCount,
|
|
561
|
+
OrderStatus status, Boolean isActive, ...) {
|
|
562
|
+
// Todos los campos asignados desde parámetros
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
**Código generado en la entidad JPA:**
|
|
567
|
+
```java
|
|
568
|
+
@Builder.Default
|
|
569
|
+
private BigDecimal totalAmount = new BigDecimal("0.00");
|
|
570
|
+
|
|
571
|
+
@Builder.Default
|
|
572
|
+
private Integer itemCount = 0;
|
|
573
|
+
|
|
574
|
+
@Enumerated(EnumType.STRING)
|
|
575
|
+
@Builder.Default
|
|
576
|
+
private OrderStatus status = OrderStatus.PENDING;
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
**Tipos Java soportados y su forma de emisión:**
|
|
580
|
+
|
|
581
|
+
| Tipo | Ejemplo YAML | Literal Java emitido |
|
|
582
|
+
|------|-------------|----------------------|
|
|
583
|
+
| `String` | `defaultValue: hello` | `"hello"` |
|
|
584
|
+
| `Integer` | `defaultValue: 0` | `0` |
|
|
585
|
+
| `Long` | `defaultValue: 0` | `0L` |
|
|
586
|
+
| `Boolean` | `defaultValue: false` | `false` |
|
|
587
|
+
| `BigDecimal` | `defaultValue: "0.00"` | `new BigDecimal("0.00")` |
|
|
588
|
+
| `LocalDateTime` | `defaultValue: now` | `LocalDateTime.now()` |
|
|
589
|
+
| `LocalDate` | `defaultValue: now` | `LocalDate.now()` |
|
|
590
|
+
| `Instant` | `defaultValue: now` | `Instant.now()` |
|
|
591
|
+
| `UUID` | `defaultValue: random` | `UUID.randomUUID()` |
|
|
592
|
+
| Enum | `defaultValue: ACTIVE` | `EnumType.ACTIVE` |
|
|
593
|
+
|
|
594
|
+
**Compatibilidad con otros flags:**
|
|
595
|
+
|
|
596
|
+
| Combinación | Constructor Creación | JPA Builder | CreateDto | ResponseDto |
|
|
597
|
+
|------------|:-------------------:|:-----------:|:---------:|:-----------:|
|
|
598
|
+
| `readOnly` + `defaultValue` | ⚡ Asignado con default | `@Builder.Default` | ❌ | ✅ |
|
|
599
|
+
| `readOnly` + `hidden` + `defaultValue` | ⚡ Asignado con default | `@Builder.Default` | ❌ | ❌ |
|
|
600
|
+
| `defaultValue` sin `readOnly` | *Ignorado (warning)* | *Ignorado* | — | — |
|
|
601
|
+
|
|
491
602
|
#### 🙈 `hidden: true` - Campos Sensibles/Internos
|
|
492
603
|
|
|
493
604
|
Marca campos que **NO deben exponerse** en respuestas de API pero sí pueden recibirse en creación.
|
|
@@ -1253,11 +1364,57 @@ valueObjects:
|
|
|
1253
1364
|
type: String
|
|
1254
1365
|
```
|
|
1255
1366
|
|
|
1367
|
+
### Value Objects con métodos de negocio
|
|
1368
|
+
|
|
1369
|
+
Los Value Objects pueden declarar métodos de negocio directamente en el `domain.yaml`. Estos se generan como métodos públicos en la clase del Value Object.
|
|
1370
|
+
|
|
1371
|
+
```yaml
|
|
1372
|
+
valueObjects:
|
|
1373
|
+
- name: Money
|
|
1374
|
+
fields:
|
|
1375
|
+
- name: amount
|
|
1376
|
+
type: BigDecimal
|
|
1377
|
+
- name: currency
|
|
1378
|
+
type: String
|
|
1379
|
+
methods:
|
|
1380
|
+
- name: add
|
|
1381
|
+
returnType: Money
|
|
1382
|
+
parameters:
|
|
1383
|
+
- name: other
|
|
1384
|
+
type: Money
|
|
1385
|
+
body: "return new Money(this.amount.add(other.getAmount()), this.currency);"
|
|
1386
|
+
|
|
1387
|
+
- name: isPositive
|
|
1388
|
+
returnType: boolean
|
|
1389
|
+
parameters: []
|
|
1390
|
+
body: "return this.amount.compareTo(BigDecimal.ZERO) > 0;"
|
|
1391
|
+
```
|
|
1392
|
+
|
|
1393
|
+
**Código generado:**
|
|
1394
|
+
```java
|
|
1395
|
+
public Money add(Money other) {
|
|
1396
|
+
return new Money(this.amount.add(other.getAmount()), this.currency);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
public boolean isPositive() {
|
|
1400
|
+
return this.amount.compareTo(BigDecimal.ZERO) > 0;
|
|
1401
|
+
}
|
|
1402
|
+
```
|
|
1403
|
+
|
|
1404
|
+
**Propiedades de un método:**
|
|
1405
|
+
|
|
1406
|
+
| Propiedad | Descripción |
|
|
1407
|
+
|-----------|-------------|
|
|
1408
|
+
| `name` | Nombre del método |
|
|
1409
|
+
| `returnType` | Tipo de retorno Java |
|
|
1410
|
+
| `parameters` | Array de `{ name, type }` |
|
|
1411
|
+
| `body` | Cuerpo del método (string Java) |
|
|
1412
|
+
|
|
1256
1413
|
---
|
|
1257
1414
|
|
|
1258
1415
|
## Enums
|
|
1259
1416
|
|
|
1260
|
-
### Definición
|
|
1417
|
+
### Definición básica
|
|
1261
1418
|
|
|
1262
1419
|
```yaml
|
|
1263
1420
|
enums:
|
|
@@ -1314,6 +1471,199 @@ enums:
|
|
|
1314
1471
|
|
|
1315
1472
|
---
|
|
1316
1473
|
|
|
1474
|
+
### Enums con Transiciones de Estado
|
|
1475
|
+
|
|
1476
|
+
Cuando un enum representa un ciclo de vida de negocio, puede declarar `transitions` e `initialValue`. Eva4j genera automáticamente los métodos de transición, guards, helpers de consulta y el mapa de transiciones válidas.
|
|
1477
|
+
|
|
1478
|
+
#### Sintaxis
|
|
1479
|
+
|
|
1480
|
+
```yaml
|
|
1481
|
+
enums:
|
|
1482
|
+
- name: OrderStatus
|
|
1483
|
+
initialValue: PENDING # ← Estado inicial (se asigna en el constructor de creación)
|
|
1484
|
+
transitions:
|
|
1485
|
+
- from: PENDING
|
|
1486
|
+
to: CONFIRMED
|
|
1487
|
+
method: confirm
|
|
1488
|
+
|
|
1489
|
+
- from: CONFIRMED
|
|
1490
|
+
to: SHIPPED
|
|
1491
|
+
method: ship
|
|
1492
|
+
|
|
1493
|
+
- from: SHIPPED
|
|
1494
|
+
to: DELIVERED
|
|
1495
|
+
method: deliver
|
|
1496
|
+
|
|
1497
|
+
- from: [PENDING, CONFIRMED] # múltiples estados origen
|
|
1498
|
+
to: CANCELLED
|
|
1499
|
+
method: cancel
|
|
1500
|
+
guard: "this.status == OrderStatus.DELIVERED" # lanza BusinessException si se cumple
|
|
1501
|
+
|
|
1502
|
+
values:
|
|
1503
|
+
- PENDING
|
|
1504
|
+
- CONFIRMED
|
|
1505
|
+
- SHIPPED
|
|
1506
|
+
- DELIVERED
|
|
1507
|
+
- CANCELLED
|
|
1508
|
+
```
|
|
1509
|
+
|
|
1510
|
+
#### Propiedades de transición
|
|
1511
|
+
|
|
1512
|
+
| Propiedad | Tipo | Descripción |
|
|
1513
|
+
|-----------|------|-------------|
|
|
1514
|
+
| `from` | String \| Array | Estado(s) de origen válidos |
|
|
1515
|
+
| `to` | String | Estado destino |
|
|
1516
|
+
| `method` | String | Nombre del método que ejecuta la transición |
|
|
1517
|
+
| `guard` | String | Condición Java que lanza `BusinessException` si se cumple (opcional) |
|
|
1518
|
+
|
|
1519
|
+
#### Qué genera
|
|
1520
|
+
|
|
1521
|
+
**En el enum (`OrderStatus.java`):**
|
|
1522
|
+
```java
|
|
1523
|
+
public enum OrderStatus {
|
|
1524
|
+
PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED;
|
|
1525
|
+
|
|
1526
|
+
private static final Map<OrderStatus, Set<OrderStatus>> VALID_TRANSITIONS;
|
|
1527
|
+
// ... mapa estático inicializado
|
|
1528
|
+
|
|
1529
|
+
public boolean canTransitionTo(OrderStatus target) { ... }
|
|
1530
|
+
public OrderStatus transitionTo(OrderStatus target) { ... } // lanza InvalidStateTransitionException
|
|
1531
|
+
}
|
|
1532
|
+
```
|
|
1533
|
+
|
|
1534
|
+
**En la entidad raíz (`Order.java`):**
|
|
1535
|
+
```java
|
|
1536
|
+
// El constructor de creación NO recibe status (se auto-inicializa a PENDING)
|
|
1537
|
+
public Order(String orderNumber, String customerId) {
|
|
1538
|
+
this.orderNumber = orderNumber;
|
|
1539
|
+
this.customerId = customerId;
|
|
1540
|
+
this.status = OrderStatus.PENDING; // ← initialValue aplicado
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// Métodos de transición generados
|
|
1544
|
+
public void confirm() { this.status = this.status.transitionTo(OrderStatus.CONFIRMED); }
|
|
1545
|
+
public void ship() { this.status = this.status.transitionTo(OrderStatus.SHIPPED); }
|
|
1546
|
+
public void deliver() { this.status = this.status.transitionTo(OrderStatus.DELIVERED); }
|
|
1547
|
+
public void cancel() {
|
|
1548
|
+
if (this.status == OrderStatus.DELIVERED) {
|
|
1549
|
+
throw new BusinessException("Cannot execute 'cancel': business rule violated");
|
|
1550
|
+
}
|
|
1551
|
+
this.status = this.status.transitionTo(OrderStatus.CANCELLED);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// Helpers de consulta de estado
|
|
1555
|
+
public boolean isPending() { return this.status == OrderStatus.PENDING; }
|
|
1556
|
+
public boolean isConfirmed() { return this.status == OrderStatus.CONFIRMED; }
|
|
1557
|
+
// ... uno por cada valor del enum
|
|
1558
|
+
|
|
1559
|
+
// Helpers de disponibilidad de transición
|
|
1560
|
+
public boolean canConfirm() { return this.status.canTransitionTo(OrderStatus.CONFIRMED); }
|
|
1561
|
+
public boolean canCancel() { return this.status.canTransitionTo(OrderStatus.CANCELLED); }
|
|
1562
|
+
// ... uno por cada método de transición
|
|
1563
|
+
```
|
|
1564
|
+
|
|
1565
|
+
**Nota:** El campo con `initialValue` se trata implícitamente como `readOnly: true` — no aparece en el constructor de negocio ni en el `CreateDto`.
|
|
1566
|
+
|
|
1567
|
+
---
|
|
1568
|
+
|
|
1569
|
+
## Eventos de Dominio
|
|
1570
|
+
|
|
1571
|
+
Los eventos de dominio representan hechos significativos que ocurren dentro del agregado. Eva4j genera las clases de evento y el handler para publicarlos automáticamente tras commit de transacción.
|
|
1572
|
+
|
|
1573
|
+
### Sintaxis
|
|
1574
|
+
|
|
1575
|
+
```yaml
|
|
1576
|
+
aggregates:
|
|
1577
|
+
- name: Order
|
|
1578
|
+
entities:
|
|
1579
|
+
- name: order
|
|
1580
|
+
isRoot: true
|
|
1581
|
+
tableName: orders
|
|
1582
|
+
fields:
|
|
1583
|
+
- name: id
|
|
1584
|
+
type: String
|
|
1585
|
+
- name: orderNumber
|
|
1586
|
+
type: String
|
|
1587
|
+
|
|
1588
|
+
events:
|
|
1589
|
+
- name: OrderConfirmedEvent # PascalCase, idealmente en pasado
|
|
1590
|
+
fields:
|
|
1591
|
+
- name: orderId
|
|
1592
|
+
type: String
|
|
1593
|
+
- name: confirmedAt
|
|
1594
|
+
type: LocalDateTime
|
|
1595
|
+
|
|
1596
|
+
- name: OrderShippedEvent
|
|
1597
|
+
kafka: true # opcional — genera publicación a Kafka
|
|
1598
|
+
fields:
|
|
1599
|
+
- name: orderId
|
|
1600
|
+
type: String
|
|
1601
|
+
- name: trackingNumber
|
|
1602
|
+
type: String
|
|
1603
|
+
```
|
|
1604
|
+
|
|
1605
|
+
### Propiedades de un evento
|
|
1606
|
+
|
|
1607
|
+
| Propiedad | Tipo | Descripción |
|
|
1608
|
+
|-----------|------|-------------|
|
|
1609
|
+
| `name` | String | Nombre de la clase del evento (PascalCase) |
|
|
1610
|
+
| `fields` | Array | Campos que transporta el evento |
|
|
1611
|
+
| `kafka` | Boolean | Si `true`, genera llamada a `messageBroker.send{EventName}()` |
|
|
1612
|
+
|
|
1613
|
+
### Archivos generados
|
|
1614
|
+
|
|
1615
|
+
Para cada evento, eva4j genera dos archivos:
|
|
1616
|
+
|
|
1617
|
+
**1. `OrderConfirmedEvent.java`** — en `domain/models/events/`
|
|
1618
|
+
```java
|
|
1619
|
+
public final class OrderConfirmedEvent extends DomainEvent {
|
|
1620
|
+
private final String orderId;
|
|
1621
|
+
private final LocalDateTime confirmedAt;
|
|
1622
|
+
|
|
1623
|
+
public OrderConfirmedEvent(String aggregateId, String orderId, LocalDateTime confirmedAt) {
|
|
1624
|
+
super(aggregateId);
|
|
1625
|
+
this.orderId = orderId;
|
|
1626
|
+
this.confirmedAt = confirmedAt;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
public String getOrderId() { return orderId; }
|
|
1630
|
+
public LocalDateTime getConfirmedAt() { return confirmedAt; }
|
|
1631
|
+
}
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
**2. `OrderDomainEventHandler.java`** — en `application/usecases/`
|
|
1635
|
+
```java
|
|
1636
|
+
@Component
|
|
1637
|
+
public class OrderDomainEventHandler {
|
|
1638
|
+
|
|
1639
|
+
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
|
1640
|
+
public void handle(OrderConfirmedEvent event) {
|
|
1641
|
+
// Lógica post-commit — ej: notificaciones, métricas
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
|
1645
|
+
public void handle(OrderShippedEvent event) {
|
|
1646
|
+
messageBroker.sendOrderShippedEvent(event); // kafka: true
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
```
|
|
1650
|
+
|
|
1651
|
+
### Cómo publicar un evento
|
|
1652
|
+
|
|
1653
|
+
Los eventos se publican desde métodos de negocio de la entidad raíz usando el método heredado `raise()`:
|
|
1654
|
+
|
|
1655
|
+
```java
|
|
1656
|
+
// En Order.java (entidad de dominio)
|
|
1657
|
+
public void confirm() {
|
|
1658
|
+
this.status = this.status.transitionTo(OrderStatus.CONFIRMED);
|
|
1659
|
+
raise(new OrderConfirmedEvent(this.id, this.id, LocalDateTime.now()));
|
|
1660
|
+
}
|
|
1661
|
+
```
|
|
1662
|
+
|
|
1663
|
+
> **Nota:** `raise()` es provisto por la clase base de dominio. La publicación real ocurre tras el commit de la transacción gracias a `@TransactionalEventListener(AFTER_COMMIT)`.
|
|
1664
|
+
|
|
1665
|
+
---
|
|
1666
|
+
|
|
1317
1667
|
## Relaciones
|
|
1318
1668
|
|
|
1319
1669
|
eva4j soporta relaciones JPA bidireccionales completas con generación automática del lado inverso.
|
|
@@ -2498,22 +2848,26 @@ eva4j generate entities <module-name>
|
|
|
2498
2848
|
### ✅ Soportado
|
|
2499
2849
|
|
|
2500
2850
|
- Agregados con entidad raíz y secundarias
|
|
2501
|
-
- Value Objects embebidos
|
|
2502
|
-
- Enums con
|
|
2503
|
-
- Relaciones OneToMany, ManyToOne, OneToOne
|
|
2851
|
+
- Value Objects embebidos (con `methods` opcionales)
|
|
2852
|
+
- Enums simples y con transiciones de estado (`transitions`, `initialValue`)
|
|
2853
|
+
- Relaciones OneToMany, ManyToOne, OneToOne (bidireccionales automáticas)
|
|
2504
2854
|
- Tipos primitivos y de fecha Java
|
|
2505
|
-
- Colecciones de primitivos y VOs
|
|
2855
|
+
- Colecciones de primitivos y VOs (`List<T>`)
|
|
2506
2856
|
- IDs: String (UUID), Long/Integer (IDENTITY)
|
|
2507
2857
|
- Cascade y Fetch personalizados
|
|
2858
|
+
- Validaciones JSR-303 en Command y CreateDto
|
|
2859
|
+
- Auditoría automática (`audit.enabled`, `audit.trackUser`)
|
|
2860
|
+
- Control de visibilidad de campos (`readOnly`, `hidden`, `defaultValue`)
|
|
2861
|
+
- Referencias cross-agregado (`reference:`)
|
|
2862
|
+
- Domain Events (`events:` con soporte opcional de Kafka)
|
|
2863
|
+
- Soft delete a nivel de módulo (configurado en `eva add module`)
|
|
2508
2864
|
|
|
2509
2865
|
### 🚧 Próximamente
|
|
2510
2866
|
|
|
2511
|
-
-
|
|
2512
|
-
-
|
|
2513
|
-
-
|
|
2514
|
-
-
|
|
2515
|
-
- Índices y constraints
|
|
2516
|
-
- Herencia de entidades
|
|
2867
|
+
- Query methods personalizados en repositorios
|
|
2868
|
+
- Índices y constraints de BD declarados en YAML
|
|
2869
|
+
- Herencia de entidades JPA
|
|
2870
|
+
- Soporte de `Instant` como tipo de campo (actualmente solo para `defaultValue`)
|
|
2517
2871
|
|
|
2518
2872
|
---
|
|
2519
2873
|
|
|
@@ -2529,7 +2883,7 @@ R: Los enums son globales al módulo, solo usa el nombre: `type: OrderStatus`
|
|
|
2529
2883
|
R: Sí, pero debes definirlo en cada agregado (por ahora).
|
|
2530
2884
|
|
|
2531
2885
|
**P: ¿Qué pasa si regenero el código?**
|
|
2532
|
-
R:
|
|
2886
|
+
R: Eva4j usa checksums SHA-256 para detectar archivos que fueron modificados manualmente. Los archivos con cambios manuales **no se sobreescriben** — se muestra un aviso y se omiten. Usa `--force` para forzar la sobreescritura de todos los archivos.
|
|
2533
2887
|
|
|
2534
2888
|
**P: ¿Puedo personalizar las entidades generadas?**
|
|
2535
2889
|
R: Sí, modifica las plantillas en `templates/aggregate/`.
|
|
@@ -2538,10 +2892,9 @@ R: Sí, modifica las plantillas en `templates/aggregate/`.
|
|
|
2538
2892
|
|
|
2539
2893
|
## Recursos Adicionales
|
|
2540
2894
|
|
|
2541
|
-
- [Guía de Implementación](IMPLEMENTATION_SUMMARY.md)
|
|
2542
|
-
- [Guía de Testing](TESTING_GUIDE.md)
|
|
2543
2895
|
- [Referencia Rápida](QUICK_REFERENCE.md)
|
|
2544
|
-
- [
|
|
2896
|
+
- [Guía de Agentes IA](AGENTS.md)
|
|
2897
|
+
- [Características Futuras](FUTURE_FEATURES.md)
|
|
2545
2898
|
|
|
2546
2899
|
---
|
|
2547
2900
|
|
package/FUTURE_FEATURES.md
CHANGED
|
@@ -967,6 +967,98 @@ private Integer age;
|
|
|
967
967
|
|
|
968
968
|
---
|
|
969
969
|
|
|
970
|
+
## 16. `defaultValue` para campos `readOnly` (Implementado)
|
|
971
|
+
|
|
972
|
+
Permite especificar un valor inicial para campos `readOnly` directamente en `domain.yaml`. El valor se emite en el **constructor de creación** de la entidad de dominio y como field initializer con `@Builder.Default` en la entidad JPA.
|
|
973
|
+
|
|
974
|
+
### Sintaxis
|
|
975
|
+
|
|
976
|
+
```yaml
|
|
977
|
+
entities:
|
|
978
|
+
- name: order
|
|
979
|
+
fields:
|
|
980
|
+
- name: status
|
|
981
|
+
type: OrderStatus
|
|
982
|
+
readOnly: true
|
|
983
|
+
defaultValue: PENDING # Enum value
|
|
984
|
+
|
|
985
|
+
- name: totalAmount
|
|
986
|
+
type: BigDecimal
|
|
987
|
+
readOnly: true
|
|
988
|
+
defaultValue: "0.00" # BigDecimal literal
|
|
989
|
+
|
|
990
|
+
- name: itemCount
|
|
991
|
+
type: Integer
|
|
992
|
+
readOnly: true
|
|
993
|
+
defaultValue: 0 # Integer literal
|
|
994
|
+
|
|
995
|
+
- name: isActive
|
|
996
|
+
type: Boolean
|
|
997
|
+
readOnly: true
|
|
998
|
+
defaultValue: true # Boolean literal
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
### Código Generado — Dominio
|
|
1002
|
+
|
|
1003
|
+
```java
|
|
1004
|
+
// Constructor de creación — defaultValue asignado automáticamente
|
|
1005
|
+
public Order(String orderNumber, String customerId) {
|
|
1006
|
+
this.orderNumber = orderNumber;
|
|
1007
|
+
this.customerId = customerId;
|
|
1008
|
+
// readOnly fields initialized with defaultValue:
|
|
1009
|
+
this.status = OrderStatus.PENDING;
|
|
1010
|
+
this.totalAmount = new BigDecimal("0.00");
|
|
1011
|
+
this.itemCount = 0;
|
|
1012
|
+
this.isActive = true;
|
|
1013
|
+
}
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
### Código Generado — JPA
|
|
1017
|
+
|
|
1018
|
+
```java
|
|
1019
|
+
@Builder.Default
|
|
1020
|
+
private OrderStatus status = OrderStatus.PENDING;
|
|
1021
|
+
|
|
1022
|
+
@Builder.Default
|
|
1023
|
+
private BigDecimal totalAmount = new BigDecimal("0.00");
|
|
1024
|
+
|
|
1025
|
+
@Builder.Default
|
|
1026
|
+
private Integer itemCount = 0;
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
### Tipos soportados
|
|
1030
|
+
|
|
1031
|
+
| Tipo Java | Ejemplo YAML | Java emitido |
|
|
1032
|
+
|-----------|-------------|---------------|
|
|
1033
|
+
| `String` | `defaultValue: hello` | `"hello"` |
|
|
1034
|
+
| `Integer` / `Long` | `defaultValue: 0` | `0` / `0L` |
|
|
1035
|
+
| `Boolean` | `defaultValue: false` | `false` |
|
|
1036
|
+
| `BigDecimal` | `defaultValue: "0.00"` | `new BigDecimal("0.00")` |
|
|
1037
|
+
| `LocalDateTime` | `defaultValue: now` | `LocalDateTime.now()` |
|
|
1038
|
+
| `LocalDate` | `defaultValue: now` | `LocalDate.now()` |
|
|
1039
|
+
| `Instant` | `defaultValue: now` | `Instant.now()` |
|
|
1040
|
+
| `UUID` | `defaultValue: random` | `UUID.randomUUID()` |
|
|
1041
|
+
| Enum | `defaultValue: ACTIVE` | `EnumType.ACTIVE` |
|
|
1042
|
+
|
|
1043
|
+
### Reglas
|
|
1044
|
+
|
|
1045
|
+
- `defaultValue` **solo es válido** en campos con `readOnly: true`. Si se usa en un campo no-readOnly, se emite un warning y se ignora.
|
|
1046
|
+
- El campo **sigue siendo readOnly** — no aparece en el constructor de negocio ni en `CreateDto`.
|
|
1047
|
+
- En campos con `autoInit` (enum con `initialValue`), `defaultValue` es ignorado — `autoInit` tiene precedencia.
|
|
1048
|
+
|
|
1049
|
+
### Archivos Modificados
|
|
1050
|
+
|
|
1051
|
+
| Archivo | Cambio |
|
|
1052
|
+
|---|---|
|
|
1053
|
+
| `src/utils/yaml-to-entity.js` | ✅ `computeJavaDefaultValue()` + `defaultValue` en `parseProperty()` |
|
|
1054
|
+
| `templates/aggregate/AggregateRoot.java.ejs` | ✅ Emite `this.field = defaultValue` en constructor de creación |
|
|
1055
|
+
| `templates/aggregate/DomainEntity.java.ejs` | ✅ Mismo cambio para entidades secundarias |
|
|
1056
|
+
| `templates/aggregate/JpaAggregateRoot.java.ejs` | ✅ `@Builder.Default` + field initializer |
|
|
1057
|
+
| `templates/aggregate/JpaEntity.java.ejs` | ✅ Mismo cambio para entidades JPA secundarias |
|
|
1058
|
+
| `examples/domain-field-visibility.yaml` | ✅ Ejemplos con `defaultValue` en campos readOnly |
|
|
1059
|
+
|
|
1060
|
+
---
|
|
1061
|
+
|
|
970
1062
|
## 15. Transactional Outbox Pattern
|
|
971
1063
|
|
|
972
1064
|
### Descripción
|
|
@@ -1067,9 +1159,10 @@ Domain Events (ítem 1) implementados y funcionando — este ítem solo añade p
|
|
|
1067
1159
|
| 13 | Auditoria completa | Impl. | -- | ✅ Implementado |
|
|
1068
1160
|
| 14 | Validaciones JSR-303 | Impl. | -- | ✅ Implementado |
|
|
1069
1161
|
| 15 | Transactional Outbox Pattern | Alta | Alta | Pendiente |
|
|
1162
|
+
| 16 | `defaultValue` para campos `readOnly` | Impl. | -- | ✅ Implementado |
|
|
1070
1163
|
|
|
1071
1164
|
---
|
|
1072
1165
|
|
|
1073
|
-
**Ultima actualizacion:** 2026-
|
|
1166
|
+
**Ultima actualizacion:** 2026-03-04
|
|
1074
1167
|
**Version de eva4j:** 1.x
|
|
1075
1168
|
**Estado:** Documento de planificacion y referencia
|