eva4j 1.0.10 → 1.0.12
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 +102 -11
- package/DOMAIN_YAML_GUIDE.md +444 -0
- package/FUTURE_FEATURES.md +1075 -556
- package/QUICK_REFERENCE.md +101 -153
- package/README.md +80 -73
- package/bin/eva4j.js +58 -1
- package/config/defaults.json +3 -0
- package/docs/commands/GENERATE_ENTITIES.md +886 -40
- package/docs/commands/GENERATE_HTTP_EXCHANGE.md +274 -450
- package/docs/commands/GENERATE_KAFKA_EVENT.md +219 -498
- package/docs/commands/GENERATE_KAFKA_LISTENER.md +18 -18
- package/docs/commands/GENERATE_TEMPORAL_ACTIVITY.md +174 -0
- package/docs/commands/GENERATE_TEMPORAL_FLOW.md +237 -0
- package/docs/commands/GENERATE_USECASE.md +216 -282
- package/docs/commands/INDEX.md +36 -7
- package/examples/domain-events.yaml +201 -0
- package/examples/domain-field-visibility.yaml +235 -0
- package/examples/domain-multi-aggregate.yaml +6 -0
- package/examples/domain-multiple-relations.yaml +4 -4
- package/examples/domain-one-to-many.yaml +1 -1
- package/examples/domain-one-to-one.yaml +6 -3
- package/examples/domain-secondary-onetomany.yaml +106 -0
- package/examples/domain-secondary-onetoone.yaml +98 -0
- package/examples/domain-transitions.yaml +60 -0
- package/package.json +5 -4
- package/prettier.config.js +9 -0
- package/src/commands/add-kafka-client.js +3 -1
- package/src/commands/add-temporal-client.js +286 -0
- package/src/commands/detach.js +57 -0
- package/src/commands/generate-entities.js +333 -61
- package/src/commands/generate-kafka-event.js +276 -89
- package/src/commands/generate-resource.js +22 -25
- package/src/commands/generate-temporal-activity.js +228 -0
- package/src/commands/generate-temporal-flow.js +216 -0
- package/src/commands/generate-usecase.js +17 -41
- package/src/generators/base-generator.js +2 -1
- package/src/generators/module-generator.js +1 -0
- package/src/generators/shared-generator.js +46 -2
- package/src/utils/checksum-manager.js +130 -0
- package/src/utils/template-engine.js +63 -5
- package/src/utils/yaml-to-entity.js +308 -19
- package/templates/aggregate/AggregateMapper.java.ejs +52 -10
- package/templates/aggregate/AggregateRepository.java.ejs +3 -2
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +15 -7
- package/templates/aggregate/AggregateRoot.java.ejs +107 -5
- package/templates/aggregate/DomainEntity.java.ejs +101 -4
- package/templates/aggregate/DomainEventHandler.java.ejs +62 -0
- package/templates/aggregate/DomainEventRecord.java.ejs +50 -0
- package/templates/aggregate/Enum.java.ejs +49 -1
- package/templates/aggregate/JpaAggregateRoot.java.ejs +1 -0
- package/templates/aggregate/JpaEntity.java.ejs +12 -3
- package/templates/base/application/Application.java.ejs +1 -1
- package/templates/base/docker/kafka-services.yaml.ejs +2 -2
- package/templates/base/docker/temporal-services.yaml.ejs +29 -0
- package/templates/base/resources/parameters/develop/temporal.yaml.ejs +9 -0
- package/templates/base/resources/parameters/local/temporal.yaml.ejs +9 -0
- package/templates/base/resources/parameters/production/temporal.yaml.ejs +9 -0
- package/templates/base/resources/parameters/test/temporal.yaml.ejs +9 -0
- package/templates/base/root/gitignore.ejs +3 -0
- package/templates/crud/ApplicationMapper.java.ejs +156 -9
- package/templates/crud/Controller.java.ejs +36 -6
- package/templates/crud/CreateCommand.java.ejs +7 -0
- package/templates/crud/CreateCommandHandler.java.ejs +55 -3
- package/templates/crud/CreateItemDto.java.ejs +10 -2
- package/templates/crud/CreateValueObjectDto.java.ejs +18 -0
- package/templates/crud/ListQuery.java.ejs +6 -2
- package/templates/crud/ListQueryHandler.java.ejs +24 -10
- package/templates/crud/SecondaryEntityDto.java.ejs +7 -2
- package/templates/crud/UpdateCommand.java.ejs +52 -0
- package/templates/crud/UpdateCommandHandler.java.ejs +105 -0
- package/templates/kafka-event/DomainEventHandlerMethod.ejs +1 -0
- package/templates/kafka-event/Event.java.ejs +23 -0
- package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
- package/templates/postman/Collection.json.ejs +95 -34
- package/templates/shared/application/dtos/PagedResponse.java.ejs +30 -0
- package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +104 -0
- package/templates/shared/customExceptions/BusinessException.java.ejs +11 -0
- package/templates/shared/customExceptions/InvalidStateTransitionException.java.ejs +11 -0
- package/templates/shared/domain/DomainEvent.java.ejs +40 -0
- package/templates/shared/handlerException/HandlerExceptions.java.ejs +16 -0
- package/templates/shared/interfaces/HeavyActivity.java.ejs +4 -0
- package/templates/shared/interfaces/LightActivity.java.ejs +4 -0
- package/templates/temporal-activity/ActivityImpl.java.ejs +14 -0
- package/templates/temporal-activity/ActivityInterface.java.ejs +11 -0
- package/templates/temporal-flow/WorkFlowImpl.java.ejs +64 -0
- package/templates/temporal-flow/WorkFlowInterface.java.ejs +19 -0
- package/templates/temporal-flow/WorkFlowService.java.ejs +49 -0
package/AGENTS.md
CHANGED
|
@@ -387,19 +387,28 @@ void assignUser(User user) { // package-private
|
|
|
387
387
|
|
|
388
388
|
```bash
|
|
389
389
|
# Crear proyecto
|
|
390
|
-
|
|
390
|
+
eva create my-app
|
|
391
391
|
|
|
392
392
|
# Agregar módulo
|
|
393
|
-
|
|
393
|
+
eva add module users
|
|
394
394
|
|
|
395
395
|
# Generar entidades desde YAML
|
|
396
|
-
|
|
396
|
+
eva g entities users
|
|
397
397
|
|
|
398
398
|
# Generar use case
|
|
399
|
-
|
|
399
|
+
eva g usecase users ActivateUser
|
|
400
400
|
|
|
401
401
|
# Generar resource (REST)
|
|
402
|
-
|
|
402
|
+
eva g resource users
|
|
403
|
+
|
|
404
|
+
# Agregar cliente Temporal
|
|
405
|
+
eva add temporal-client
|
|
406
|
+
|
|
407
|
+
# Generar workflow Temporal
|
|
408
|
+
eva g temporal-flow users
|
|
409
|
+
|
|
410
|
+
# Generar actividad Temporal
|
|
411
|
+
eva g temporal-activity users
|
|
403
412
|
```
|
|
404
413
|
|
|
405
414
|
### Estructura de domain.yaml
|
|
@@ -543,6 +552,80 @@ public record UserResponseDto(
|
|
|
543
552
|
| Instant | Instant | Timestamp UTC |
|
|
544
553
|
| UUID | UUID | Identificador único |
|
|
545
554
|
|
|
555
|
+
### Propiedades de Campo
|
|
556
|
+
|
|
557
|
+
Los campos en domain.yaml soportan las siguientes propiedades:
|
|
558
|
+
|
|
559
|
+
| Propiedad | Tipo | Default | Descripción |
|
|
560
|
+
|-----------|------|---------|-------------|
|
|
561
|
+
| `name` | String | - | Nombre del campo (obligatorio) |
|
|
562
|
+
| `type` | String | - | Tipo de dato Java (obligatorio) |
|
|
563
|
+
| `annotations` | Array | `[]` | Anotaciones JPA personalizadas |
|
|
564
|
+
| `isValueObject` | Boolean | `false` | Marca explícita de Value Object |
|
|
565
|
+
| `isEmbedded` | Boolean | `false` | Marca explícita de @Embedded |
|
|
566
|
+
| `enumValues` | Array | `[]` | Valores inline de enum |
|
|
567
|
+
| **`readOnly`** | Boolean | `false` | **Excluye del constructor de negocio y CreateDto** |
|
|
568
|
+
| **`hidden`** | Boolean | `false` | **Excluye del ResponseDto** |
|
|
569
|
+
| **`validations`** | Array | `[]` | **Anotaciones JSR-303 en el Command y CreateDto** |
|
|
570
|
+
| **`reference`** | Object | `null` | **Declara referencia semántica a otro agregado (genera comentario Javadoc)** |
|
|
571
|
+
|
|
572
|
+
#### Flags de Visibilidad: `readOnly` y `hidden`
|
|
573
|
+
|
|
574
|
+
**`readOnly: true`** - Campos calculados/derivados
|
|
575
|
+
- ❌ Excluido de: Constructor de negocio, CreateDto
|
|
576
|
+
- ✅ Incluido en: Constructor completo, ResponseDto
|
|
577
|
+
- **Uso:** Totales calculados, contadores, campos derivados
|
|
578
|
+
|
|
579
|
+
```yaml
|
|
580
|
+
fields:
|
|
581
|
+
- name: totalAmount
|
|
582
|
+
type: BigDecimal
|
|
583
|
+
readOnly: true # Calculado de la suma de items
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
**`hidden: true`** - Campos sensibles/internos
|
|
587
|
+
- ❌ Excluido de: ResponseDto
|
|
588
|
+
- ✅ Incluido en: Constructor de negocio, CreateDto
|
|
589
|
+
- **Uso:** Passwords, tokens, secrets, información sensible
|
|
590
|
+
|
|
591
|
+
```yaml
|
|
592
|
+
fields:
|
|
593
|
+
- name: passwordHash
|
|
594
|
+
type: String
|
|
595
|
+
hidden: true # No exponer en API
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**Matriz de comportamiento:**
|
|
599
|
+
|
|
600
|
+
| Campo | Constructor Negocio | CreateDto | ResponseDto |
|
|
601
|
+
|-------|---------------------|-----------|-------------|
|
|
602
|
+
| Normal | ✅ | ✅ | ✅ |
|
|
603
|
+
| `readOnly: true` | ❌ | ❌ | ✅ |
|
|
604
|
+
| `hidden: true` | ✅ | ✅ | ❌ |
|
|
605
|
+
| Ambos flags | ❌ | ❌ | ❌ |
|
|
606
|
+
|
|
607
|
+
**Ejemplo práctico:**
|
|
608
|
+
```yaml
|
|
609
|
+
entities:
|
|
610
|
+
- name: order
|
|
611
|
+
fields:
|
|
612
|
+
- name: orderNumber
|
|
613
|
+
type: String # ✅ Normal - en todos lados
|
|
614
|
+
|
|
615
|
+
- name: totalAmount
|
|
616
|
+
type: BigDecimal
|
|
617
|
+
readOnly: true # ⚙️ Calculado - no en constructor
|
|
618
|
+
|
|
619
|
+
- name: processingToken
|
|
620
|
+
type: String
|
|
621
|
+
hidden: true # 🔒 Sensible - no en respuesta
|
|
622
|
+
|
|
623
|
+
- name: internalFlag
|
|
624
|
+
type: Boolean
|
|
625
|
+
readOnly: true # 🔐 Calculado Y sensible
|
|
626
|
+
hidden: true
|
|
627
|
+
```
|
|
628
|
+
|
|
546
629
|
### Tipos de Relaciones
|
|
547
630
|
|
|
548
631
|
- `OneToOne` - Relación uno a uno
|
|
@@ -572,15 +655,18 @@ public record UserResponseDto(
|
|
|
572
655
|
### Al Generar Mappers
|
|
573
656
|
|
|
574
657
|
1. **NUNCA** mapear campos de auditoría (createdAt, updatedAt, createdBy, updatedBy)
|
|
575
|
-
2. **
|
|
576
|
-
3. **
|
|
658
|
+
2. **NUNCA** mapear campos readOnly en métodos de creación (fromCommand, fromDto)
|
|
659
|
+
3. **NUNCA** mapear campos hidden en métodos de respuesta (toDto, toResponseDto)
|
|
660
|
+
4. **SIEMPRE** filtrar campos antes de usar `.builder()`
|
|
661
|
+
5. **SIEMPRE** mapear bidireccionalidad en relaciones
|
|
577
662
|
|
|
578
663
|
### Al Generar DTOs
|
|
579
664
|
|
|
580
665
|
1. **NUNCA** exponer `createdBy` y `updatedBy` en respuestas
|
|
581
|
-
2. **
|
|
582
|
-
3. **
|
|
583
|
-
4. **SIEMPRE**
|
|
666
|
+
2. **NUNCA** incluir campos `readOnly` en CreateDto
|
|
667
|
+
3. **NUNCA** incluir campos `hidden` en ResponseDto
|
|
668
|
+
4. **SIEMPRE** usar Java Records para DTOs
|
|
669
|
+
5. **SIEMPRE** filtrar campos según flags de visibilidad
|
|
584
670
|
|
|
585
671
|
---
|
|
586
672
|
|
|
@@ -677,13 +763,18 @@ Al generar o modificar código, verificar:
|
|
|
677
763
|
- [ ] Métodos de negocio con **validaciones explícitas**
|
|
678
764
|
- [ ] Entidades JPA con **Lombok y herencia correcta**
|
|
679
765
|
- [ ] Mappers **excluyen campos de auditoría**
|
|
766
|
+
- [ ] Mappers **excluyen campos readOnly en creación**
|
|
767
|
+
- [ ] Mappers **excluyen campos hidden en respuestas**
|
|
680
768
|
- [ ] DTOs de respuesta **sin createdBy/updatedBy**
|
|
769
|
+
- [ ] DTOs de respuesta **sin campos hidden**
|
|
770
|
+
- [ ] DTOs de creación **sin campos readOnly**
|
|
681
771
|
- [ ] Relaciones bidireccionales con métodos `assign*()`
|
|
682
772
|
- [ ] Value Objects **inmutables**
|
|
683
773
|
- [ ] Configuración de auditoría cuando `trackUser: true`
|
|
774
|
+
- [ ] Validaciones JSR-303 **solo en Command y CreateDto, nunca en dominio**
|
|
684
775
|
|
|
685
776
|
---
|
|
686
777
|
|
|
687
|
-
**Última actualización:** 2026-02-
|
|
778
|
+
**Última actualización:** 2026-02-21
|
|
688
779
|
**Versión de eva4j:** 1.x
|
|
689
780
|
**Estado:** Documento de referencia para agentes IA
|
package/DOMAIN_YAML_GUIDE.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
- [Entidades](#entidades)
|
|
11
11
|
- [Value Objects](#value-objects)
|
|
12
12
|
- [Enums](#enums)
|
|
13
|
+
- [Validaciones JSR-303](#validaciones-jsr-303)
|
|
13
14
|
- [Relaciones](#relaciones)
|
|
14
15
|
- [Tipos de Datos](#tipos-de-datos)
|
|
15
16
|
- [Ejemplos Completos](#ejemplos-completos)
|
|
@@ -396,6 +397,449 @@ fields:
|
|
|
396
397
|
|
|
397
398
|
---
|
|
398
399
|
|
|
400
|
+
### Control de Visibilidad de Campos
|
|
401
|
+
|
|
402
|
+
Eva4j permite controlar qué campos participan en constructores, DTOs de creación y DTOs de respuesta mediante dos flags opcionales: **`readOnly`** y **`hidden`**.
|
|
403
|
+
|
|
404
|
+
#### 📋 Matriz de Comportamiento
|
|
405
|
+
|
|
406
|
+
| Campo | Constructor Negocio | Constructor Completo | CreateDto | ResponseDto |
|
|
407
|
+
|-------|---------------------|----------------------|-----------|-------------|
|
|
408
|
+
| **Normal** | ✅ Incluido | ✅ Incluido | ✅ Incluido | ✅ Incluido |
|
|
409
|
+
| **`readOnly: true`** | ❌ Excluido | ✅ Incluido | ❌ Excluido | ✅ Incluido |
|
|
410
|
+
| **`hidden: true`** | ✅ Incluido | ✅ Incluido | ✅ Incluido | ❌ Excluido |
|
|
411
|
+
| **Ambos flags** | ❌ Excluido | ✅ Incluido | ❌ Excluido | ❌ Excluido |
|
|
412
|
+
|
|
413
|
+
#### 🔒 `readOnly: true` - Campos Calculados/Derivados
|
|
414
|
+
|
|
415
|
+
Marca campos que **se calculan internamente** y no deben pasarse como parámetros en constructores o DTOs de creación.
|
|
416
|
+
|
|
417
|
+
**Casos de uso típicos:**
|
|
418
|
+
- Totales calculados (suma de items)
|
|
419
|
+
- Contadores automáticos
|
|
420
|
+
- Campos derivados de otros datos
|
|
421
|
+
- Timestamps calculados
|
|
422
|
+
|
|
423
|
+
**Sintaxis:**
|
|
424
|
+
```yaml
|
|
425
|
+
fields:
|
|
426
|
+
- name: totalAmount
|
|
427
|
+
type: BigDecimal
|
|
428
|
+
readOnly: true # ✅ No en constructor ni CreateDto
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Ejemplo completo:**
|
|
432
|
+
```yaml
|
|
433
|
+
entities:
|
|
434
|
+
- name: order
|
|
435
|
+
isRoot: true
|
|
436
|
+
tableName: orders
|
|
437
|
+
audit:
|
|
438
|
+
enabled: true
|
|
439
|
+
fields:
|
|
440
|
+
- name: id
|
|
441
|
+
type: String
|
|
442
|
+
- name: orderNumber
|
|
443
|
+
type: String
|
|
444
|
+
- name: customerId
|
|
445
|
+
type: String
|
|
446
|
+
# Campo readOnly - calculado de los items
|
|
447
|
+
- name: totalAmount
|
|
448
|
+
type: BigDecimal
|
|
449
|
+
readOnly: true
|
|
450
|
+
# Campo readOnly - contador de items
|
|
451
|
+
- name: itemCount
|
|
452
|
+
type: Integer
|
|
453
|
+
readOnly: true
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
**Código generado:**
|
|
457
|
+
```java
|
|
458
|
+
// Constructor de negocio - SIN fields readOnly
|
|
459
|
+
public Order(String orderNumber, String customerId) {
|
|
460
|
+
this.orderNumber = orderNumber;
|
|
461
|
+
this.customerId = customerId;
|
|
462
|
+
// totalAmount e itemCount NO están aquí
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Constructor completo - CON fields readOnly (reconstrucción desde DB)
|
|
466
|
+
public Order(String id, String orderNumber, String customerId,
|
|
467
|
+
BigDecimal totalAmount, Integer itemCount,
|
|
468
|
+
LocalDateTime createdAt, LocalDateTime updatedAt) {
|
|
469
|
+
// Todos los campos incluidos
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// CreateDto - SIN fields readOnly
|
|
473
|
+
public record CreateOrderDto(
|
|
474
|
+
String orderNumber,
|
|
475
|
+
String customerId
|
|
476
|
+
// totalAmount e itemCount NO están aquí
|
|
477
|
+
) {}
|
|
478
|
+
|
|
479
|
+
// ResponseDto - CON fields readOnly (mostrar valores calculados)
|
|
480
|
+
public record OrderResponseDto(
|
|
481
|
+
String id,
|
|
482
|
+
String orderNumber,
|
|
483
|
+
String customerId,
|
|
484
|
+
BigDecimal totalAmount, // ✅ Incluido
|
|
485
|
+
Integer itemCount, // ✅ Incluido
|
|
486
|
+
LocalDateTime createdAt,
|
|
487
|
+
LocalDateTime updatedAt
|
|
488
|
+
) {}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
#### 🙈 `hidden: true` - Campos Sensibles/Internos
|
|
492
|
+
|
|
493
|
+
Marca campos que **NO deben exponerse** en respuestas de API pero sí pueden recibirse en creación.
|
|
494
|
+
|
|
495
|
+
**Casos de uso típicos:**
|
|
496
|
+
- Passwords/hashes de seguridad
|
|
497
|
+
- Tokens internos
|
|
498
|
+
- Secrets y claves de API
|
|
499
|
+
- Información sensible (SSN, datos privados)
|
|
500
|
+
- Flags de control interno
|
|
501
|
+
|
|
502
|
+
**Sintaxis:**
|
|
503
|
+
```yaml
|
|
504
|
+
fields:
|
|
505
|
+
- name: passwordHash
|
|
506
|
+
type: String
|
|
507
|
+
hidden: true # ✅ No en ResponseDto
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**Ejemplo completo:**
|
|
511
|
+
```yaml
|
|
512
|
+
entities:
|
|
513
|
+
- name: user
|
|
514
|
+
isRoot: true
|
|
515
|
+
tableName: users
|
|
516
|
+
audit:
|
|
517
|
+
enabled: true
|
|
518
|
+
trackUser: true
|
|
519
|
+
fields:
|
|
520
|
+
- name: id
|
|
521
|
+
type: String
|
|
522
|
+
- name: username
|
|
523
|
+
type: String
|
|
524
|
+
- name: email
|
|
525
|
+
type: String
|
|
526
|
+
# Campo hidden - NO en ResponseDto
|
|
527
|
+
- name: passwordHash
|
|
528
|
+
type: String
|
|
529
|
+
hidden: true
|
|
530
|
+
# Campo hidden - token interno
|
|
531
|
+
- name: resetPasswordToken
|
|
532
|
+
type: String
|
|
533
|
+
hidden: true
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
**Código generado:**
|
|
537
|
+
```java
|
|
538
|
+
// Constructor de negocio - CON fields hidden
|
|
539
|
+
public User(String username, String email,
|
|
540
|
+
String passwordHash, String resetPasswordToken) {
|
|
541
|
+
this.username = username;
|
|
542
|
+
this.email = email;
|
|
543
|
+
this.passwordHash = passwordHash;
|
|
544
|
+
this.resetPasswordToken = resetPasswordToken;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// CreateDto - CON fields hidden (para recibirlos en creación)
|
|
548
|
+
public record CreateUserDto(
|
|
549
|
+
String username,
|
|
550
|
+
String email,
|
|
551
|
+
String passwordHash, // ✅ Se puede recibir
|
|
552
|
+
String resetPasswordToken // ✅ Se puede recibir
|
|
553
|
+
) {}
|
|
554
|
+
|
|
555
|
+
// ResponseDto - SIN fields hidden (proteger datos sensibles)
|
|
556
|
+
public record UserResponseDto(
|
|
557
|
+
String id,
|
|
558
|
+
String username,
|
|
559
|
+
String email,
|
|
560
|
+
LocalDateTime createdAt,
|
|
561
|
+
LocalDateTime updatedAt
|
|
562
|
+
// passwordHash y resetPasswordToken NO están aquí
|
|
563
|
+
) {}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
#### 🔐 Combinando Ambos Flags
|
|
567
|
+
|
|
568
|
+
Puedes combinar `readOnly` y `hidden` para campos que son **calculados internamente Y sensibles**.
|
|
569
|
+
|
|
570
|
+
**Ejemplo:**
|
|
571
|
+
```yaml
|
|
572
|
+
fields:
|
|
573
|
+
- name: isLocked
|
|
574
|
+
type: Boolean
|
|
575
|
+
readOnly: true # Calculado internamente
|
|
576
|
+
hidden: true # NO exponer en API
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
**Resultado:**
|
|
580
|
+
- ❌ NO en constructor de negocio (es readOnly)
|
|
581
|
+
- ❌ NO en CreateDto (es readOnly)
|
|
582
|
+
- ❌ NO en ResponseDto (es hidden)
|
|
583
|
+
- ✅ SÍ en constructor completo (para reconstrucción desde DB)
|
|
584
|
+
|
|
585
|
+
#### 📘 Ejemplo Completo: Sistema de Órdenes
|
|
586
|
+
|
|
587
|
+
```yaml
|
|
588
|
+
aggregates:
|
|
589
|
+
- name: Order
|
|
590
|
+
entities:
|
|
591
|
+
- name: order
|
|
592
|
+
isRoot: true
|
|
593
|
+
tableName: orders
|
|
594
|
+
audit:
|
|
595
|
+
enabled: true
|
|
596
|
+
trackUser: true
|
|
597
|
+
fields:
|
|
598
|
+
- name: id
|
|
599
|
+
type: String
|
|
600
|
+
|
|
601
|
+
# Campos normales
|
|
602
|
+
- name: orderNumber
|
|
603
|
+
type: String
|
|
604
|
+
- name: customerId
|
|
605
|
+
type: String
|
|
606
|
+
- name: status
|
|
607
|
+
type: String
|
|
608
|
+
|
|
609
|
+
# Campos readOnly (calculados)
|
|
610
|
+
- name: totalAmount
|
|
611
|
+
type: BigDecimal
|
|
612
|
+
readOnly: true # Suma de items
|
|
613
|
+
- name: itemCount
|
|
614
|
+
type: Integer
|
|
615
|
+
readOnly: true # Cuenta de items
|
|
616
|
+
|
|
617
|
+
# Campo hidden (interno)
|
|
618
|
+
- name: processingToken
|
|
619
|
+
type: String
|
|
620
|
+
hidden: true # Token de procesamiento interno
|
|
621
|
+
|
|
622
|
+
# Campo readOnly + hidden (calculado e interno)
|
|
623
|
+
- name: riskScore
|
|
624
|
+
type: Integer
|
|
625
|
+
readOnly: true
|
|
626
|
+
hidden: true # Puntaje de riesgo interno
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**Constructor de negocio generado:**
|
|
630
|
+
```java
|
|
631
|
+
public Order(String orderNumber, String customerId,
|
|
632
|
+
String status, String processingToken) {
|
|
633
|
+
// totalAmount, itemCount, riskScore NO están (readOnly)
|
|
634
|
+
// processingToken SÍ está (solo hidden, no readOnly)
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
**CreateOrderDto generado:**
|
|
639
|
+
```java
|
|
640
|
+
public record CreateOrderDto(
|
|
641
|
+
String orderNumber,
|
|
642
|
+
String customerId,
|
|
643
|
+
String status,
|
|
644
|
+
String processingToken // ✅ hidden pero SÍ en create
|
|
645
|
+
// totalAmount, itemCount, riskScore NO están (readOnly)
|
|
646
|
+
) {}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
**OrderResponseDto generado:**
|
|
650
|
+
```java
|
|
651
|
+
public record OrderResponseDto(
|
|
652
|
+
String id,
|
|
653
|
+
String orderNumber,
|
|
654
|
+
String customerId,
|
|
655
|
+
String status,
|
|
656
|
+
BigDecimal totalAmount, // ✅ readOnly pero SÍ en response
|
|
657
|
+
Integer itemCount, // ✅ readOnly pero SÍ en response
|
|
658
|
+
LocalDateTime createdAt,
|
|
659
|
+
LocalDateTime updatedAt
|
|
660
|
+
// processingToken NO está (hidden)
|
|
661
|
+
// riskScore NO está (hidden)
|
|
662
|
+
) {}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
#### ⚡ Comportamiento por Defecto
|
|
666
|
+
|
|
667
|
+
Si no especificas `readOnly` ni `hidden`:
|
|
668
|
+
- ✅ El comportamiento actual se mantiene sin cambios
|
|
669
|
+
- ✅ Campos normales aparecen en todos lados
|
|
670
|
+
- ✅ Solo los campos de auditoría (`createdBy`, `updatedBy`) se excluyen automáticamente de ResponseDto
|
|
671
|
+
|
|
672
|
+
```yaml
|
|
673
|
+
# Sin flags - comportamiento estándar
|
|
674
|
+
fields:
|
|
675
|
+
- name: productName
|
|
676
|
+
type: String # ✅ En constructor, CreateDto Y ResponseDto
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
#### 📚 Ver También
|
|
680
|
+
|
|
681
|
+
- **Ejemplo completo:** [examples/domain-field-visibility.yaml](../examples/domain-field-visibility.yaml)
|
|
682
|
+
- **Campos de auditoría:** Los campos `createdAt`, `updatedAt`, `createdBy`, `updatedBy` siguen su propio comportamiento especial definido en la sección de Auditoría
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
### Referencias entre Agregados (`reference:`)
|
|
687
|
+
|
|
688
|
+
La propiedad `reference:` declara explícitamente que un campo es un puntero intencional a la raíz de otro agregado. El campo sigue siendo un tipo primitivo (`String`, `Long`, etc.) — **no se genera ningún `@ManyToOne`**.
|
|
689
|
+
|
|
690
|
+
#### Sintaxis
|
|
691
|
+
|
|
692
|
+
```yaml
|
|
693
|
+
fields:
|
|
694
|
+
- name: customerId
|
|
695
|
+
type: String
|
|
696
|
+
reference:
|
|
697
|
+
aggregate: Customer # Nombre del agregado referenciado (PascalCase) — obligatorio
|
|
698
|
+
module: customers # Módulo donde vive el agregado — opcional
|
|
699
|
+
- name: productId
|
|
700
|
+
type: String
|
|
701
|
+
reference:
|
|
702
|
+
aggregate: Product
|
|
703
|
+
module: catalog
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
#### Comportamiento
|
|
707
|
+
|
|
708
|
+
- El tipo Java **no cambia** — sigue siendo `String`, `Long`, UUID, etc.
|
|
709
|
+
- JPA genera `@Column` normal — **sin** `@ManyToOne` ni `@JoinColumn`.
|
|
710
|
+
- En la entidad de dominio y en la entidad JPA se genera un **comentario Javadoc** que documenta la referencia.
|
|
711
|
+
- `module:` es opcional: puede omitirse si el agregado referenciado está en el mismo módulo.
|
|
712
|
+
- Si `reference:` está malformado (falta `aggregate`), eva4j lanza un error descriptivo.
|
|
713
|
+
|
|
714
|
+
#### Código Generado
|
|
715
|
+
|
|
716
|
+
```java
|
|
717
|
+
// domain/models/entities/Order.java
|
|
718
|
+
/** Cross-aggregate reference → Customer (module: customers) */
|
|
719
|
+
private String customerId;
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
```java
|
|
723
|
+
// infrastructure/database/entities/OrderJpa.java
|
|
724
|
+
@Column(name = "customer_id")
|
|
725
|
+
/** Cross-aggregate reference → Customer (module: customers) */
|
|
726
|
+
private String customerId;
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
#### Por qué no usar `@ManyToOne` entre agregados
|
|
730
|
+
|
|
731
|
+
En DDD cada agregado es una unidad transaccional independiente. Un `@ManyToOne` cruzando límites crea un único grafo JPA que viola los límites transaccionales y crea dependencias de carga invisibles. La referencia por ID es el patrón correcto: el handler que necesite los datos del otro agregado los obtiene explícitamente via su propio repositorio.
|
|
732
|
+
|
|
733
|
+
- **Ejemplo completo:** [examples/domain-multi-aggregate.yaml](../examples/domain-multi-aggregate.yaml)
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
### Validaciones JSR-303
|
|
738
|
+
|
|
739
|
+
Eva4j soporta anotaciones Bean Validation (JSR-303/Jakarta Validation) en campos del `domain.yaml`. Las validaciones se generan **únicamente en la capa de aplicación**: en el `Create<Aggregate>Command` y en los `Create<Entity>Dto` de entidades secundarias. **No se aplican a entidades de dominio** ni a campos con `readOnly: true`.
|
|
740
|
+
|
|
741
|
+
El import `jakarta.validation.constraints.*` se agrega automáticamente cuando se detecta al menos una validación en los campos del comando.
|
|
742
|
+
|
|
743
|
+
#### Sintaxis
|
|
744
|
+
|
|
745
|
+
```yaml
|
|
746
|
+
fields:
|
|
747
|
+
- name: email
|
|
748
|
+
type: String
|
|
749
|
+
validations:
|
|
750
|
+
- type: NotBlank
|
|
751
|
+
message: "Email es requerido"
|
|
752
|
+
- type: Email
|
|
753
|
+
message: "Email inválido"
|
|
754
|
+
|
|
755
|
+
- name: age
|
|
756
|
+
type: Integer
|
|
757
|
+
validations:
|
|
758
|
+
- type: Min
|
|
759
|
+
value: 18
|
|
760
|
+
message: "Edad mínima 18 años"
|
|
761
|
+
- type: Max
|
|
762
|
+
value: 120
|
|
763
|
+
|
|
764
|
+
- name: username
|
|
765
|
+
type: String
|
|
766
|
+
validations:
|
|
767
|
+
- type: Size
|
|
768
|
+
min: 3
|
|
769
|
+
max: 50
|
|
770
|
+
message: "Username entre 3 y 50 caracteres"
|
|
771
|
+
|
|
772
|
+
- name: code
|
|
773
|
+
type: String
|
|
774
|
+
validations:
|
|
775
|
+
- type: Pattern
|
|
776
|
+
regexp: "^[A-Z]{3}-[0-9]{4}$"
|
|
777
|
+
message: "Formato inválido"
|
|
778
|
+
|
|
779
|
+
- name: price
|
|
780
|
+
type: BigDecimal
|
|
781
|
+
validations:
|
|
782
|
+
- type: Digits
|
|
783
|
+
integer: 10
|
|
784
|
+
fraction: 2
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
#### Propiedades por Tipo
|
|
788
|
+
|
|
789
|
+
| Propiedad | Tipos que la usan | Descripción |
|
|
790
|
+
|-----------|-------------------|-------------|
|
|
791
|
+
| `type` | Todos | Nombre de la anotación (`NotNull`, `NotBlank`, `Email`, `Min`, `Max`, `Size`, `Pattern`, `Digits`, `Positive`, `Negative`, `Past`, `Future`, etc.) |
|
|
792
|
+
| `message` | Todos (opcional) | Mensaje de error personalizado |
|
|
793
|
+
| `value` | `Min`, `Max` | Valor límite numérico |
|
|
794
|
+
| `min` | `Size` | Tamaño mínimo |
|
|
795
|
+
| `max` | `Size` | Tamaño máximo |
|
|
796
|
+
| `regexp` | `Pattern` | Expresión regular |
|
|
797
|
+
| `integer` | `Digits` | Máximo de dígitos enteros |
|
|
798
|
+
| `fraction` | `Digits` | Máximo de dígitos decimales |
|
|
799
|
+
| `inclusive` | `DecimalMin`, `DecimalMax` | Si el límite es inclusivo |
|
|
800
|
+
|
|
801
|
+
#### Anotaciones sin parámetros (solo `type` requerido)
|
|
802
|
+
|
|
803
|
+
`NotNull`, `NotBlank`, `NotEmpty`, `Email`, `Positive`, `PositiveOrZero`, `Negative`, `NegativeOrZero`, `Past`, `PastOrPresent`, `Future`, `FutureOrPresent`, `AssertTrue`, `AssertFalse`
|
|
804
|
+
|
|
805
|
+
#### Código generado
|
|
806
|
+
|
|
807
|
+
Para un campo con validaciones:
|
|
808
|
+
|
|
809
|
+
```yaml
|
|
810
|
+
- name: email
|
|
811
|
+
type: String
|
|
812
|
+
validations:
|
|
813
|
+
- type: Email
|
|
814
|
+
message: "Email inválido"
|
|
815
|
+
- type: NotBlank
|
|
816
|
+
message: "Email es requerido"
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
Se genera en `CreateUserCommand.java`:
|
|
820
|
+
|
|
821
|
+
```java
|
|
822
|
+
import jakarta.validation.constraints.*;
|
|
823
|
+
|
|
824
|
+
public record CreateUserCommand(
|
|
825
|
+
@Email(message = "Email inválido")
|
|
826
|
+
@NotBlank(message = "Email es requerido")
|
|
827
|
+
String email,
|
|
828
|
+
...
|
|
829
|
+
) implements Command {
|
|
830
|
+
}
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
#### Reglas de aplicación
|
|
834
|
+
|
|
835
|
+
- ✅ **Sí** se aplican en `Create<Aggregate>Command`
|
|
836
|
+
- ✅ **Sí** se aplican en `Create<Entity>Dto` (entidades secundarias)
|
|
837
|
+
- ❌ **No** se aplican a entidades de dominio (`Order.java`, etc.)
|
|
838
|
+
- ❌ **No** se aplican a campos con `readOnly: true` (ya están excluidos del command)
|
|
839
|
+
- ❌ **No** se aplican a campos con `hidden: true` si también son `readOnly: true`
|
|
840
|
+
|
|
841
|
+
---
|
|
842
|
+
|
|
399
843
|
### Auditoría Automática
|
|
400
844
|
|
|
401
845
|
eva4j soporta dos niveles de auditoría automática de entidades:
|