eva4j 1.0.13 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +51 -9
- package/DOMAIN_YAML_GUIDE.md +150 -0
- package/bin/eva4j.js +31 -1
- package/design-system.md +797 -0
- package/docs/commands/EVALUATE_SYSTEM.md +542 -0
- package/docs/commands/GENERATE_ENTITIES.md +196 -0
- package/docs/commands/INDEX.md +10 -1
- package/examples/domain-endpoints-relations.yaml +353 -0
- package/examples/domain-endpoints-versioned.yaml +144 -0
- package/examples/domain-endpoints.yaml +135 -0
- package/examples/system.yaml +289 -0
- package/package.json +1 -1
- package/src/commands/create.js +6 -3
- package/src/commands/evaluate-system.js +384 -0
- package/src/commands/generate-entities.js +677 -14
- package/src/commands/generate-kafka-event.js +59 -5
- package/src/commands/generate-system.js +243 -0
- package/src/generators/base-generator.js +9 -1
- package/src/utils/naming.js +3 -2
- package/src/utils/system-validator.js +314 -0
- package/src/utils/yaml-to-entity.js +31 -2
- package/templates/aggregate/AggregateRepository.java.ejs +5 -0
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +9 -0
- package/templates/aggregate/DomainEventHandler.java.ejs +24 -20
- package/templates/aggregate/JpaRepository.java.ejs +5 -0
- package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +1103 -0
- package/templates/base/root/skill-build-domain-yaml.ejs +292 -0
- package/templates/base/root/skill-build-system-yaml.ejs +252 -0
- package/templates/base/root/system.yaml.ejs +97 -0
- package/templates/crud/EndpointsController.java.ejs +178 -0
- package/templates/crud/FindByQuery.java.ejs +17 -0
- package/templates/crud/FindByQueryHandler.java.ejs +57 -0
- package/templates/crud/ScaffoldCommand.java.ejs +12 -0
- package/templates/crud/ScaffoldCommandHandler.java.ejs +43 -0
- package/templates/crud/ScaffoldQuery.java.ejs +12 -0
- package/templates/crud/ScaffoldQueryHandler.java.ejs +40 -0
- package/templates/crud/SubEntityAddCommand.java.ejs +17 -0
- package/templates/crud/SubEntityAddCommandHandler.java.ejs +43 -0
- package/templates/crud/SubEntityRemoveCommand.java.ejs +9 -0
- package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +42 -0
- package/templates/crud/TransitionCommand.java.ejs +9 -0
- package/templates/crud/TransitionCommandHandler.java.ejs +39 -0
- package/templates/evaluate/report.html.ejs +971 -0
- package/templates/kafka-event/Event.java.ejs +7 -0
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
13. [Generated files](#13-generated-files)
|
|
20
20
|
14. [Complete examples](#14-complete-examples)
|
|
21
21
|
15. [Prerequisites and common errors](#15-prerequisites-and-common-errors)
|
|
22
|
+
16. [Declarative endpoints — use case patterns](#16-declarative-endpoints-endpoints--use-case-patterns)
|
|
22
23
|
|
|
23
24
|
---
|
|
24
25
|
|
|
@@ -907,3 +908,198 @@ aggregates:
|
|
|
907
908
|
| `Column 'x_id' is duplicated` | ManyToOne defined manually + auto-generated | Remove the manual ManyToOne; let eva4j generate it |
|
|
908
909
|
| File not regenerated | File was manually modified (checksum) | Use `--force` to overwrite |
|
|
909
910
|
| Import errors | Field `type` doesn't match name in `enums:` or `valueObjects:` | Verify names match exactly |
|
|
911
|
+
|
|
912
|
+
---
|
|
913
|
+
|
|
914
|
+
## 16. Declarative endpoints (`endpoints:`) — Use case patterns
|
|
915
|
+
|
|
916
|
+
When `domain.yaml` includes an `endpoints:` section, the generator examines each `useCase` name and classifies it semantically before generating code. This determines whether a full, working implementation or a scaffold stub is produced.
|
|
917
|
+
|
|
918
|
+
### 16.1 Pattern table
|
|
919
|
+
|
|
920
|
+
| Pattern | Category | Recognition condition | What is generated |
|
|
921
|
+
|---------|----------|----------------------|-------------------|
|
|
922
|
+
| `Create{Aggregate}` | **standard** | Exact string match | Full `CreateCommand` + `CreateCommandHandler` (`ApplicationMapper.fromCommand → save`) |
|
|
923
|
+
| `Update{Aggregate}` | **standard** | Exact string match | Full `UpdateCommand` + `UpdateCommandHandler` |
|
|
924
|
+
| `Delete{Aggregate}` | **standard** | Exact string match | Full `DeleteCommand` + `DeleteCommandHandler` |
|
|
925
|
+
| `Get{Aggregate}` | **standard** | Exact string match | Full `GetQuery` + `GetQueryHandler` (find + `mapper.toDto`) |
|
|
926
|
+
| `FindAll{Aggregate}s` | **standard** | Exact string match (trailing literal `s`) | Full `ListQuery` + `ListQueryHandler` (paginated) |
|
|
927
|
+
| `{MethodPascal}{Aggregate}` | **transition** | `MethodPascal` is `toPascalCase(transitions[n].method)` for any enum in the aggregate | Full `TransitionCommand(id)` + handler that calls `entity.{method}() → save()` |
|
|
928
|
+
| `Add{EntityName}` | **subEntityAdd** | `EntityName` is the `target` of a `OneToMany` relationship on the root | Full `AddCommand(id, entityFields…)` + handler that calls `entity.add{Entity}(new {Entity}(…)) → save()` |
|
|
929
|
+
| `Remove{EntityName}` | **subEntityRemove** | Same `target` from a `OneToMany` relationship | Full `RemoveCommand(id, itemId)` + handler that calls `entity.remove{Entity}ById(itemId) → save()` |
|
|
930
|
+
| `FindAll{Aggregate}sBy{FieldPascal}` | **findBy** | `FieldPascal` is `toPascalCase(fieldName)` for any field in the root entity | Full `FindByQuery` + `FindByQueryHandler` + `findBy{FieldPascal}` added to `{Aggregate}Repository`, `{Aggregate}RepositoryImpl`, and `{Aggregate}JpaRepository` |
|
|
931
|
+
| _anything else_ | **scaffold** | No pattern matched | `*Command(id)` or `*Query(id)` + handler that throws `UnsupportedOperationException` with a TODO comment |
|
|
932
|
+
|
|
933
|
+
> **Note on `FindAll{Aggregate}s`:** The trailing `s` is a literal character. For `Aggregate = Order` the standard name is `FindAllOrders` (not `FindAllOrder`). Irregular plurals are not supported — use the exact pattern or it will be classified as scaffold.
|
|
934
|
+
|
|
935
|
+
### 16.2 Transition pattern in detail
|
|
936
|
+
|
|
937
|
+
Enumerate state transitions in the `enums:` block using `transitions`:
|
|
938
|
+
|
|
939
|
+
```yaml
|
|
940
|
+
enums:
|
|
941
|
+
- name: OrderStatus
|
|
942
|
+
transitions:
|
|
943
|
+
- from: PENDING
|
|
944
|
+
to: CONFIRMED
|
|
945
|
+
method: confirm # → recognized as ConfirmOrder (PascalCase(confirm) + Order)
|
|
946
|
+
- from: [PENDING, CONFIRMED]
|
|
947
|
+
to: CANCELLED
|
|
948
|
+
method: cancel # → CancelOrder
|
|
949
|
+
- from: CONFIRMED
|
|
950
|
+
to: SHIPPED
|
|
951
|
+
method: ship # → ShipOrder
|
|
952
|
+
values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
Declare the corresponding use cases in `endpoints:`:
|
|
956
|
+
|
|
957
|
+
```yaml
|
|
958
|
+
endpoints:
|
|
959
|
+
basePath: /orders
|
|
960
|
+
versions:
|
|
961
|
+
- version: v1
|
|
962
|
+
operations:
|
|
963
|
+
- method: PUT
|
|
964
|
+
path: /{id}/confirm
|
|
965
|
+
useCase: ConfirmOrder # ← transition pattern: confirm + Order
|
|
966
|
+
type: command
|
|
967
|
+
- method: PUT
|
|
968
|
+
path: /{id}/cancel
|
|
969
|
+
useCase: CancelOrder
|
|
970
|
+
type: command
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
**Generated output:**
|
|
974
|
+
|
|
975
|
+
```java
|
|
976
|
+
// ConfirmOrderCommand.java
|
|
977
|
+
public record ConfirmOrderCommand(String id) implements Command {}
|
|
978
|
+
|
|
979
|
+
// ConfirmOrderCommandHandler.java
|
|
980
|
+
@Transactional
|
|
981
|
+
public void handle(ConfirmOrderCommand command) {
|
|
982
|
+
Order entity = repository.findById(command.id())
|
|
983
|
+
.orElseThrow(() -> new NotFoundException("Order not found with id: " + command.id()));
|
|
984
|
+
entity.confirm(); // ← the domain method from transitions[].method
|
|
985
|
+
repository.save(entity);
|
|
986
|
+
}
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
### 16.3 Sub-entity add/remove pattern in detail
|
|
990
|
+
|
|
991
|
+
Requirements:
|
|
992
|
+
- A `OneToMany` relationship must be declared on the root entity pointing to the target entity.
|
|
993
|
+
- The aggregate root must expose `add{EntityName}({EntityName} item)` and `remove{EntityName}ById(String id)` domain methods (generated automatically by `eva g entities`).
|
|
994
|
+
|
|
995
|
+
```yaml
|
|
996
|
+
# aggregates: section
|
|
997
|
+
relationships:
|
|
998
|
+
- type: OneToMany
|
|
999
|
+
target: OrderItem # ← entityName used to match Add/Remove pattern
|
|
1000
|
+
fieldName: items
|
|
1001
|
+
...
|
|
1002
|
+
|
|
1003
|
+
# endpoints: section
|
|
1004
|
+
operations:
|
|
1005
|
+
- method: POST
|
|
1006
|
+
path: /{id}/items
|
|
1007
|
+
useCase: AddOrderItem # ← Add + OrderItem (target name)
|
|
1008
|
+
type: command
|
|
1009
|
+
- method: DELETE
|
|
1010
|
+
path: /{id}/items/{itemId}
|
|
1011
|
+
useCase: RemoveOrderItem # ← Remove + OrderItem
|
|
1012
|
+
type: command
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
**Generated output:**
|
|
1016
|
+
|
|
1017
|
+
```java
|
|
1018
|
+
// AddOrderItemCommand.java — fields taken from OrderItem (non-id, non-audit, non-readOnly)
|
|
1019
|
+
public record AddOrderItemCommand(
|
|
1020
|
+
String id,
|
|
1021
|
+
String productId,
|
|
1022
|
+
String productName,
|
|
1023
|
+
Integer quantity,
|
|
1024
|
+
BigDecimal unitPrice
|
|
1025
|
+
) implements Command {}
|
|
1026
|
+
|
|
1027
|
+
// AddOrderItemCommandHandler.java
|
|
1028
|
+
@Transactional
|
|
1029
|
+
public void handle(AddOrderItemCommand command) {
|
|
1030
|
+
Order entity = repository.findById(command.id()) ...;
|
|
1031
|
+
OrderItem item = new OrderItem(command.productId(), command.productName(),
|
|
1032
|
+
command.quantity(), command.unitPrice());
|
|
1033
|
+
entity.addOrderItem(item);
|
|
1034
|
+
repository.save(entity);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// RemoveOrderItemCommand.java
|
|
1038
|
+
public record RemoveOrderItemCommand(String id, String itemId) implements Command {}
|
|
1039
|
+
|
|
1040
|
+
// RemoveOrderItemCommandHandler.java
|
|
1041
|
+
@Transactional
|
|
1042
|
+
public void handle(RemoveOrderItemCommand command) {
|
|
1043
|
+
Order entity = repository.findById(command.id()) ...;
|
|
1044
|
+
entity.removeOrderItemById(command.itemId());
|
|
1045
|
+
repository.save(entity);
|
|
1046
|
+
}
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
### 16.4 FindBy pattern in detail
|
|
1050
|
+
|
|
1051
|
+
Strict pattern: `FindAll{Aggregate}sBy{FieldPascal}` — both parts are required.
|
|
1052
|
+
|
|
1053
|
+
When detected, the generator:
|
|
1054
|
+
1. Creates a paginated `FindBy{Field}Query` + `FindBy{Field}QueryHandler`.
|
|
1055
|
+
2. Re-generates `{Aggregate}Repository.java` (domain interface), `{Aggregate}JpaRepository.java`, and `{Aggregate}RepositoryImpl.java` with the `findBy{FieldPascal}(FieldType value, Pageable pageable)` method added. Checksum protection still applies — manually modified files are skipped unless `--force` is used.
|
|
1056
|
+
|
|
1057
|
+
```yaml
|
|
1058
|
+
# Root entity field
|
|
1059
|
+
fields:
|
|
1060
|
+
- name: customerId
|
|
1061
|
+
type: String
|
|
1062
|
+
|
|
1063
|
+
# Endpoint
|
|
1064
|
+
operations:
|
|
1065
|
+
- method: GET
|
|
1066
|
+
path: /customer/{customerId}
|
|
1067
|
+
useCase: FindAllOrdersByCustomerId # ← FindAll + Order + s + By + CustomerId
|
|
1068
|
+
type: query
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
**Generated output:**
|
|
1072
|
+
|
|
1073
|
+
```java
|
|
1074
|
+
// FindAllOrdersByCustomerIdQuery.java
|
|
1075
|
+
public record FindAllOrdersByCustomerIdQuery(
|
|
1076
|
+
String customerId, int page, int size, String sortBy, String sortDirection
|
|
1077
|
+
) implements Query<PagedResponse<OrderResponseDto>> {}
|
|
1078
|
+
|
|
1079
|
+
// OrderRepository.java (domain interface — method appended)
|
|
1080
|
+
Page<Order> findByCustomerId(String customerId, Pageable pageable);
|
|
1081
|
+
|
|
1082
|
+
// OrderJpaRepository.java (Spring Data JPA — method auto-implemented)
|
|
1083
|
+
Page<OrderJpa> findByCustomerId(String customerId, Pageable pageable);
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
### 16.5 Scaffold (fallback)
|
|
1087
|
+
|
|
1088
|
+
Any `useCase` name that does not match any pattern above becomes a scaffold. A scaffold generates:
|
|
1089
|
+
|
|
1090
|
+
- A minimal `{UseCase}Command(String id)` or `{UseCase}Query(String id)` record.
|
|
1091
|
+
- A handler that throws `UnsupportedOperationException` and includes a step-by-step TODO comment.
|
|
1092
|
+
|
|
1093
|
+
This is intentional: the developer fills in the custom business logic while the wiring (registration, mediator dispatch, controller method) is already in place.
|
|
1094
|
+
|
|
1095
|
+
### 16.6 Naming rules
|
|
1096
|
+
|
|
1097
|
+
| What | Convention | Example |
|
|
1098
|
+
|------|-----------|---------|
|
|
1099
|
+
| Aggregate name | PascalCase | `Order` |
|
|
1100
|
+
| Use case name in YAML | PascalCase | `ConfirmOrder`, `FindAllOrdersByCustomerId` |
|
|
1101
|
+
| Transition method in YAML | camelCase | `confirm`, `cancelOrder` |
|
|
1102
|
+
| Pattern `{MethodPascal}` | `toPascalCase(method)` | `confirm` → `Confirm` |
|
|
1103
|
+
| Pattern `{FieldPascal}` | `toPascalCase(fieldName)` | `customerId` → `CustomerId` |
|
|
1104
|
+
| Sub-entity target | PascalCase (must match entity `name:`) | `OrderItem` |
|
|
1105
|
+
|
package/docs/commands/INDEX.md
CHANGED
|
@@ -112,7 +112,16 @@ Commands for transitioning from monolith to microservices.
|
|
|
112
112
|
- Service independence
|
|
113
113
|
- Zero code rewrite
|
|
114
114
|
|
|
115
|
-
### 📊 Utility Commands
|
|
115
|
+
### 📊 Analysis & Utility Commands
|
|
116
|
+
|
|
117
|
+
- **[evaluate system](./EVALUATE_SYSTEM.md)** - Statically analyze a system.yaml for architectural problems
|
|
118
|
+
- Referential integrity (producers, consumers, sync endpoints)
|
|
119
|
+
- Synchronous cycle detection (DFS — deadlock prevention)
|
|
120
|
+
- Module role analysis (isolation, pure consumers, autonomous modules)
|
|
121
|
+
- Behavior gap detection (scheduler verbs with no trigger)
|
|
122
|
+
- Coupling pattern analysis (asymmetric sync↔async dependencies)
|
|
123
|
+
- Interactive HTML report with flow simulator + network diagram
|
|
124
|
+
- Domain-agnostic: works for any microservices design
|
|
116
125
|
|
|
117
126
|
- **info** - Display project information
|
|
118
127
|
- *Documentation coming soon*
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# EJEMPLO: ENDPOINTS + RELACIONES
|
|
2
|
+
#
|
|
3
|
+
# Muestra cómo funciona la sección endpoints: cuando el agregado tiene
|
|
4
|
+
# - Entidad raíz : Order
|
|
5
|
+
# - OneToMany : OrderItem
|
|
6
|
+
# - OneToOne : ShippingAddress
|
|
7
|
+
#
|
|
8
|
+
# REGLA CLAVE:
|
|
9
|
+
# La sección `endpoints:` controla CUÁLES use cases se generan.
|
|
10
|
+
# La sección `aggregates:` controla LA ESTRUCTURA de cada use case
|
|
11
|
+
# (campos, listas anidadas, DTOs de relaciones).
|
|
12
|
+
#
|
|
13
|
+
# Ambas secciones son INDEPENDIENTES entre sí. El generador combina
|
|
14
|
+
# la información de ambas para producir el código correcto.
|
|
15
|
+
#
|
|
16
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
17
|
+
# QUÉ SE GENERA
|
|
18
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
19
|
+
#
|
|
20
|
+
# CreateOrder → implementación COMPLETA
|
|
21
|
+
# Porque su nombre coincide con el patrón estándar Create{Aggregate}.
|
|
22
|
+
# El Command incluirá automáticamente:
|
|
23
|
+
# - Los campos simples de Order (orderNumber, customerId)
|
|
24
|
+
# - List<CreateOrderItemDto> items ← por la relación OneToMany
|
|
25
|
+
# - CreateShippingAddressDto shipping ← por la relación OneToOne
|
|
26
|
+
#
|
|
27
|
+
# GetOrder / FindAllOrders → implementación COMPLETA
|
|
28
|
+
# El ResponseDto incluirá:
|
|
29
|
+
# - Campos de Order
|
|
30
|
+
# - List<OrderItemDto> items ← OneToMany en respuesta
|
|
31
|
+
# - ShippingAddressDto shippingAddress ← OneToOne en respuesta
|
|
32
|
+
#
|
|
33
|
+
# ConfirmOrder / CancelOrder / ShipOrder → Cat. 1 TRANSITION
|
|
34
|
+
# Porque su nombre es PascalCase(method) + Aggregate para una transición
|
|
35
|
+
# declarada en enums[].transitions. Handler completo generado automáticamente.
|
|
36
|
+
#
|
|
37
|
+
# AddOrderItem / RemoveOrderItem → Cat. 2 SUB-ENTITY ADD/REMOVE
|
|
38
|
+
# Porque su nombre es Add{Target} / Remove{Target} para una relación OneToMany.
|
|
39
|
+
# Handler completo generado automáticamente.
|
|
40
|
+
#
|
|
41
|
+
# FindAllOrdersByCustomerId → Cat. 3 FIND-BY
|
|
42
|
+
# Porque su nombre sigue FindAll{Aggregate}sBy{FieldPascal} para un campo de la raíz.
|
|
43
|
+
# Query + handler + método en el repositorio generados automáticamente.
|
|
44
|
+
#
|
|
45
|
+
# ProcessOrderPayment → SCAFFOLD (nombre libre)
|
|
46
|
+
# No coincide con ningún patrón → genera stubs con TODO.
|
|
47
|
+
# El desarrollador añade los campos al Command e implementa el Handler.
|
|
48
|
+
|
|
49
|
+
aggregates:
|
|
50
|
+
- name: Order
|
|
51
|
+
entities:
|
|
52
|
+
|
|
53
|
+
# ── Raíz del agregado ──────────────────────────────────────────────
|
|
54
|
+
- name: Order
|
|
55
|
+
isRoot: true
|
|
56
|
+
tableName: orders
|
|
57
|
+
audit:
|
|
58
|
+
enabled: true
|
|
59
|
+
trackUser: true
|
|
60
|
+
fields:
|
|
61
|
+
- name: id
|
|
62
|
+
type: String
|
|
63
|
+
- name: orderNumber
|
|
64
|
+
type: String
|
|
65
|
+
validations:
|
|
66
|
+
- type: NotBlank
|
|
67
|
+
message: "El número de pedido es obligatorio"
|
|
68
|
+
- name: customerId
|
|
69
|
+
type: String
|
|
70
|
+
reference:
|
|
71
|
+
aggregate: Customer
|
|
72
|
+
module: customers
|
|
73
|
+
- name: status
|
|
74
|
+
type: OrderStatus
|
|
75
|
+
readOnly: true # Manejado por transitions — no va en CreateCommand
|
|
76
|
+
- name: totalAmount
|
|
77
|
+
type: BigDecimal
|
|
78
|
+
readOnly: true # Calculado — no va en CreateCommand
|
|
79
|
+
defaultValue: "0.00"
|
|
80
|
+
- name: notes
|
|
81
|
+
type: String
|
|
82
|
+
relationships:
|
|
83
|
+
# El generador crea automáticamente la relación inversa (ManyToOne) en OrderItem
|
|
84
|
+
# No es necesario declarar la relación inversa en OrderItem
|
|
85
|
+
- type: OneToMany
|
|
86
|
+
target: OrderItem
|
|
87
|
+
mappedBy: order # → OrderItem tendrá un campo 'order' (FK: order_id)
|
|
88
|
+
cascade: [PERSIST, MERGE, REMOVE]
|
|
89
|
+
fetch: LAZY
|
|
90
|
+
# El generador crea automáticamente la relación inversa (OneToOne) en ShippingAddress
|
|
91
|
+
# No es necesario declarar la relación inversa en ShippingAddress
|
|
92
|
+
- type: OneToOne
|
|
93
|
+
target: ShippingAddress
|
|
94
|
+
mappedBy: order # → ShippingAddress tendrá un campo 'order' (FK: order_id)
|
|
95
|
+
cascade: [PERSIST, MERGE, REMOVE]
|
|
96
|
+
fetch: LAZY
|
|
97
|
+
|
|
98
|
+
# ── OneToMany: líneas del pedido ───────────────────────────────────
|
|
99
|
+
- name: OrderItem
|
|
100
|
+
tableName: order_items
|
|
101
|
+
fields:
|
|
102
|
+
- name: id
|
|
103
|
+
type: String
|
|
104
|
+
- name: productId
|
|
105
|
+
type: String
|
|
106
|
+
reference:
|
|
107
|
+
aggregate: Product
|
|
108
|
+
module: catalog
|
|
109
|
+
- name: productName
|
|
110
|
+
type: String
|
|
111
|
+
- name: quantity
|
|
112
|
+
type: Integer
|
|
113
|
+
validations:
|
|
114
|
+
- type: Min
|
|
115
|
+
value: 1
|
|
116
|
+
message: "La cantidad mínima es 1"
|
|
117
|
+
- name: unitPrice
|
|
118
|
+
type: BigDecimal
|
|
119
|
+
- name: subtotal
|
|
120
|
+
type: BigDecimal
|
|
121
|
+
readOnly: true # Calculado por el método de negocio addOrderItem()
|
|
122
|
+
defaultValue: "0.00"
|
|
123
|
+
# ❌ No declarar aquí la relación inversa (ManyToOne hacia Order):
|
|
124
|
+
# el generador la infiere automáticamente desde la definición de Order
|
|
125
|
+
|
|
126
|
+
# ── OneToOne: dirección de envío ───────────────────────────────────
|
|
127
|
+
- name: ShippingAddress
|
|
128
|
+
tableName: order_shipping_addresses
|
|
129
|
+
fields:
|
|
130
|
+
- name: id
|
|
131
|
+
type: String
|
|
132
|
+
- name: street
|
|
133
|
+
type: String
|
|
134
|
+
validations:
|
|
135
|
+
- type: NotBlank
|
|
136
|
+
- name: city
|
|
137
|
+
type: String
|
|
138
|
+
validations:
|
|
139
|
+
- type: NotBlank
|
|
140
|
+
- name: postalCode
|
|
141
|
+
type: String
|
|
142
|
+
- name: country
|
|
143
|
+
type: String
|
|
144
|
+
# ❌ No declarar aquí la relación inversa (OneToOne hacia Order):
|
|
145
|
+
# el generador la infiere automáticamente desde la definición de Order
|
|
146
|
+
|
|
147
|
+
enums:
|
|
148
|
+
- name: OrderStatus
|
|
149
|
+
initialValue: PENDING
|
|
150
|
+
transitions:
|
|
151
|
+
- from: PENDING
|
|
152
|
+
to: CONFIRMED
|
|
153
|
+
method: confirm
|
|
154
|
+
- from: [PENDING, CONFIRMED]
|
|
155
|
+
to: CANCELLED
|
|
156
|
+
method: cancel
|
|
157
|
+
- from: CONFIRMED
|
|
158
|
+
to: SHIPPED
|
|
159
|
+
method: ship
|
|
160
|
+
values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
|
|
161
|
+
|
|
162
|
+
events:
|
|
163
|
+
- name: OrderConfirmedEvent
|
|
164
|
+
fields:
|
|
165
|
+
- name: orderId
|
|
166
|
+
type: String
|
|
167
|
+
- name: customerId
|
|
168
|
+
type: String
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
172
|
+
# ENDPOINTS
|
|
173
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
174
|
+
#
|
|
175
|
+
# Solo controla CUÁLES use cases existen y bajo qué URLs viven.
|
|
176
|
+
# La estructura (campos anidados, listas) la define aggregates: arriba.
|
|
177
|
+
|
|
178
|
+
endpoints:
|
|
179
|
+
basePath: /orders
|
|
180
|
+
versions:
|
|
181
|
+
- version: v1
|
|
182
|
+
operations:
|
|
183
|
+
|
|
184
|
+
# ── CRUD estándar ─────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
- method: POST
|
|
187
|
+
path: /
|
|
188
|
+
description: "Crear pedido con sus líneas y dirección de envío"
|
|
189
|
+
useCase: CreateOrder
|
|
190
|
+
# El Command generado incluirá automáticamente:
|
|
191
|
+
# String orderNumber
|
|
192
|
+
# String customerId
|
|
193
|
+
# String notes
|
|
194
|
+
# List<CreateOrderItemDto> items ← OneToMany
|
|
195
|
+
# CreateShippingAddressDto shippingAddress ← OneToOne
|
|
196
|
+
|
|
197
|
+
- method: GET
|
|
198
|
+
path: /{id}
|
|
199
|
+
description: "Obtener pedido completo con líneas y dirección"
|
|
200
|
+
useCase: GetOrder
|
|
201
|
+
# El ResponseDto tendrá:
|
|
202
|
+
# campos de Order + List<OrderItemDto> + ShippingAddressDto
|
|
203
|
+
|
|
204
|
+
- method: GET
|
|
205
|
+
path: /
|
|
206
|
+
description: "Listar pedidos con paginación"
|
|
207
|
+
useCase: FindAllOrders
|
|
208
|
+
|
|
209
|
+
- method: DELETE
|
|
210
|
+
path: /{id}
|
|
211
|
+
description: "Cancelar y eliminar un pedido"
|
|
212
|
+
useCase: DeleteOrder
|
|
213
|
+
|
|
214
|
+
# ── Operaciones de negocio (scaffold) ────────────────────────────
|
|
215
|
+
|
|
216
|
+
- method: PUT
|
|
217
|
+
path: /{id}/confirm
|
|
218
|
+
description: "Confirmar un pedido pendiente"
|
|
219
|
+
useCase: ConfirmOrder
|
|
220
|
+
# Cat. 1 TRANSITION — nombre coincide con PascalCase(confirm) + Order
|
|
221
|
+
# Genera ConfirmOrderCommand(String id) + handler completo:
|
|
222
|
+
# repository.findById(id) → entity.confirm() → repository.save(entity)
|
|
223
|
+
# type inferido: PUT → command
|
|
224
|
+
|
|
225
|
+
- method: PUT
|
|
226
|
+
path: /{id}/cancel
|
|
227
|
+
description: "Cancelar un pedido"
|
|
228
|
+
useCase: CancelOrder
|
|
229
|
+
# Cat. 1 TRANSITION — cancel + Order
|
|
230
|
+
|
|
231
|
+
- method: PUT
|
|
232
|
+
path: /{id}/ship
|
|
233
|
+
description: "Marcar pedido como enviado"
|
|
234
|
+
useCase: ShipOrder
|
|
235
|
+
# Cat. 1 TRANSITION — ship + Order
|
|
236
|
+
|
|
237
|
+
# ── Operaciones sobre sub-entidades (scaffold) ───────────────────
|
|
238
|
+
|
|
239
|
+
- method: POST
|
|
240
|
+
path: /{id}/items
|
|
241
|
+
description: "Agregar línea a un pedido existente"
|
|
242
|
+
useCase: AddOrderItem
|
|
243
|
+
# Cat. 2a SUB-ENTITY ADD — patrón: Add{target} donde target=OrderItem (OneToMany)
|
|
244
|
+
# Patrón de nombre: Add + rel.target → AddOrderItem
|
|
245
|
+
# Genera AddOrderItemCommand(String id, String productId, String productName, Integer quantity, BigDecimal unitPrice)
|
|
246
|
+
# Handler: findById → entity.addOrderItem(productId, productName, quantity, unitPrice) → save
|
|
247
|
+
# Nota: el campo generado en Order se llama 'orderItems' (auto-pluralizado de OrderItem)
|
|
248
|
+
|
|
249
|
+
- method: DELETE
|
|
250
|
+
path: /{id}/items/{itemId}
|
|
251
|
+
description: "Eliminar una línea del pedido"
|
|
252
|
+
useCase: RemoveOrderItem
|
|
253
|
+
# Cat. 2b SUB-ENTITY REMOVE — patrón: Remove{target} donde target=OrderItem
|
|
254
|
+
# Patrón de nombre: Remove + rel.target → RemoveOrderItem
|
|
255
|
+
# Genera RemoveOrderItemCommand(String id, String itemId)
|
|
256
|
+
# Handler: findById → entity.removeOrderItemById(itemId) → save
|
|
257
|
+
|
|
258
|
+
- method: GET
|
|
259
|
+
path: /customer/{customerId}
|
|
260
|
+
description: "Buscar pedidos por cliente"
|
|
261
|
+
useCase: FindAllOrdersByCustomerId
|
|
262
|
+
# Cat. 3 FIND-BY — patrón FindAll{Aggregate}sBy{FieldPascal}
|
|
263
|
+
# Genera FindAllOrdersByCustomerIdQuery(customerId, page, size, sortBy, sortDirection)
|
|
264
|
+
# Handler: repository.findByCustomerId(customerId, pageable)
|
|
265
|
+
# Además actualiza OrderRepository + OrderJpaRepository + OrderRepositoryImpl
|
|
266
|
+
# type inferido: GET → query
|
|
267
|
+
|
|
268
|
+
# ── SCAFFOLD (nombre libre — ningún patrón lo reconoce) ──────────
|
|
269
|
+
|
|
270
|
+
- method: POST
|
|
271
|
+
path: /{id}/payment
|
|
272
|
+
description: "Procesar el pago de un pedido"
|
|
273
|
+
useCase: ProcessOrderPayment
|
|
274
|
+
# ⚠️ SCAFFOLD — el nombre no coincide con ningún patrón:
|
|
275
|
+
# ✗ No es Create/Get/FindAll/Update/Delete{Aggregate} (estándar)
|
|
276
|
+
# ✗ No es {MethodPascal}{Aggregate} de ninguna transition (Cat. 1)
|
|
277
|
+
# ✗ No es Add{Target} / Remove{Target} de un OneToMany (Cat. 2)
|
|
278
|
+
# ✗ No es FindAll{Aggregate}sBy{Field} (Cat. 3)
|
|
279
|
+
#
|
|
280
|
+
# Por eso genera stubs con TODO y UnsupportedOperationException:
|
|
281
|
+
#
|
|
282
|
+
# ProcessOrderPaymentCommand.java ← record con /* TODO: add fields */
|
|
283
|
+
# ProcessOrderPaymentCommandHandler.java ← lanza UnsupportedOperationException
|
|
284
|
+
#
|
|
285
|
+
# El desarrollador debe:
|
|
286
|
+
# 1. Añadir los campos al Command (paymentMethod, amount, etc.)
|
|
287
|
+
# 2. Implementar la lógica en el Handler
|
|
288
|
+
#
|
|
289
|
+
# type inferido: POST → command (o declarar explícitamente si es GET)
|
|
290
|
+
|
|
291
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
292
|
+
# RESUMEN DE ARCHIVOS GENERADOS
|
|
293
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
294
|
+
#
|
|
295
|
+
# domain/models/entities/
|
|
296
|
+
# ├── Order.java (raíz — con métodos confirm/cancel/ship/addOrderItem/removeOrderItemById)
|
|
297
|
+
# ├── OrderItem.java
|
|
298
|
+
# └── ShippingAddress.java
|
|
299
|
+
#
|
|
300
|
+
# domain/models/enums/
|
|
301
|
+
# └── OrderStatus.java
|
|
302
|
+
#
|
|
303
|
+
# domain/models/events/
|
|
304
|
+
# └── OrderConfirmedEvent.java
|
|
305
|
+
#
|
|
306
|
+
# domain/repositories/
|
|
307
|
+
# └── OrderRepository.java ← actualizado con findByCustomerId (Cat.3)
|
|
308
|
+
#
|
|
309
|
+
# infrastructure/database/repositories/
|
|
310
|
+
# ├── OrderJpaRepository.java ← actualizado con findByCustomerId
|
|
311
|
+
# └── OrderRepositoryImpl.java ← actualizado con findByCustomerId
|
|
312
|
+
#
|
|
313
|
+
# application/commands/
|
|
314
|
+
# ├── CreateOrderCommand.java ← ESTÁNDAR — incluye items[] y shippingAddress
|
|
315
|
+
# ├── DeleteOrderCommand.java ← ESTÁNDAR
|
|
316
|
+
# ├── ConfirmOrderCommand.java ← TRANSITION Cat.1 — record(String id)
|
|
317
|
+
# ├── CancelOrderCommand.java ← TRANSITION Cat.1
|
|
318
|
+
# ├── ShipOrderCommand.java ← TRANSITION Cat.1
|
|
319
|
+
# ├── AddOrderItemCommand.java ← SUB-ENTITY ADD Cat.2 — incluye campos de OrderItem
|
|
320
|
+
# ├── RemoveOrderItemCommand.java ← SUB-ENTITY REMOVE Cat.2 — record(id, itemId)
|
|
321
|
+
# └── ProcessOrderPaymentCommand.java ← SCAFFOLD — record con /* TODO: add fields */
|
|
322
|
+
#
|
|
323
|
+
# application/queries/
|
|
324
|
+
# ├── GetOrderQuery.java ← ESTÁNDAR
|
|
325
|
+
# ├── FindAllOrdersQuery.java ← ESTÁNDAR (FindAll + Order + s)
|
|
326
|
+
# └── FindAllOrdersByCustomerIdQuery.java ← FIND-BY Cat.3 — filtra por customerId
|
|
327
|
+
#
|
|
328
|
+
# application/usecases/
|
|
329
|
+
# ├── CreateOrderCommandHandler.java ← ESTÁNDAR — completo
|
|
330
|
+
# ├── DeleteOrderCommandHandler.java ← ESTÁNDAR — completo
|
|
331
|
+
# ├── GetOrderQueryHandler.java ← ESTÁNDAR — completo
|
|
332
|
+
# ├── FindAllOrdersQueryHandler.java ← ESTÁNDAR — paginado completo
|
|
333
|
+
# ├── ConfirmOrderCommandHandler.java ← TRANSITION — findById → entity.confirm() → save
|
|
334
|
+
# ├── CancelOrderCommandHandler.java ← TRANSITION — findById → entity.cancel() → save
|
|
335
|
+
# ├── ShipOrderCommandHandler.java ← TRANSITION — findById → entity.ship() → save
|
|
336
|
+
# ├── AddOrderItemCommandHandler.java ← SUB-ENTITY ADD — findById → entity.addOrderItem(new OrderItem(...)) → save
|
|
337
|
+
# ├── RemoveOrderItemCommandHandler.java ← SUB-ENTITY REMOVE — findById → entity.removeOrderItemById(itemId) → save
|
|
338
|
+
# ├── FindAllOrdersByCustomerIdQueryHandler.java ← FIND-BY — repository.findByCustomerId(customerId, pageable)
|
|
339
|
+
# └── ProcessOrderPaymentCommandHandler.java ← SCAFFOLD — UnsupportedOperationException (TODO)
|
|
340
|
+
#
|
|
341
|
+
# application/dtos/
|
|
342
|
+
# ├── OrderResponseDto.java
|
|
343
|
+
# ├── OrderItemDto.java
|
|
344
|
+
# ├── ShippingAddressDto.java
|
|
345
|
+
# ├── CreateOrderItemDto.java ← para el comando de creación
|
|
346
|
+
# └── CreateShippingAddressDto.java ← para el comando de creación
|
|
347
|
+
#
|
|
348
|
+
# application/mappers/
|
|
349
|
+
# └── OrderApplicationMapper.java
|
|
350
|
+
#
|
|
351
|
+
# infrastructure/rest/controllers/order/
|
|
352
|
+
# └── v1/
|
|
353
|
+
# └── OrderV1Controller.java ← 10 endpoints declarados
|