eva4j 1.0.16 → 1.0.18
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 +220 -5
- package/DOMAIN_YAML_GUIDE.md +188 -3
- package/FUTURE_FEATURES.md +33 -52
- package/QUICK_REFERENCE.md +8 -4
- package/bin/eva4j.js +70 -2
- package/config/defaults.json +1 -0
- package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
- package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
- package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
- package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
- package/docs/commands/EVALUATE_SYSTEM.md +290 -10
- package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
- package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
- package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
- package/docs/commands/INDEX.md +27 -3
- package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
- package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
- package/docs/prototype/system/RISKS.md +277 -0
- package/docs/prototype/system/customers.yaml +133 -0
- package/docs/prototype/system/inventory.yaml +109 -0
- package/docs/prototype/system/notifications.yaml +131 -0
- package/docs/prototype/system/orders.yaml +241 -0
- package/docs/prototype/system/payments.yaml +256 -0
- package/docs/prototype/system/products.yaml +168 -0
- package/docs/prototype/system/system.yaml +269 -0
- package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
- package/examples/domain-events.yaml +26 -0
- package/examples/domain-read-models.yaml +113 -0
- package/examples/system/customer.yaml +89 -0
- package/examples/system/orders.yaml +119 -0
- package/examples/system/product.yaml +27 -0
- package/examples/system/system.yaml +80 -0
- package/package.json +1 -1
- package/read-model-spec.md +664 -0
- package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
- package/src/agents/design-gap-analyst.agent.md +383 -0
- package/src/agents/design-reviewer-temporal.agent.md +412 -0
- package/src/agents/design-reviewer.agent.md +34 -5
- package/src/agents/implement-use-cases.prompt.md +179 -0
- package/src/agents/ux-gap-analyst.agent.md +412 -0
- package/src/commands/add-rabbitmq-client.js +261 -0
- package/src/commands/add-temporal-client.js +22 -2
- package/src/commands/build.js +267 -11
- package/src/commands/evaluate-system.js +700 -13
- package/src/commands/generate-entities.js +560 -24
- package/src/commands/generate-http-exchange.js +3 -0
- package/src/commands/generate-kafka-event.js +3 -0
- package/src/commands/generate-kafka-listener.js +3 -0
- package/src/commands/generate-rabbitmq-event.js +665 -0
- package/src/commands/generate-rabbitmq-listener.js +205 -0
- package/src/commands/generate-record.js +2 -2
- package/src/commands/generate-resource.js +4 -1
- package/src/commands/generate-temporal-activity.js +970 -33
- package/src/commands/generate-temporal-flow.js +98 -38
- package/src/commands/generate-temporal-system.js +708 -0
- package/src/commands/generate-usecase.js +4 -1
- package/src/skills/build-system-yaml/SKILL.md +343 -2
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +253 -26
- package/src/skills/build-system-yaml/references/module-spec.md +90 -9
- package/src/skills/build-system-yaml/references/system-yaml-spec.md +36 -0
- package/src/skills/build-temporal-system/SKILL.md +752 -0
- package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
- package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
- package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
- package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
- package/src/skills/implement-use-case/SKILL.md +350 -0
- package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
- package/src/skills/requirements-elicitation/SKILL.md +228 -0
- package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
- package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
- package/src/utils/bounded-context-diagram.js +844 -0
- package/src/utils/config-manager.js +4 -2
- package/src/utils/domain-validator.js +495 -17
- package/src/utils/naming.js +20 -0
- package/src/utils/system-validator.js +169 -11
- package/src/utils/system-yaml-parser.js +318 -0
- package/src/utils/temporal-validator.js +497 -0
- package/src/utils/validator.js +3 -1
- package/src/utils/yaml-to-entity.js +281 -9
- package/templates/aggregate/AggregateRepository.java.ejs +4 -0
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +8 -0
- package/templates/aggregate/AggregateRoot.java.ejs +38 -4
- package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
- package/templates/aggregate/JpaAggregateRoot.java.ejs +4 -4
- package/templates/aggregate/JpaEntity.java.ejs +2 -2
- package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
- package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
- package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
- package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
- package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
- package/templates/base/root/AGENTS.md.ejs +1 -1
- package/templates/crud/DeleteCommandHandler.java.ejs +19 -1
- package/templates/crud/EndpointsController.java.ejs +1 -1
- package/templates/crud/ScaffoldCommand.java.ejs +5 -2
- package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
- package/templates/crud/ScaffoldQuery.java.ejs +5 -2
- package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
- package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
- package/templates/crud/UpdateCommandHandler.java.ejs +53 -2
- package/templates/evaluate/report.html.ejs +1447 -90
- package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
- package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
- package/templates/ports/PortAclMapper.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +7 -22
- package/templates/ports/PortFeignClient.java.ejs +4 -0
- package/templates/ports/PortResponseDto.java.ejs +1 -1
- package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
- package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
- package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
- package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
- package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
- package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
- package/templates/read-model/ReadModelDomain.java.ejs +46 -0
- package/templates/read-model/ReadModelJpa.java.ejs +58 -0
- package/templates/read-model/ReadModelJpaRepository.java.ejs +13 -0
- package/templates/read-model/ReadModelKafkaListener.java.ejs +64 -0
- package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
- package/templates/read-model/ReadModelRepository.java.ejs +42 -0
- package/templates/read-model/ReadModelRepositoryImpl.java.ejs +85 -0
- package/templates/read-model/ReadModelSyncHandler.java.ejs +54 -0
- package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
- package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
- package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
- package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
- package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
- package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
- package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/NestedType.java.ejs +12 -0
- package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
- package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
- package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
- package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
- package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
- package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
- package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
- package/COMMAND_EVALUATION.md +0 -911
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# PROTOTYPE: orders.yaml — versión Temporal (sin Kafka, sin Read Models)
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Cambios vs original:
|
|
5
|
+
# - ELIMINA listeners[] — el workflow ya integra la respuesta del pago
|
|
6
|
+
# - ELIMINA ports[] — la reserva de stock es una Activity del workflow
|
|
7
|
+
# - ELIMINA readModels[] — datos cross-module se obtienen on-demand via
|
|
8
|
+
# Remote Activities de lectura (GetCustomerById)
|
|
9
|
+
# - ELIMINA saga: — la saga se define en system.yaml workflows
|
|
10
|
+
# - events[].notifies referencia workflows definidos en system.yaml
|
|
11
|
+
# =============================================================================
|
|
12
|
+
|
|
13
|
+
aggregates:
|
|
14
|
+
- name: Order
|
|
15
|
+
entities:
|
|
16
|
+
- name: order
|
|
17
|
+
isRoot: true
|
|
18
|
+
tableName: orders
|
|
19
|
+
audit:
|
|
20
|
+
enabled: true
|
|
21
|
+
trackUser: true
|
|
22
|
+
fields:
|
|
23
|
+
- name: id
|
|
24
|
+
type: String
|
|
25
|
+
- name: customerId
|
|
26
|
+
type: String
|
|
27
|
+
reference:
|
|
28
|
+
aggregate: Customer
|
|
29
|
+
module: customers
|
|
30
|
+
validations:
|
|
31
|
+
- type: NotBlank
|
|
32
|
+
message: "Customer ID is required"
|
|
33
|
+
- name: status
|
|
34
|
+
type: OrderStatus
|
|
35
|
+
readOnly: true
|
|
36
|
+
- name: totalAmount
|
|
37
|
+
type: BigDecimal
|
|
38
|
+
readOnly: true
|
|
39
|
+
defaultValue: "0.00"
|
|
40
|
+
- name: itemCount
|
|
41
|
+
type: Integer
|
|
42
|
+
readOnly: true
|
|
43
|
+
defaultValue: 0
|
|
44
|
+
- name: shippingAddress
|
|
45
|
+
type: ShippingAddress
|
|
46
|
+
- name: notes
|
|
47
|
+
type: String
|
|
48
|
+
relationships:
|
|
49
|
+
- type: OneToMany
|
|
50
|
+
target: OrderItem
|
|
51
|
+
mappedBy: order
|
|
52
|
+
cascade: [PERSIST, MERGE, REMOVE]
|
|
53
|
+
fetch: LAZY
|
|
54
|
+
|
|
55
|
+
- name: orderItem
|
|
56
|
+
tableName: order_items
|
|
57
|
+
audit:
|
|
58
|
+
enabled: true
|
|
59
|
+
fields:
|
|
60
|
+
- name: id
|
|
61
|
+
type: String
|
|
62
|
+
- name: productId
|
|
63
|
+
type: String
|
|
64
|
+
reference:
|
|
65
|
+
aggregate: Product
|
|
66
|
+
module: products
|
|
67
|
+
- name: productName
|
|
68
|
+
type: String
|
|
69
|
+
- name: quantity
|
|
70
|
+
type: Integer
|
|
71
|
+
validations:
|
|
72
|
+
- type: Min
|
|
73
|
+
value: 1
|
|
74
|
+
message: "Quantity must be at least 1"
|
|
75
|
+
- name: unitPrice
|
|
76
|
+
type: BigDecimal
|
|
77
|
+
validations:
|
|
78
|
+
- type: Positive
|
|
79
|
+
message: "Unit price must be positive"
|
|
80
|
+
- name: subtotal
|
|
81
|
+
type: BigDecimal
|
|
82
|
+
readOnly: true
|
|
83
|
+
|
|
84
|
+
valueObjects:
|
|
85
|
+
- name: ShippingAddress
|
|
86
|
+
fields:
|
|
87
|
+
- name: street
|
|
88
|
+
type: String
|
|
89
|
+
- name: city
|
|
90
|
+
type: String
|
|
91
|
+
- name: department
|
|
92
|
+
type: String
|
|
93
|
+
- name: zipCode
|
|
94
|
+
type: String
|
|
95
|
+
- name: neighborhood
|
|
96
|
+
type: String
|
|
97
|
+
methods:
|
|
98
|
+
- name: format
|
|
99
|
+
returnType: String
|
|
100
|
+
parameters: []
|
|
101
|
+
body: "return street + \", \" + neighborhood + \", \" + city + \" (\" + department + \") \" + zipCode;"
|
|
102
|
+
|
|
103
|
+
enums:
|
|
104
|
+
- name: OrderStatus
|
|
105
|
+
initialValue: PENDING
|
|
106
|
+
transitions:
|
|
107
|
+
- from: PENDING
|
|
108
|
+
to: CONFIRMED
|
|
109
|
+
method: confirm
|
|
110
|
+
- from: PENDING
|
|
111
|
+
to: CANCELLED
|
|
112
|
+
method: cancel
|
|
113
|
+
- from: PENDING
|
|
114
|
+
to: PAYMENT_FAILED
|
|
115
|
+
method: failPayment
|
|
116
|
+
values: [PENDING, CONFIRMED, CANCELLED, PAYMENT_FAILED]
|
|
117
|
+
|
|
118
|
+
events:
|
|
119
|
+
- name: OrderPlacedEvent
|
|
120
|
+
lifecycle: create
|
|
121
|
+
fields:
|
|
122
|
+
- name: orderId
|
|
123
|
+
type: String
|
|
124
|
+
- name: placedAt
|
|
125
|
+
type: LocalDateTime
|
|
126
|
+
notifies:
|
|
127
|
+
- workflow: PlaceOrderWorkflow # ← La saga completa
|
|
128
|
+
|
|
129
|
+
- name: OrderCancelledEvent
|
|
130
|
+
triggers:
|
|
131
|
+
- cancel
|
|
132
|
+
fields:
|
|
133
|
+
- name: orderId
|
|
134
|
+
type: String
|
|
135
|
+
notifies:
|
|
136
|
+
- workflow: CancelOrderWorkflow
|
|
137
|
+
|
|
138
|
+
# ─── ELIMINADO: listeners[] ──────────────────────────────────────────────────
|
|
139
|
+
# PaymentApprovedEvent y PaymentFailedEvent ya no llegan como eventos Kafka.
|
|
140
|
+
# El PlaceOrderWorkflow espera el resultado de ProcessOrderPayment directamente
|
|
141
|
+
# (paso sync en la saga). Si falla → compensa. Si aprueba → confirma.
|
|
142
|
+
|
|
143
|
+
# ─── ELIMINADO: ports[] ─────────────────────────────────────────────────────
|
|
144
|
+
# ReserveStock y ReleaseStock ya no son Feign calls.
|
|
145
|
+
# Son Activities ejecutadas por el worker de inventory via Temporal.
|
|
146
|
+
|
|
147
|
+
# ─── ELIMINADO: readModels[] ────────────────────────────────────────────────
|
|
148
|
+
# CustomerReadModel y ProductReadModel ELIMINADOS.
|
|
149
|
+
# Datos cross-module se obtienen ON-DEMAND en los workflows:
|
|
150
|
+
# - PlaceOrderWorkflow: GetCustomerById → customers (Remote Activity)
|
|
151
|
+
# Valida existencia, obtiene email/nombre para notificaciones.
|
|
152
|
+
# - CancelOrderWorkflow: GetCustomerById → customers
|
|
153
|
+
# Obtiene email para notificación de cancelación.
|
|
154
|
+
#
|
|
155
|
+
# Productos se validan al crear la orden; no se re-validan en la saga.
|
|
156
|
+
|
|
157
|
+
# ─── ELIMINADO: saga: ───────────────────────────────────────────────────────
|
|
158
|
+
# La saga (PlaceOrderWorkflow) se define en system.yaml, no en el módulo.
|
|
159
|
+
# El CreateOrderCommandHandler persiste la orden y lanza el workflow.
|
|
160
|
+
|
|
161
|
+
# ─── Activities del módulo ───────────────────────────────────────────────────
|
|
162
|
+
# Activities LOCALES — invocadas por workflows cross-module (system.yaml)
|
|
163
|
+
# y workflows single-module (definidos abajo).
|
|
164
|
+
activities:
|
|
165
|
+
- name: GetOrderDetails
|
|
166
|
+
type: light
|
|
167
|
+
description: "Obtiene los detalles completos de una orden (items, totales, IDs relacionados)"
|
|
168
|
+
input:
|
|
169
|
+
- name: orderId
|
|
170
|
+
type: String
|
|
171
|
+
output:
|
|
172
|
+
- name: customerId
|
|
173
|
+
type: String
|
|
174
|
+
- name: items
|
|
175
|
+
type: List<OrderItemDetail>
|
|
176
|
+
- name: totalAmount
|
|
177
|
+
type: BigDecimal
|
|
178
|
+
- name: paymentId
|
|
179
|
+
type: String
|
|
180
|
+
nestedTypes:
|
|
181
|
+
- name: OrderItemDetail
|
|
182
|
+
fields:
|
|
183
|
+
- name: productId
|
|
184
|
+
type: String
|
|
185
|
+
- name: productName
|
|
186
|
+
type: String
|
|
187
|
+
- name: quantity
|
|
188
|
+
type: Integer
|
|
189
|
+
- name: unitPrice
|
|
190
|
+
type: BigDecimal
|
|
191
|
+
- name: subtotal
|
|
192
|
+
type: BigDecimal
|
|
193
|
+
timeout: 5s
|
|
194
|
+
|
|
195
|
+
- name: ConfirmOrder
|
|
196
|
+
type: light
|
|
197
|
+
description: "Confirma la orden tras pago exitoso (invocada por PlaceOrderWorkflow)"
|
|
198
|
+
input:
|
|
199
|
+
- name: orderId
|
|
200
|
+
type: String
|
|
201
|
+
timeout: 5s
|
|
202
|
+
|
|
203
|
+
- name: CancelExpiredOrder
|
|
204
|
+
type: light
|
|
205
|
+
description: "Cancela orden pendiente que no recibió pago a tiempo"
|
|
206
|
+
input:
|
|
207
|
+
- name: orderId
|
|
208
|
+
type: String
|
|
209
|
+
timeout: 5s
|
|
210
|
+
|
|
211
|
+
# ─── Workflows single-module ────────────────────────────────────────────────
|
|
212
|
+
# Flujos que NO cruzan fronteras de módulo → se definen aquí, no en system.yaml.
|
|
213
|
+
workflows:
|
|
214
|
+
- name: ExpireOrderWorkflow
|
|
215
|
+
description: "Cancela la orden si no se recibe pago dentro del timeout"
|
|
216
|
+
trigger:
|
|
217
|
+
on: orderCreated
|
|
218
|
+
taskQueue: ORDER_WORKFLOW_QUEUE
|
|
219
|
+
steps:
|
|
220
|
+
- wait: paymentCompleted
|
|
221
|
+
timeout: 30m
|
|
222
|
+
- activity: CancelExpiredOrder
|
|
223
|
+
timeout: 5s
|
|
224
|
+
|
|
225
|
+
endpoints:
|
|
226
|
+
basePath: /orders
|
|
227
|
+
versions:
|
|
228
|
+
- version: v1
|
|
229
|
+
operations:
|
|
230
|
+
- useCase: CreateOrder
|
|
231
|
+
method: POST
|
|
232
|
+
path: /
|
|
233
|
+
- useCase: GetOrder
|
|
234
|
+
method: GET
|
|
235
|
+
path: /{id}
|
|
236
|
+
- useCase: FindAllOrders
|
|
237
|
+
method: GET
|
|
238
|
+
path: /
|
|
239
|
+
- useCase: CancelOrder
|
|
240
|
+
method: PUT
|
|
241
|
+
path: /{id}/cancel
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# PROTOTYPE: payments.yaml — versión Temporal (sin Kafka)
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Cambios vs original:
|
|
5
|
+
# - ELIMINA listeners[] — ya no consume OrderPlacedEvent por Kafka
|
|
6
|
+
# - ELIMINA events[] con topic: — ya no publica por Kafka
|
|
7
|
+
# - Los eventos internos siguen existiendo para lógica de dominio
|
|
8
|
+
# - ports[] SE MANTIENE — PaymentGateway es un servicio EXTERNO (no Temporal)
|
|
9
|
+
# - ProcessOrderPayment es ahora una Activity invocada por PlaceOrderWorkflow
|
|
10
|
+
# =============================================================================
|
|
11
|
+
|
|
12
|
+
aggregates:
|
|
13
|
+
- name: Payment
|
|
14
|
+
entities:
|
|
15
|
+
- name: payment
|
|
16
|
+
isRoot: true
|
|
17
|
+
tableName: payments
|
|
18
|
+
audit:
|
|
19
|
+
enabled: true
|
|
20
|
+
fields:
|
|
21
|
+
- name: id
|
|
22
|
+
type: String
|
|
23
|
+
- name: orderId
|
|
24
|
+
type: String
|
|
25
|
+
reference:
|
|
26
|
+
aggregate: Order
|
|
27
|
+
module: orders
|
|
28
|
+
validations:
|
|
29
|
+
- type: NotBlank
|
|
30
|
+
message: "Order ID is required"
|
|
31
|
+
- name: customerId
|
|
32
|
+
type: String
|
|
33
|
+
reference:
|
|
34
|
+
aggregate: Customer
|
|
35
|
+
module: customers
|
|
36
|
+
- name: amount
|
|
37
|
+
type: BigDecimal
|
|
38
|
+
validations:
|
|
39
|
+
- type: Positive
|
|
40
|
+
message: "Amount must be positive"
|
|
41
|
+
- type: NotNull
|
|
42
|
+
message: "Amount is required"
|
|
43
|
+
- name: currency
|
|
44
|
+
type: String
|
|
45
|
+
readOnly: true
|
|
46
|
+
defaultValue: "COP"
|
|
47
|
+
- name: status
|
|
48
|
+
type: PaymentStatus
|
|
49
|
+
readOnly: true
|
|
50
|
+
- name: paymentMethod
|
|
51
|
+
type: PaymentMethod
|
|
52
|
+
- name: transactionId
|
|
53
|
+
type: String
|
|
54
|
+
readOnly: true
|
|
55
|
+
hidden: true
|
|
56
|
+
- name: gatewayResponse
|
|
57
|
+
type: String
|
|
58
|
+
readOnly: true
|
|
59
|
+
hidden: true
|
|
60
|
+
|
|
61
|
+
enums:
|
|
62
|
+
- name: PaymentStatus
|
|
63
|
+
initialValue: PENDING
|
|
64
|
+
transitions:
|
|
65
|
+
- from: PENDING
|
|
66
|
+
to: APPROVED
|
|
67
|
+
method: approve
|
|
68
|
+
- from: PENDING
|
|
69
|
+
to: FAILED
|
|
70
|
+
method: fail
|
|
71
|
+
- from: APPROVED
|
|
72
|
+
to: REFUNDED
|
|
73
|
+
method: refund
|
|
74
|
+
values: [PENDING, APPROVED, FAILED, REFUNDED]
|
|
75
|
+
|
|
76
|
+
- name: PaymentMethod
|
|
77
|
+
values:
|
|
78
|
+
- CREDIT_CARD
|
|
79
|
+
- DEBIT_CARD
|
|
80
|
+
- PSE
|
|
81
|
+
- NEQUI
|
|
82
|
+
- CASH_ON_DELIVERY
|
|
83
|
+
|
|
84
|
+
events:
|
|
85
|
+
# Domain Events internos — siguen existiendo para la entidad.
|
|
86
|
+
# PERO ya no generan IntegrationEvent ni MessageBroker.
|
|
87
|
+
# El resultado del pago lo retorna la Activity directamente al workflow.
|
|
88
|
+
- name: PaymentApprovedEvent
|
|
89
|
+
triggers:
|
|
90
|
+
- approve
|
|
91
|
+
fields:
|
|
92
|
+
- name: paymentId
|
|
93
|
+
type: String
|
|
94
|
+
- name: orderId
|
|
95
|
+
type: String
|
|
96
|
+
- name: customerId
|
|
97
|
+
type: String
|
|
98
|
+
- name: amount
|
|
99
|
+
type: BigDecimal
|
|
100
|
+
- name: approvedAt
|
|
101
|
+
type: LocalDateTime
|
|
102
|
+
# ⚠️ SIN notifies: — el resultado viaja como retorno de Activity,
|
|
103
|
+
# no como evento. El workflow ya sabe qué hacer.
|
|
104
|
+
|
|
105
|
+
- name: PaymentFailedEvent
|
|
106
|
+
triggers:
|
|
107
|
+
- fail
|
|
108
|
+
fields:
|
|
109
|
+
- name: paymentId
|
|
110
|
+
type: String
|
|
111
|
+
- name: orderId
|
|
112
|
+
type: String
|
|
113
|
+
- name: customerId
|
|
114
|
+
type: String
|
|
115
|
+
- name: reason
|
|
116
|
+
type: String
|
|
117
|
+
|
|
118
|
+
# ─── Activities que este módulo EXPONE a workflows de otros módulos ──────────
|
|
119
|
+
# Concepto nuevo: en Kafka, payments "escuchaba" OrderPlacedEvent.
|
|
120
|
+
# Con Temporal, payments "ofrece" una Activity que el workflow de orders invoca.
|
|
121
|
+
activities:
|
|
122
|
+
- name: ProcessOrderPayment
|
|
123
|
+
type: heavy # Puede tardar (gateway externo)
|
|
124
|
+
description: "Crea un pago y cobra al gateway externo"
|
|
125
|
+
input:
|
|
126
|
+
- name: orderId
|
|
127
|
+
type: String
|
|
128
|
+
- name: customerId
|
|
129
|
+
type: String
|
|
130
|
+
- name: totalAmount
|
|
131
|
+
type: BigDecimal
|
|
132
|
+
output:
|
|
133
|
+
- name: paymentId
|
|
134
|
+
type: String
|
|
135
|
+
- name: status
|
|
136
|
+
type: String # APPROVED | FAILED
|
|
137
|
+
- name: reason
|
|
138
|
+
type: String # null si APPROVED
|
|
139
|
+
timeout: 30s
|
|
140
|
+
retryPolicy:
|
|
141
|
+
maxAttempts: 3
|
|
142
|
+
initialInterval: 1s
|
|
143
|
+
backoffCoefficient: 2.0
|
|
144
|
+
|
|
145
|
+
- name: RefundPayment
|
|
146
|
+
type: heavy
|
|
147
|
+
description: "Reembolsa un pago aprobado (compensación de saga)"
|
|
148
|
+
input:
|
|
149
|
+
- name: paymentId
|
|
150
|
+
type: String
|
|
151
|
+
output:
|
|
152
|
+
- name: refundId
|
|
153
|
+
type: String
|
|
154
|
+
- name: status
|
|
155
|
+
type: String
|
|
156
|
+
|
|
157
|
+
# Activities INTERNAS — solo usadas por workflows single-module de payments
|
|
158
|
+
- name: RetryCharge
|
|
159
|
+
type: heavy
|
|
160
|
+
description: "Reintenta cobro al gateway externo"
|
|
161
|
+
input:
|
|
162
|
+
- name: paymentId
|
|
163
|
+
type: String
|
|
164
|
+
output:
|
|
165
|
+
- name: status
|
|
166
|
+
type: String
|
|
167
|
+
- name: reason
|
|
168
|
+
type: String
|
|
169
|
+
timeout: 30s
|
|
170
|
+
|
|
171
|
+
- name: MarkPaymentFailed
|
|
172
|
+
type: light
|
|
173
|
+
description: "Marca el pago como fallido definitivamente tras agotar reintentos"
|
|
174
|
+
input:
|
|
175
|
+
- name: paymentId
|
|
176
|
+
type: String
|
|
177
|
+
timeout: 5s
|
|
178
|
+
|
|
179
|
+
# ─── Workflows single-module ────────────────────────────────────────────────
|
|
180
|
+
# Flujos internos que NO cruzan fronteras de módulo.
|
|
181
|
+
workflows:
|
|
182
|
+
- name: RetryChargeWorkflow
|
|
183
|
+
description: "Reintenta cobro fallido con backoff exponencial"
|
|
184
|
+
trigger:
|
|
185
|
+
on: chargeFailed
|
|
186
|
+
taskQueue: PAYMENT_WORKFLOW_QUEUE
|
|
187
|
+
steps:
|
|
188
|
+
- activity: RetryCharge
|
|
189
|
+
retryPolicy:
|
|
190
|
+
maxAttempts: 3
|
|
191
|
+
initialInterval: 2s
|
|
192
|
+
backoffCoefficient: 2.0
|
|
193
|
+
- activity: MarkPaymentFailed
|
|
194
|
+
timeout: 5s
|
|
195
|
+
|
|
196
|
+
# ─── ELIMINADOS: listeners[] ─────────────────────────────────────────────────
|
|
197
|
+
# OrderPlacedEvent ya no se consume. ProcessOrderPayment es una Activity.
|
|
198
|
+
|
|
199
|
+
# ─── ports[] SE MANTIENE para servicios EXTERNOS (no-Temporal) ───────────────
|
|
200
|
+
# PaymentGateway es un proveedor externo — no corre workers Temporal.
|
|
201
|
+
# Para servicios externos, HTTP sigue siendo necesario.
|
|
202
|
+
ports:
|
|
203
|
+
- name: processCharge
|
|
204
|
+
service: PaymentGatewayService
|
|
205
|
+
target: payment-gateway
|
|
206
|
+
baseUrl: https://api.payments.example.com
|
|
207
|
+
http: POST /charges
|
|
208
|
+
body:
|
|
209
|
+
- name: amount
|
|
210
|
+
type: BigDecimal
|
|
211
|
+
- name: currency
|
|
212
|
+
type: String
|
|
213
|
+
- name: paymentMethod
|
|
214
|
+
type: String
|
|
215
|
+
- name: description
|
|
216
|
+
type: String
|
|
217
|
+
fields:
|
|
218
|
+
- name: transactionId
|
|
219
|
+
type: String
|
|
220
|
+
- name: status
|
|
221
|
+
type: String
|
|
222
|
+
- name: message
|
|
223
|
+
type: String
|
|
224
|
+
|
|
225
|
+
- name: processRefund
|
|
226
|
+
service: PaymentGatewayService
|
|
227
|
+
target: payment-gateway
|
|
228
|
+
http: POST /refunds
|
|
229
|
+
body:
|
|
230
|
+
- name: transactionId
|
|
231
|
+
type: String
|
|
232
|
+
- name: amount
|
|
233
|
+
type: BigDecimal
|
|
234
|
+
fields:
|
|
235
|
+
- name: refundId
|
|
236
|
+
type: String
|
|
237
|
+
- name: status
|
|
238
|
+
type: String
|
|
239
|
+
|
|
240
|
+
endpoints:
|
|
241
|
+
basePath: /payments
|
|
242
|
+
versions:
|
|
243
|
+
- version: v1
|
|
244
|
+
operations:
|
|
245
|
+
- useCase: CreatePayment
|
|
246
|
+
method: POST
|
|
247
|
+
path: /
|
|
248
|
+
- useCase: GetPayment
|
|
249
|
+
method: GET
|
|
250
|
+
path: /{id}
|
|
251
|
+
- useCase: FindAllPayments
|
|
252
|
+
method: GET
|
|
253
|
+
path: /
|
|
254
|
+
- useCase: RefundPayment
|
|
255
|
+
method: POST
|
|
256
|
+
path: /{id}/refund
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# PROTOTYPE: products.yaml — versión Temporal (sin Kafka)
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Cambios vs original:
|
|
5
|
+
# - events[] MANTIENE los Domain Events internos (raise() sigue funcionando)
|
|
6
|
+
# - ELIMINA topic: — ya no hay topics Kafka
|
|
7
|
+
# - AGREGA notifies: — lista de activities que el workflow debe invocar
|
|
8
|
+
# - endpoints, aggregates, enums → SIN CAMBIOS
|
|
9
|
+
# =============================================================================
|
|
10
|
+
|
|
11
|
+
aggregates:
|
|
12
|
+
- name: Product
|
|
13
|
+
entities:
|
|
14
|
+
- name: product
|
|
15
|
+
isRoot: true
|
|
16
|
+
tableName: products
|
|
17
|
+
hasSoftDelete: true
|
|
18
|
+
audit:
|
|
19
|
+
enabled: true
|
|
20
|
+
fields:
|
|
21
|
+
- name: id
|
|
22
|
+
type: String
|
|
23
|
+
- name: name
|
|
24
|
+
type: String
|
|
25
|
+
validations:
|
|
26
|
+
- type: NotBlank
|
|
27
|
+
message: "Product name is required"
|
|
28
|
+
- name: description
|
|
29
|
+
type: String
|
|
30
|
+
- name: price
|
|
31
|
+
type: BigDecimal
|
|
32
|
+
validations:
|
|
33
|
+
- type: Positive
|
|
34
|
+
message: "Price must be positive"
|
|
35
|
+
- type: NotNull
|
|
36
|
+
message: "Price is required"
|
|
37
|
+
- name: category
|
|
38
|
+
type: ProductCategory
|
|
39
|
+
- name: imageUrl
|
|
40
|
+
type: String
|
|
41
|
+
- name: sku
|
|
42
|
+
type: String
|
|
43
|
+
validations:
|
|
44
|
+
- type: NotBlank
|
|
45
|
+
message: "SKU is required"
|
|
46
|
+
- name: active
|
|
47
|
+
type: Boolean
|
|
48
|
+
readOnly: true
|
|
49
|
+
defaultValue: true
|
|
50
|
+
|
|
51
|
+
enums:
|
|
52
|
+
- name: ProductCategory
|
|
53
|
+
values:
|
|
54
|
+
- GRAINS
|
|
55
|
+
- DAIRY
|
|
56
|
+
- MEAT
|
|
57
|
+
- FRUITS
|
|
58
|
+
- VEGETABLES
|
|
59
|
+
- BEVERAGES
|
|
60
|
+
- CLEANING
|
|
61
|
+
- PERSONAL_CARE
|
|
62
|
+
- SNACKS
|
|
63
|
+
- OTHER
|
|
64
|
+
|
|
65
|
+
events:
|
|
66
|
+
# Domain Events internos se mantienen (raise() en la entidad).
|
|
67
|
+
# La diferencia: ya no generan IntegrationEvent ni MessageBroker.
|
|
68
|
+
# En su lugar, un WorkflowService los intercepta y lanza el workflow.
|
|
69
|
+
- name: ProductCreatedEvent
|
|
70
|
+
lifecycle: create
|
|
71
|
+
fields:
|
|
72
|
+
- name: productId
|
|
73
|
+
type: String
|
|
74
|
+
- name: name
|
|
75
|
+
type: String
|
|
76
|
+
- name: price
|
|
77
|
+
type: BigDecimal
|
|
78
|
+
- name: category
|
|
79
|
+
type: String
|
|
80
|
+
notifies:
|
|
81
|
+
- workflow: ProductCreatedWorkflow # → InitializeStock en inventory
|
|
82
|
+
|
|
83
|
+
- name: ProductUpdatedEvent
|
|
84
|
+
lifecycle: update
|
|
85
|
+
fields:
|
|
86
|
+
- name: productId
|
|
87
|
+
type: String
|
|
88
|
+
- name: name
|
|
89
|
+
type: String
|
|
90
|
+
- name: price
|
|
91
|
+
type: BigDecimal
|
|
92
|
+
- name: category
|
|
93
|
+
type: String
|
|
94
|
+
# SIN notifies → Domain Event interno.
|
|
95
|
+
# Sin read models, no hay consumers cross-module para updates.
|
|
96
|
+
# Los workflows que necesitan datos de producto los obtienen
|
|
97
|
+
# on-demand via GetProductById / GetProductsByIds.
|
|
98
|
+
|
|
99
|
+
- name: ProductDeactivatedEvent
|
|
100
|
+
lifecycle: softDelete
|
|
101
|
+
fields:
|
|
102
|
+
- name: productId
|
|
103
|
+
type: String
|
|
104
|
+
# SIN notifies → Domain Event interno.
|
|
105
|
+
# Mismo razonamiento: sin read models, no hay consumers.
|
|
106
|
+
|
|
107
|
+
# ─── Activities que este módulo EXPONE ───────────────────────────────────────
|
|
108
|
+
# Activities de lectura: permiten a workflows de otros módulos obtener datos
|
|
109
|
+
# de producto on-demand (Remote Activity), eliminando la necesidad de read models.
|
|
110
|
+
activities:
|
|
111
|
+
- name: GetProductById
|
|
112
|
+
type: light
|
|
113
|
+
description: "Obtiene un producto por su ID"
|
|
114
|
+
input:
|
|
115
|
+
- name: productId
|
|
116
|
+
type: String
|
|
117
|
+
output:
|
|
118
|
+
- name: id
|
|
119
|
+
type: String
|
|
120
|
+
- name: name
|
|
121
|
+
type: String
|
|
122
|
+
- name: price
|
|
123
|
+
type: BigDecimal
|
|
124
|
+
- name: category
|
|
125
|
+
type: String
|
|
126
|
+
- name: sku
|
|
127
|
+
type: String
|
|
128
|
+
timeout: 5s
|
|
129
|
+
|
|
130
|
+
- name: GetProductsByIds
|
|
131
|
+
type: light
|
|
132
|
+
description: "Obtiene múltiples productos por sus IDs (batch)"
|
|
133
|
+
input:
|
|
134
|
+
- name: productIds
|
|
135
|
+
type: List<String>
|
|
136
|
+
output:
|
|
137
|
+
- name: products
|
|
138
|
+
type: List<String> # List<{id, name, price, category, sku}>
|
|
139
|
+
timeout: 10s
|
|
140
|
+
|
|
141
|
+
endpoints:
|
|
142
|
+
basePath: /products
|
|
143
|
+
versions:
|
|
144
|
+
- version: v1
|
|
145
|
+
operations:
|
|
146
|
+
- useCase: GetProduct
|
|
147
|
+
method: GET
|
|
148
|
+
path: /{id}
|
|
149
|
+
- useCase: FindAllProducts
|
|
150
|
+
method: GET
|
|
151
|
+
path: /
|
|
152
|
+
- useCase: CreateProduct
|
|
153
|
+
method: POST
|
|
154
|
+
path: /
|
|
155
|
+
- useCase: UpdateProduct
|
|
156
|
+
method: PUT
|
|
157
|
+
path: /{id}
|
|
158
|
+
- useCase: DeleteProduct
|
|
159
|
+
method: DELETE
|
|
160
|
+
path: /{id}
|
|
161
|
+
|
|
162
|
+
# ─── ELIMINADO: No hay listeners (products no consume eventos) ───────────────
|
|
163
|
+
# ─── ELIMINADO: No hay ports (products no llama a otros servicios) ────────────
|
|
164
|
+
# ─── ELIMINADO: No hay readModels (products no proyecta datos ajenos) ─────────
|
|
165
|
+
# ─── DESACOPLAMIENTO: products ya no necesita saber quién consume sus datos ──
|
|
166
|
+
# ProductUpdatedEvent y ProductDeactivatedEvent son Domain Events internos.
|
|
167
|
+
# Solo ProductCreatedEvent notifica ProductCreatedWorkflow (→ InitializeStock)
|
|
168
|
+
# porque es un efecto de negocio real, no sincronización de datos.
|