eva4j 1.0.13 → 1.0.15
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 +314 -10
- package/COMMAND_EVALUATION.md +15 -16
- package/DOMAIN_YAML_GUIDE.md +576 -10
- package/FUTURE_FEATURES.md +1627 -1168
- package/README.md +318 -13
- package/bin/eva4j.js +34 -0
- package/config/defaults.json +1 -0
- package/design-system.md +797 -0
- package/docs/commands/EVALUATE_SYSTEM.md +994 -0
- package/docs/commands/GENERATE_ENTITIES.md +795 -6
- 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/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 +332 -0
- package/package.json +2 -1
- package/src/commands/build.js +714 -0
- package/src/commands/create.js +7 -3
- package/src/commands/detach.js +1 -0
- package/src/commands/evaluate-system.js +610 -0
- package/src/commands/generate-entities.js +1331 -49
- package/src/commands/generate-http-exchange.js +2 -0
- package/src/commands/generate-kafka-event.js +98 -11
- package/src/generators/base-generator.js +8 -1
- package/src/generators/postman-generator.js +188 -0
- package/src/generators/shared-generator.js +10 -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 +970 -0
- package/src/utils/fake-data.js +376 -0
- package/src/utils/naming.js +3 -2
- package/src/utils/system-validator.js +434 -0
- package/src/utils/yaml-to-entity.js +302 -8
- package/templates/aggregate/AggregateMapper.java.ejs +3 -2
- package/templates/aggregate/AggregateRepository.java.ejs +8 -2
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +13 -3
- package/templates/aggregate/AggregateRoot.java.ejs +60 -2
- package/templates/aggregate/DomainEventHandler.java.ejs +27 -20
- 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/aggregate/JpaRepository.java.ejs +5 -0
- package/templates/base/gradle/build.gradle.ejs +3 -2
- package/templates/base/root/AGENTS.md.ejs +306 -45
- package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +1663 -0
- package/templates/base/root/skill-build-system-yaml.ejs +1446 -0
- package/templates/base/root/system.yaml.ejs +97 -0
- 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/CreateItemDto.java.ejs +4 -0
- package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
- package/templates/crud/DeleteCommandHandler.java.ejs +10 -2
- 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/ListQuery.java.ejs +1 -1
- package/templates/crud/ListQueryHandler.java.ejs +8 -8
- package/templates/crud/ScaffoldCommand.java.ejs +12 -0
- package/templates/crud/ScaffoldCommandHandler.java.ejs +43 -0
- package/templates/crud/ScaffoldQuery.java.ejs +13 -0
- package/templates/crud/ScaffoldQueryHandler.java.ejs +41 -0
- package/templates/crud/SubEntityAddCommand.java.ejs +21 -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/crud/UpdateCommand.java.ejs +4 -0
- package/templates/evaluate/report.html.ejs +1363 -0
- package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
- package/templates/kafka-event/Event.java.ejs +16 -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 +23 -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/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
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
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# EJEMPLO: ENDPOINTS CON VERSIONADO MÚLTIPLE (v1 + v2)
|
|
2
|
+
# Demuestra cómo v1 y v2 coexisten generando controladores independientes.
|
|
3
|
+
#
|
|
4
|
+
# REGLA ANTI-DUPLICADO:
|
|
5
|
+
# Si un useCase declarado en v2 ya fue generado en v1, el generador
|
|
6
|
+
# omite la creación del Command/Query + Handler y SOLO agrega la
|
|
7
|
+
# referencia en el controlador de v2.
|
|
8
|
+
#
|
|
9
|
+
# RESULTADO:
|
|
10
|
+
# - 1 controlador por versión (ProductV1Controller, ProductV2Controller)
|
|
11
|
+
# - Use cases propios de cada versión generados una sola vez
|
|
12
|
+
# - Use cases compartidos referenciados sin regenerar
|
|
13
|
+
|
|
14
|
+
aggregates:
|
|
15
|
+
- name: Product
|
|
16
|
+
entities:
|
|
17
|
+
- name: product
|
|
18
|
+
isRoot: true
|
|
19
|
+
tableName: products
|
|
20
|
+
audit:
|
|
21
|
+
enabled: true
|
|
22
|
+
fields:
|
|
23
|
+
- name: id
|
|
24
|
+
type: String
|
|
25
|
+
- name: name
|
|
26
|
+
type: String
|
|
27
|
+
validations:
|
|
28
|
+
- type: NotBlank
|
|
29
|
+
- name: description
|
|
30
|
+
type: String
|
|
31
|
+
- name: price
|
|
32
|
+
type: BigDecimal
|
|
33
|
+
validations:
|
|
34
|
+
- type: Positive
|
|
35
|
+
- name: stock
|
|
36
|
+
type: Integer
|
|
37
|
+
readOnly: true
|
|
38
|
+
defaultValue: 0
|
|
39
|
+
- name: active
|
|
40
|
+
type: Boolean
|
|
41
|
+
readOnly: true
|
|
42
|
+
defaultValue: true
|
|
43
|
+
|
|
44
|
+
endpoints:
|
|
45
|
+
basePath: /products
|
|
46
|
+
versions:
|
|
47
|
+
# ── v1: CRUD estándar ──────────────────────────────────────────────────
|
|
48
|
+
- version: v1
|
|
49
|
+
operations:
|
|
50
|
+
- method: GET
|
|
51
|
+
path: /{id}
|
|
52
|
+
description: "Obtener producto por ID"
|
|
53
|
+
useCase: FindProductById # completo
|
|
54
|
+
|
|
55
|
+
- method: GET
|
|
56
|
+
path: /
|
|
57
|
+
description: "Listar productos activos"
|
|
58
|
+
useCase: FindAllProducts # completo
|
|
59
|
+
|
|
60
|
+
- method: POST
|
|
61
|
+
path: /
|
|
62
|
+
description: "Crear producto"
|
|
63
|
+
useCase: CreateProduct # completo
|
|
64
|
+
|
|
65
|
+
- method: PUT
|
|
66
|
+
path: /{id}
|
|
67
|
+
description: "Actualizar datos del producto"
|
|
68
|
+
useCase: UpdateProduct # completo
|
|
69
|
+
|
|
70
|
+
- method: DELETE
|
|
71
|
+
path: /{id}
|
|
72
|
+
description: "Eliminar producto"
|
|
73
|
+
useCase: DeleteProduct # completo
|
|
74
|
+
|
|
75
|
+
# ── v2: nuevas operaciones + reuso de use cases de v1 ─────────────────
|
|
76
|
+
- version: v2
|
|
77
|
+
operations:
|
|
78
|
+
- method: GET
|
|
79
|
+
path: /{id}
|
|
80
|
+
description: "Producto con categorías y variantes embebidas"
|
|
81
|
+
useCase: FindProductByIdV2 # NUEVO handler — shape de respuesta distinto
|
|
82
|
+
|
|
83
|
+
- method: GET
|
|
84
|
+
path: /
|
|
85
|
+
description: "Listado con filtros avanzados y metadata extendida"
|
|
86
|
+
useCase: FindAllProductsV2 # NUEVO handler — parámetros de búsqueda distintos
|
|
87
|
+
|
|
88
|
+
- method: POST
|
|
89
|
+
path: /
|
|
90
|
+
description: "Crear producto (misma lógica que v1)"
|
|
91
|
+
useCase: CreateProduct # REUTILIZA v1 — no regenera handler
|
|
92
|
+
|
|
93
|
+
- method: PUT
|
|
94
|
+
path: /{id}
|
|
95
|
+
description: "Actualizar producto (misma lógica que v1)"
|
|
96
|
+
useCase: UpdateProduct # REUTILIZA v1 — no regenera handler
|
|
97
|
+
|
|
98
|
+
- method: DELETE
|
|
99
|
+
path: /{id}
|
|
100
|
+
description: "Eliminar producto (misma lógica que v1)"
|
|
101
|
+
useCase: DeleteProduct # REUTILIZA v1 — no regenera handler
|
|
102
|
+
|
|
103
|
+
- method: PUT
|
|
104
|
+
path: /{id}/activate
|
|
105
|
+
description: "Activar un producto desactivado"
|
|
106
|
+
useCase: ActivateProduct # NUEVO scaffold — TODO: product.activate()
|
|
107
|
+
|
|
108
|
+
- method: PUT
|
|
109
|
+
path: /{id}/deactivate
|
|
110
|
+
description: "Desactivar un producto activo"
|
|
111
|
+
useCase: DeactivateProduct # NUEVO scaffold — TODO: product.deactivate()
|
|
112
|
+
|
|
113
|
+
# Archivos generados:
|
|
114
|
+
#
|
|
115
|
+
# infrastructure/rest/controllers/product/
|
|
116
|
+
# ├── v1/ProductV1Controller.java ← 5 endpoints (v1)
|
|
117
|
+
# └── v2/ProductV2Controller.java ← 7 endpoints (v2)
|
|
118
|
+
#
|
|
119
|
+
# application/commands/ (solo use cases ÚNICOS — sin duplicados)
|
|
120
|
+
# ├── CreateProductCommand.java
|
|
121
|
+
# ├── UpdateProductCommand.java
|
|
122
|
+
# ├── DeleteProductCommand.java
|
|
123
|
+
# ├── ActivateProductCommand.java ← solo v2
|
|
124
|
+
# └── DeactivateProductCommand.java ← solo v2
|
|
125
|
+
#
|
|
126
|
+
# application/queries/ (solo use cases ÚNICOS)
|
|
127
|
+
# ├── FindProductByIdQuery.java
|
|
128
|
+
# ├── FindAllProductsQuery.java
|
|
129
|
+
# ├── FindProductByIdV2Query.java ← solo v2
|
|
130
|
+
# └── FindAllProductsV2Query.java ← solo v2
|
|
131
|
+
#
|
|
132
|
+
# application/usecases/
|
|
133
|
+
# ├── CreateProductCommandHandler.java ← completo
|
|
134
|
+
# ├── UpdateProductCommandHandler.java ← completo
|
|
135
|
+
# ├── DeleteProductCommandHandler.java ← completo
|
|
136
|
+
# ├── ActivateProductCommandHandler.java ← scaffold + TODO
|
|
137
|
+
# ├── DeactivateProductCommandHandler.java ← scaffold + TODO
|
|
138
|
+
# ├── FindProductByIdQueryHandler.java ← completo
|
|
139
|
+
# ├── FindAllProductsQueryHandler.java ← completo
|
|
140
|
+
# ├── FindProductByIdV2QueryHandler.java ← completo (shape distinto)
|
|
141
|
+
# └── FindAllProductsV2QueryHandler.java ← completo (filtros extendidos)
|
|
142
|
+
#
|
|
143
|
+
# CreateProduct, UpdateProduct, DeleteProduct aparecen en v2 pero ya
|
|
144
|
+
# existen de v1 → ProductV2Controller los inyecta sin regenerar handlers.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# EJEMPLO: ENDPOINTS DECLARATIVOS — CASO BÁSICO (una versión)
|
|
2
|
+
# Módulo de pedidos con operaciones específicas del negocio.
|
|
3
|
+
#
|
|
4
|
+
# COMPORTAMIENTO CONDICIONAL:
|
|
5
|
+
#
|
|
6
|
+
# SIN sección endpoints: → genera 5 CRUD fijos por defecto:
|
|
7
|
+
# CreateOrder, UpdateOrder, DeleteOrder,
|
|
8
|
+
# FindOrderById, FindAllOrders
|
|
9
|
+
#
|
|
10
|
+
# CON sección endpoints: → genera ÚNICAMENTE las operaciones
|
|
11
|
+
# declaradas en operations[] — ni más, ni menos.
|
|
12
|
+
#
|
|
13
|
+
# TYPE INFERIDO desde method:
|
|
14
|
+
# GET → query
|
|
15
|
+
# POST/PUT/DELETE/PATCH → command
|
|
16
|
+
#
|
|
17
|
+
# SCAFFOLD vs COMPLETO:
|
|
18
|
+
# Use cases estándar (Create*, Update*, Delete*, FindById*, FindAll*):
|
|
19
|
+
# → implementación completa con lógica de repositorio
|
|
20
|
+
# Use cases custom (ConfirmOrder, CancelOrder, etc.):
|
|
21
|
+
# → scaffold con // TODO apuntando al método de negocio sugerido
|
|
22
|
+
|
|
23
|
+
aggregates:
|
|
24
|
+
- name: Order
|
|
25
|
+
entities:
|
|
26
|
+
- name: order
|
|
27
|
+
isRoot: true
|
|
28
|
+
tableName: orders
|
|
29
|
+
audit:
|
|
30
|
+
enabled: true
|
|
31
|
+
trackUser: true
|
|
32
|
+
fields:
|
|
33
|
+
- name: id
|
|
34
|
+
type: String
|
|
35
|
+
- name: orderNumber
|
|
36
|
+
type: String
|
|
37
|
+
validations:
|
|
38
|
+
- type: NotBlank
|
|
39
|
+
message: "Order number is required"
|
|
40
|
+
- name: customerId
|
|
41
|
+
type: String
|
|
42
|
+
reference:
|
|
43
|
+
aggregate: Customer
|
|
44
|
+
module: customers
|
|
45
|
+
- name: status
|
|
46
|
+
type: OrderStatus
|
|
47
|
+
readOnly: true
|
|
48
|
+
- name: totalAmount
|
|
49
|
+
type: BigDecimal
|
|
50
|
+
readOnly: true
|
|
51
|
+
defaultValue: "0.00"
|
|
52
|
+
|
|
53
|
+
enums:
|
|
54
|
+
- name: OrderStatus
|
|
55
|
+
initialValue: PENDING
|
|
56
|
+
transitions:
|
|
57
|
+
- from: PENDING
|
|
58
|
+
to: CONFIRMED
|
|
59
|
+
method: confirm
|
|
60
|
+
- from: [PENDING, CONFIRMED]
|
|
61
|
+
to: CANCELLED
|
|
62
|
+
method: cancel
|
|
63
|
+
values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
|
|
64
|
+
|
|
65
|
+
events:
|
|
66
|
+
- name: OrderPlacedEvent
|
|
67
|
+
fields:
|
|
68
|
+
- name: orderId
|
|
69
|
+
type: String
|
|
70
|
+
- name: customerId
|
|
71
|
+
type: String
|
|
72
|
+
kafka: true
|
|
73
|
+
|
|
74
|
+
# ─── Sección endpoints ─────────────────────────────────────────────────────
|
|
75
|
+
# Presente → genera solo los use cases declarados
|
|
76
|
+
# Ausente → comportamiento actual (5 CRUD por defecto)
|
|
77
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
endpoints:
|
|
80
|
+
basePath: /orders
|
|
81
|
+
versions:
|
|
82
|
+
- version: v1
|
|
83
|
+
operations:
|
|
84
|
+
- method: GET
|
|
85
|
+
path: /{id}
|
|
86
|
+
description: "Obtener detalle de un pedido"
|
|
87
|
+
useCase: FindOrderById # completo: FindOrderByIdQuery + Handler
|
|
88
|
+
|
|
89
|
+
- method: GET
|
|
90
|
+
path: /
|
|
91
|
+
description: "Listar pedidos con paginación"
|
|
92
|
+
useCase: FindAllOrders # completo: FindAllOrdersQuery + Handler
|
|
93
|
+
|
|
94
|
+
- method: POST
|
|
95
|
+
path: /
|
|
96
|
+
description: "Crear nuevo pedido"
|
|
97
|
+
useCase: CreateOrder # completo: CreateOrderCommand + Handler
|
|
98
|
+
|
|
99
|
+
- method: PUT
|
|
100
|
+
path: /{id}/confirm
|
|
101
|
+
description: "Confirmar pedido pendiente — PENDING → CONFIRMED"
|
|
102
|
+
useCase: ConfirmOrder # scaffold: TODO → order.confirm(); repo.save(order)
|
|
103
|
+
|
|
104
|
+
- method: PUT
|
|
105
|
+
path: /{id}/cancel
|
|
106
|
+
description: "Cancelar pedido — [PENDING, CONFIRMED] → CANCELLED"
|
|
107
|
+
useCase: CancelOrder # scaffold: TODO → order.cancel(); repo.save(order)
|
|
108
|
+
|
|
109
|
+
- method: DELETE
|
|
110
|
+
path: /{id}
|
|
111
|
+
description: "Eliminar pedido"
|
|
112
|
+
useCase: DeleteOrder # completo: DeleteOrderCommand + Handler
|
|
113
|
+
|
|
114
|
+
# Archivos generados con esta configuración:
|
|
115
|
+
#
|
|
116
|
+
# infrastructure/rest/controllers/order/v1/
|
|
117
|
+
# └── OrderV1Controller.java ← 6 métodos exactos
|
|
118
|
+
#
|
|
119
|
+
# application/commands/
|
|
120
|
+
# ├── CreateOrderCommand.java
|
|
121
|
+
# ├── ConfirmOrderCommand.java
|
|
122
|
+
# ├── CancelOrderCommand.java
|
|
123
|
+
# └── DeleteOrderCommand.java
|
|
124
|
+
#
|
|
125
|
+
# application/queries/
|
|
126
|
+
# ├── FindOrderByIdQuery.java
|
|
127
|
+
# └── FindAllOrdersQuery.java
|
|
128
|
+
#
|
|
129
|
+
# application/usecases/
|
|
130
|
+
# ├── CreateOrderCommandHandler.java ← implementación completa
|
|
131
|
+
# ├── ConfirmOrderCommandHandler.java ← scaffold + TODO
|
|
132
|
+
# ├── CancelOrderCommandHandler.java ← scaffold + TODO
|
|
133
|
+
# ├── DeleteOrderCommandHandler.java ← implementación completa
|
|
134
|
+
# ├── FindOrderByIdQueryHandler.java ← implementación completa
|
|
135
|
+
# └── FindAllOrdersQueryHandler.java ← implementación completa
|