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
|
@@ -45,30 +45,49 @@
|
|
|
45
45
|
#
|
|
46
46
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
47
47
|
#
|
|
48
|
-
# 3. orders/domain/models/entities/Order.java (
|
|
48
|
+
# 3. orders/domain/models/entities/Order.java (con triggers: declarados en events[])
|
|
49
49
|
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
50
|
+
# // El desarrollador NO escribe raise() manualmente — el generador lo emite
|
|
51
|
+
# // automáticamente basándose en triggers: declarados en domain.yaml.
|
|
52
52
|
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
53
|
+
# // Construcción de args (en orden):
|
|
54
|
+
# // 1º aggregateId → this.getId() (primer arg del constructor DomainEvent, siempre)
|
|
55
|
+
# // 2º+ campos del evento → resolución automática:
|
|
56
|
+
# // · {entityName}Id (ej: orderId en Order) → this.getId()
|
|
57
|
+
# // · coincide con campo de la entidad → this.get{Field}()
|
|
58
|
+
# // · nombre termina en At + tipo LocalDateTime → LocalDateTime.now()
|
|
59
|
+
# // · no resuelto → null /* TODO: provide X */
|
|
55
60
|
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
61
|
+
# public void place() {
|
|
62
|
+
# this.status = this.status.transitionTo(OrderStatus.PLACED);
|
|
63
|
+
# raise(new OrderPlaced(this.getId(), this.getCustomerId(), this.getOrderNumber(), this.getTotalAmount()));
|
|
64
|
+
# // ^—aggregateId ^—customerId ^—orderNumber ^—totalAmount
|
|
65
|
+
# // orderId no se pasa — disponible como event.getAggregateId() en el consumidor
|
|
66
|
+
# }
|
|
67
|
+
#
|
|
68
|
+
# public void cancel() {
|
|
69
|
+
# this.status = this.status.transitionTo(OrderStatus.CANCELLED);
|
|
70
|
+
# raise(new OrderCancelled(this.getId(), null /* TODO: provide reason */));
|
|
71
|
+
# // ^—aggregateId ^—reason no resuelto automáticamente
|
|
72
|
+
# }
|
|
73
|
+
#
|
|
74
|
+
# public void ship() {
|
|
75
|
+
# this.status = this.status.transitionTo(OrderStatus.SHIPPED);
|
|
76
|
+
# raise(new OrderShipped(this.getId(), null /* TODO: provide shippingAddress */, null /* TODO: provide estimatedDeliveryDate */));
|
|
77
|
+
# }
|
|
59
78
|
#
|
|
60
|
-
# public List<DomainEvent> pullDomainEvents() {
|
|
61
79
|
# List<DomainEvent> events =
|
|
62
80
|
# Collections.unmodifiableList(new ArrayList<>(_domainEvents));
|
|
63
81
|
# _domainEvents.clear();
|
|
64
82
|
# return events; // retorna y limpia (operación atómica)
|
|
65
83
|
# }
|
|
66
84
|
#
|
|
67
|
-
# //
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
85
|
+
# // Con triggers: auto-generados NO hace falta escribir raise() a mano. Ver arriba.
|
|
86
|
+
# // Si un evento NO declara triggers, el dev puede llamar a raise() explicitamente:
|
|
87
|
+
# // public void placeManual(String customerId, BigDecimal total) {
|
|
88
|
+
# // this.status = OrderStatus.PLACED;
|
|
89
|
+
# // raise(new OrderPlaced(this.id, customerId, orderNumber, total));
|
|
90
|
+
# // }
|
|
72
91
|
# }
|
|
73
92
|
#
|
|
74
93
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -123,6 +142,65 @@
|
|
|
123
142
|
# Para conectar un broker externo después de generar:
|
|
124
143
|
# eva4j g kafka-event orders OrderPlaced
|
|
125
144
|
# (el selector muestra los eventos declarados en este domain.yaml como opciones)
|
|
145
|
+
#
|
|
146
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
147
|
+
#
|
|
148
|
+
# SECCIÓN listeners: — eventos externos que CONSUME este módulo
|
|
149
|
+
#
|
|
150
|
+
# Complemento de events: (producción). A diferencia de events:, que vive
|
|
151
|
+
# DENTRO del agregado porque pertenece al modelo de dominio, listeners: vive
|
|
152
|
+
# en el NIVEL RAÍZ del domain.yaml porque es una preocupación de integración.
|
|
153
|
+
#
|
|
154
|
+
# Estructura generada por cada listener:
|
|
155
|
+
#
|
|
156
|
+
# ┌─ application/events/
|
|
157
|
+
# │ PaymentApprovedIntegrationEvent.java ← record tipado con los fields
|
|
158
|
+
# │
|
|
159
|
+
# └─ infrastructure/kafkaListener/
|
|
160
|
+
# PaymentApprovedKafkaListener.java ← @KafkaListener que despacha
|
|
161
|
+
# al useCase via UseCaseMediator
|
|
162
|
+
#
|
|
163
|
+
# Código generado de ejemplo:
|
|
164
|
+
#
|
|
165
|
+
# // application/events/PaymentApprovedIntegrationEvent.java
|
|
166
|
+
# public record PaymentApprovedIntegrationEvent(
|
|
167
|
+
# String orderId,
|
|
168
|
+
# String paymentId,
|
|
169
|
+
# LocalDateTime approvedAt
|
|
170
|
+
# ) {}
|
|
171
|
+
#
|
|
172
|
+
# // infrastructure/kafkaListener/PaymentApprovedKafkaListener.java
|
|
173
|
+
# @Component
|
|
174
|
+
# public class PaymentApprovedKafkaListener {
|
|
175
|
+
#
|
|
176
|
+
# private final UseCaseMediator useCaseMediator;
|
|
177
|
+
#
|
|
178
|
+
# @Value("${kafka.topics.PAYMENT_APPROVED}")
|
|
179
|
+
# private String paymentApprovedTopic;
|
|
180
|
+
#
|
|
181
|
+
# @KafkaListener(topics = "${kafka.topics.PAYMENT_APPROVED}")
|
|
182
|
+
# public void handle(EventEnvelope<PaymentApprovedIntegrationEvent> envelope,
|
|
183
|
+
# Acknowledgment ack) {
|
|
184
|
+
# PaymentApprovedIntegrationEvent event = envelope.data();
|
|
185
|
+
# useCaseMediator.dispatch(new ConfirmOrderCommand(event.orderId()));
|
|
186
|
+
# ack.acknowledge();
|
|
187
|
+
# }
|
|
188
|
+
# }
|
|
189
|
+
#
|
|
190
|
+
# Propiedades:
|
|
191
|
+
# event (requerido) — Nombre del evento consumido (PascalCase + sufijo Event)
|
|
192
|
+
# producer (requerido) — Módulo que produce el evento (referencia documental)
|
|
193
|
+
# topic (requerido) — Topic Kafka. Si el proyecto tiene system.yaml el generador
|
|
194
|
+
# puede inferirlo; en módulos standalone es obligatorio
|
|
195
|
+
# declararlo explícitamente (no hay otra fuente de verdad).
|
|
196
|
+
# useCase (requerido) — Caso de uso que maneja el evento (PascalCase)
|
|
197
|
+
# fields (requerido) — Payload recibido; genera el record IntegrationEvent
|
|
198
|
+
# y tipifica el Command despachado desde el listener.
|
|
199
|
+
# nestedTypes (opcional) — Tipos auxiliares para campos objeto en fields.
|
|
200
|
+
# Genera {Name}.java en application/events/ por cada entrada.
|
|
201
|
+
# Usar cuando un field no es un tipo Java primitivo sino
|
|
202
|
+
# un objeto estructurado del dominio del productor.
|
|
203
|
+
# El name se declara camelCase y se genera en PascalCase.
|
|
126
204
|
|
|
127
205
|
aggregates:
|
|
128
206
|
- name: Order
|
|
@@ -160,42 +238,110 @@ aggregates:
|
|
|
160
238
|
transitions:
|
|
161
239
|
- from: DRAFT
|
|
162
240
|
to: PLACED
|
|
241
|
+
method: place
|
|
163
242
|
- from: DRAFT
|
|
164
243
|
to: CANCELLED
|
|
244
|
+
method: cancel
|
|
165
245
|
- from: PLACED
|
|
166
246
|
to: CONFIRMED
|
|
247
|
+
method: confirm
|
|
167
248
|
- from: PLACED
|
|
168
249
|
to: CANCELLED
|
|
250
|
+
method: cancel
|
|
169
251
|
- from: CONFIRMED
|
|
170
252
|
to: SHIPPED
|
|
253
|
+
method: ship
|
|
171
254
|
- from: CONFIRMED
|
|
172
255
|
to: CANCELLED
|
|
256
|
+
method: cancel
|
|
173
257
|
- from: SHIPPED
|
|
174
258
|
to: DELIVERED
|
|
259
|
+
method: deliver
|
|
175
260
|
|
|
176
261
|
# ─── Domain Events ────────────────────────────────────────────────────────
|
|
177
262
|
# Siblings de entities:, enums: y valueObjects:
|
|
178
263
|
# La declaración es agnóstica al broker — ningún campo de infraestructura aquí.
|
|
179
264
|
# Cada entrada genera: domain/models/events/{Name}Event.java
|
|
265
|
+
#
|
|
266
|
+
# triggers: (lista de nombres de métodos de transición)
|
|
267
|
+
# Conecta automáticamente una transición de estado con la publicación de este evento.
|
|
268
|
+
# El generador emite raise(new XEvent(...)) dentro del método de transición.
|
|
269
|
+
# Si no se declara triggers, el dev debe llamar a raise() manualmente.
|
|
270
|
+
#
|
|
271
|
+
# Reglas de resolución de argumentos (automática):
|
|
272
|
+
# 1. aggregateId → siempre this.getId() (primer arg del constructor de DomainEvent)
|
|
273
|
+
# 2. {entityName}Id (ej: orderId en Order) → IGNORADO — el id ya está en aggregateId
|
|
274
|
+
# El consumidor usa event.getAggregateId(); no declarar este campo en events[].fields
|
|
275
|
+
# 3. coincide con campo de la entidad → this.get{Field}()
|
|
276
|
+
# 4. nombre termina en At + tipo LocalDateTime → LocalDateTime.now()
|
|
277
|
+
# 5. no resuelto → null /* TODO: provide {fieldName} */
|
|
180
278
|
|
|
181
279
|
events:
|
|
182
280
|
- name: OrderPlaced
|
|
281
|
+
triggers:
|
|
282
|
+
- place # auto-genera raise() en el método place() de Order
|
|
183
283
|
fields:
|
|
184
|
-
|
|
284
|
+
# orderId NO se declara aquí — ya disponible como event.getAggregateId()
|
|
285
|
+
- name: customerId # → this.getCustomerId() (campo de la entidad)
|
|
185
286
|
type: String
|
|
186
|
-
- name: orderNumber
|
|
287
|
+
- name: orderNumber # → this.getOrderNumber()
|
|
187
288
|
type: String
|
|
188
|
-
- name: totalAmount
|
|
289
|
+
- name: totalAmount # → this.getTotalAmount()
|
|
189
290
|
type: BigDecimal
|
|
190
291
|
|
|
191
292
|
- name: OrderCancelled
|
|
293
|
+
triggers:
|
|
294
|
+
- cancel # auto-genera raise() en cancel() de Order
|
|
192
295
|
fields:
|
|
193
|
-
- name: reason
|
|
296
|
+
- name: reason # no resuelto → null /* TODO: provide reason */
|
|
194
297
|
type: String
|
|
195
298
|
|
|
196
299
|
- name: OrderShipped
|
|
300
|
+
triggers:
|
|
301
|
+
- ship
|
|
197
302
|
fields:
|
|
198
|
-
- name: shippingAddress
|
|
303
|
+
- name: shippingAddress # no resuelto → null /* TODO */
|
|
199
304
|
type: String
|
|
200
|
-
- name: estimatedDeliveryDate
|
|
305
|
+
- name: estimatedDeliveryDate # no resuelto → null /* TODO */
|
|
201
306
|
type: LocalDate
|
|
307
|
+
|
|
308
|
+
# ─── Eventos externos que este módulo CONSUME ────────────────────────────────
|
|
309
|
+
# Nivel raíz, sibling de aggregates:
|
|
310
|
+
# Requiere broker instalado (eva add kafka-client) para generación.
|
|
311
|
+
# topic: es obligatorio en módulos standalone (sin system.yaml).
|
|
312
|
+
|
|
313
|
+
listeners:
|
|
314
|
+
- event: PaymentApprovedEvent
|
|
315
|
+
producer: payments # módulo origen (solo documental)
|
|
316
|
+
topic: PAYMENT_APPROVED # obligatorio si no hay system.yaml
|
|
317
|
+
useCase: ConfirmOrder # Command handler que se ejecuta
|
|
318
|
+
fields: # payload del Integration Event recibido
|
|
319
|
+
- name: orderId
|
|
320
|
+
type: String
|
|
321
|
+
- name: approvedAt
|
|
322
|
+
type: LocalDateTime
|
|
323
|
+
- name: details
|
|
324
|
+
type: PaymentDetails # objeto estructurado → declarado en nestedTypes
|
|
325
|
+
nestedTypes: # genera PaymentDetails.java en application/events/
|
|
326
|
+
- name: paymentDetails
|
|
327
|
+
fields:
|
|
328
|
+
- name: paymentId
|
|
329
|
+
type: String
|
|
330
|
+
- name: method
|
|
331
|
+
type: String
|
|
332
|
+
- name: currency
|
|
333
|
+
type: String
|
|
334
|
+
- name: amount
|
|
335
|
+
type: BigDecimal
|
|
336
|
+
|
|
337
|
+
- event: PaymentRejectedEvent
|
|
338
|
+
producer: payments
|
|
339
|
+
topic: PAYMENT_REJECTED
|
|
340
|
+
useCase: CancelOrder
|
|
341
|
+
fields:
|
|
342
|
+
- name: orderId
|
|
343
|
+
type: String
|
|
344
|
+
- name: rejectedAt
|
|
345
|
+
type: LocalDateTime
|
|
346
|
+
- name: reason
|
|
347
|
+
type: String
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# EJEMPLO: LISTENERS — eventos externos que CONSUME un módulo
|
|
2
|
+
# Complemento de domain-events.yaml (producción).
|
|
3
|
+
#
|
|
4
|
+
# La sección listeners: vive en el NIVEL RAÍZ del domain.yaml,
|
|
5
|
+
# como sibling de aggregates:, porque es una responsabilidad de
|
|
6
|
+
# integración/infraestructura, no de dominio.
|
|
7
|
+
#
|
|
8
|
+
# Generación:
|
|
9
|
+
# eva4j g entities reservations
|
|
10
|
+
#
|
|
11
|
+
# Requiere broker instalado:
|
|
12
|
+
# eva add kafka-client
|
|
13
|
+
#
|
|
14
|
+
# ─── ARCHIVOS GENERADOS ───────────────────────────────────────────────────────
|
|
15
|
+
#
|
|
16
|
+
# Por cada entrada en listeners: se generan CINCO archivos:
|
|
17
|
+
#
|
|
18
|
+
# application/events/{Name}IntegrationEvent.java ← record contrato (documentación)
|
|
19
|
+
# application/commands/{UseCase}Command.java ← comando tipado despachado
|
|
20
|
+
# application/usecases/{UseCase}CommandHandler.java ← handler stub (implementar)
|
|
21
|
+
# infrastructure/kafkaListener/{Name}KafkaListener.java
|
|
22
|
+
# kafka.yaml (todas las envs) ← topic registrado
|
|
23
|
+
#
|
|
24
|
+
# Si se declaran nestedTypes:, se genera también:
|
|
25
|
+
# application/events/{NestedName}.java ← record auxiliar por tipo objeto
|
|
26
|
+
#
|
|
27
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
#
|
|
29
|
+
# EJEMPLO con nestedTypes: — campo 'details' de tipo PaymentDetails
|
|
30
|
+
#
|
|
31
|
+
# 0. reservations/application/events/PaymentDetails.java (nested type)
|
|
32
|
+
#
|
|
33
|
+
# public record PaymentDetails(
|
|
34
|
+
# String paymentId,
|
|
35
|
+
# String method,
|
|
36
|
+
# String currency,
|
|
37
|
+
# BigDecimal amount
|
|
38
|
+
# ) {}
|
|
39
|
+
#
|
|
40
|
+
# 1. reservations/application/events/PaymentApprovedIntegrationEvent.java
|
|
41
|
+
#
|
|
42
|
+
# public record PaymentApprovedIntegrationEvent(
|
|
43
|
+
# String reservationId,
|
|
44
|
+
# LocalDateTime approvedAt,
|
|
45
|
+
# PaymentDetails details
|
|
46
|
+
# ) {}
|
|
47
|
+
#
|
|
48
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
+
#
|
|
50
|
+
# 2. reservations/infrastructure/kafkaListener/PaymentApprovedKafkaListener.java
|
|
51
|
+
#
|
|
52
|
+
# @Component
|
|
53
|
+
# public class PaymentApprovedKafkaListener {
|
|
54
|
+
#
|
|
55
|
+
# private final UseCaseMediator useCaseMediator;
|
|
56
|
+
# private final ObjectMapper objectMapper;
|
|
57
|
+
#
|
|
58
|
+
# @Value("${topics.payment-approved}")
|
|
59
|
+
# private String paymentApprovedTopic;
|
|
60
|
+
#
|
|
61
|
+
# public PaymentApprovedKafkaListener(UseCaseMediator useCaseMediator,
|
|
62
|
+
# ObjectMapper objectMapper) {
|
|
63
|
+
# this.useCaseMediator = useCaseMediator;
|
|
64
|
+
# this.objectMapper = objectMapper;
|
|
65
|
+
# }
|
|
66
|
+
#
|
|
67
|
+
# @KafkaListener(topics = "${topics.payment-approved}")
|
|
68
|
+
# public void handle(EventEnvelope<Map<String, Object>> event, Acknowledgment ack) {
|
|
69
|
+
# String reservationId = objectMapper.convertValue(
|
|
70
|
+
# event.data().get("reservationId"), String.class);
|
|
71
|
+
# LocalDateTime approvedAt = objectMapper.convertValue(
|
|
72
|
+
# event.data().get("approvedAt"), LocalDateTime.class);
|
|
73
|
+
# PaymentDetails details = objectMapper.convertValue(
|
|
74
|
+
# event.data().get("details"), PaymentDetails.class);
|
|
75
|
+
# useCaseMediator.dispatch(new ConfirmReservationCommand(
|
|
76
|
+
# reservationId, approvedAt, details));
|
|
77
|
+
# ack.acknowledge();
|
|
78
|
+
# }
|
|
79
|
+
# }
|
|
80
|
+
#
|
|
81
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
#
|
|
83
|
+
# REGLA DE topic:
|
|
84
|
+
#
|
|
85
|
+
# • Módulo standalone (sin system.yaml) → topic: OBLIGATORIO
|
|
86
|
+
# No hay ninguna otra fuente de verdad para el nombre del topic.
|
|
87
|
+
#
|
|
88
|
+
# • Proyecto con system.yaml → topic: puede omitirse.
|
|
89
|
+
# El generador lo infiere de integrations.async[].topic donde coincidan
|
|
90
|
+
# event y producer.
|
|
91
|
+
#
|
|
92
|
+
# • topic: declarado explícitamente + system.yaml → el valor declarado
|
|
93
|
+
# tiene PRECEDENCIA sobre la inferencia del system.yaml.
|
|
94
|
+
#
|
|
95
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
#
|
|
97
|
+
# CONTRASTE: producción vs. consumo
|
|
98
|
+
#
|
|
99
|
+
# domain.yaml
|
|
100
|
+
# ├── aggregates:
|
|
101
|
+
# │ └── [Aggregate]
|
|
102
|
+
# │ └── events: → Domain Events que PRODUCE (domain/models/events/)
|
|
103
|
+
# │
|
|
104
|
+
# └── listeners: → Integration Events que CONSUME (infrastructure/kafkaListener/)
|
|
105
|
+
|
|
106
|
+
aggregates:
|
|
107
|
+
- name: Reservation
|
|
108
|
+
entities:
|
|
109
|
+
- name: reservation
|
|
110
|
+
isRoot: true
|
|
111
|
+
tableName: reservations
|
|
112
|
+
audit:
|
|
113
|
+
enabled: true
|
|
114
|
+
fields:
|
|
115
|
+
- name: id
|
|
116
|
+
type: String
|
|
117
|
+
- name: screeningId
|
|
118
|
+
type: String
|
|
119
|
+
- name: customerId
|
|
120
|
+
type: String
|
|
121
|
+
- name: status
|
|
122
|
+
type: ReservationStatus
|
|
123
|
+
- name: totalAmount
|
|
124
|
+
type: BigDecimal
|
|
125
|
+
readOnly: true
|
|
126
|
+
|
|
127
|
+
enums:
|
|
128
|
+
- name: ReservationStatus
|
|
129
|
+
initialValue: PENDING
|
|
130
|
+
values:
|
|
131
|
+
- PENDING
|
|
132
|
+
- CONFIRMED
|
|
133
|
+
- CANCELLED
|
|
134
|
+
- EXPIRED
|
|
135
|
+
transitions:
|
|
136
|
+
- from: PENDING
|
|
137
|
+
to: CONFIRMED
|
|
138
|
+
method: confirm
|
|
139
|
+
- from: PENDING
|
|
140
|
+
to: CANCELLED
|
|
141
|
+
method: cancel
|
|
142
|
+
- from: PENDING
|
|
143
|
+
to: EXPIRED
|
|
144
|
+
method: expire
|
|
145
|
+
|
|
146
|
+
events:
|
|
147
|
+
- name: ReservationConfirmed
|
|
148
|
+
triggers:
|
|
149
|
+
- confirm
|
|
150
|
+
fields:
|
|
151
|
+
# reservationId NO se declara — disponible como event.getAggregateId()
|
|
152
|
+
- name: customerId
|
|
153
|
+
type: String
|
|
154
|
+
- name: confirmedAt
|
|
155
|
+
type: LocalDateTime
|
|
156
|
+
|
|
157
|
+
- name: ReservationCancelled
|
|
158
|
+
fields:
|
|
159
|
+
- name: reservationId
|
|
160
|
+
type: String
|
|
161
|
+
- name: reason
|
|
162
|
+
type: String
|
|
163
|
+
|
|
164
|
+
# ─── Eventos externos que este módulo CONSUME ────────────────────────────────
|
|
165
|
+
# Nivel raíz, sibling de aggregates:
|
|
166
|
+
# topic: es obligatorio en módulos standalone (sin system.yaml).
|
|
167
|
+
|
|
168
|
+
listeners:
|
|
169
|
+
- event: PaymentApprovedEvent
|
|
170
|
+
producer: payments # módulo origen (solo referencia documental)
|
|
171
|
+
topic: PAYMENT_APPROVED # obligatorio: no hay system.yaml en este ejemplo
|
|
172
|
+
useCase: ConfirmReservation # command handler invocado al recibir el evento
|
|
173
|
+
fields: # payload del Integration Event recibido
|
|
174
|
+
- name: reservationId
|
|
175
|
+
type: String
|
|
176
|
+
- name: approvedAt
|
|
177
|
+
type: LocalDateTime
|
|
178
|
+
- name: details
|
|
179
|
+
type: PaymentDetails # objeto estructurado → declarado en nestedTypes
|
|
180
|
+
nestedTypes: # genera PaymentDetails.java en application/events/
|
|
181
|
+
- name: paymentDetails # camelCase → PascalCase en el record generado
|
|
182
|
+
fields:
|
|
183
|
+
- name: paymentId
|
|
184
|
+
type: String
|
|
185
|
+
- name: method
|
|
186
|
+
type: String
|
|
187
|
+
- name: currency
|
|
188
|
+
type: String
|
|
189
|
+
- name: amount
|
|
190
|
+
type: BigDecimal
|
|
191
|
+
|
|
192
|
+
- event: PaymentRejectedEvent
|
|
193
|
+
producer: payments
|
|
194
|
+
topic: PAYMENT_REJECTED
|
|
195
|
+
useCase: ExpireReservation
|
|
196
|
+
fields:
|
|
197
|
+
- name: reservationId
|
|
198
|
+
type: String
|
|
199
|
+
- name: rejectedAt
|
|
200
|
+
type: LocalDateTime
|
|
201
|
+
- name: reason
|
|
202
|
+
type: String
|
|
203
|
+
|
|
204
|
+
- event: ScreeningCancelledEvent
|
|
205
|
+
producer: screenings
|
|
206
|
+
topic: SCREENING_CANCELLED
|
|
207
|
+
useCase: CancelReservationsByScreening
|
|
208
|
+
fields:
|
|
209
|
+
- name: screeningId
|
|
210
|
+
type: String
|
|
211
|
+
- name: cancelledAt
|
|
212
|
+
type: LocalDateTime
|