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,350 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implement-use-case
|
|
3
|
+
description: "Implementar casos de uso (use cases) en proyectos generados por eva4j siguiendo arquitectura hexagonal, DDD y código limpio. USAR SIEMPRE cuando el usuario pida: implementar un handler con UnsupportedOperationException, resolver un TODO en un QueryHandler o CommandHandler, implementar lógica de negocio en un use case, agregar filtros/búsquedas personalizadas al repositorio, implementar actividades de Temporal, o resolver cualquier caso de uso descrito en los archivos .md del directorio system/. También aplica cuando el usuario menciona: 'implementar', 'resolver', 'completar el handler', 'quitar el UnsupportedOperationException', 'agregar lógica', 'filtrar por', 'buscar por', o cualquier referencia a casos de uso pendientes."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Implement Use Case
|
|
7
|
+
|
|
8
|
+
Eres un desarrollador senior experto en DDD, arquitectura hexagonal y Spring Boot. Tu misión es implementar casos de uso en proyectos generados por **eva4j** de forma que:
|
|
9
|
+
|
|
10
|
+
- Respeten estrictamente la arquitectura de capas (domain → application → infrastructure)
|
|
11
|
+
- Sigan los principios de código limpio y DDD
|
|
12
|
+
- Sean consistentes con los patrones ya establecidos por el generador
|
|
13
|
+
- No rompan invariantes ni violen las convenciones del proyecto
|
|
14
|
+
|
|
15
|
+
> **Regla de oro:** El dominio NUNCA conoce la infraestructura. La aplicación orquesta. La infraestructura adapta.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Descubrimiento del contexto
|
|
20
|
+
|
|
21
|
+
Antes de escribir una sola línea, recopila contexto del proyecto. El orden importa:
|
|
22
|
+
|
|
23
|
+
### Paso 1 — Identificar el bounded context
|
|
24
|
+
|
|
25
|
+
1. Lee el directorio `system/` del proyecto para localizar el archivo `{module}.md` correspondiente al bounded context
|
|
26
|
+
2. Lee el `.md` del módulo — contiene: rol del módulo, invariantes, máquina de estados, diagramas de interacción, secuencia, y la **descripción detallada de cada caso de uso** (tipo, precondiciones, postcondiciones, validaciones, eventos emitidos)
|
|
27
|
+
3. Si existe `system/system.md`, léelo para entender las integraciones entre módulos
|
|
28
|
+
|
|
29
|
+
### Paso 2 — Leer el código generado
|
|
30
|
+
|
|
31
|
+
Lee los archivos del módulo en este orden (todos conviven bajo `src/main/java/{package}/{module}/`):
|
|
32
|
+
|
|
33
|
+
1. **`domain.yaml`** — la fuente de verdad del modelo: entidades, campos, relaciones, enums con transiciones, auditoría, eventos, soft delete, readOnly, hidden
|
|
34
|
+
2. **Entidad de dominio** (`domain/models/entities/{Entity}.java`) — constructores, métodos de negocio existentes, campos
|
|
35
|
+
3. **Repositorio de dominio** (`domain/repositories/{Entity}Repository.java`) — métodos ya definidos en la interfaz
|
|
36
|
+
4. **Handler scaffold** (`application/usecases/{UseCase}Handler.java`) — el archivo que contiene el `UnsupportedOperationException` que vas a reemplazar
|
|
37
|
+
5. **Command o Query** (`application/commands/` o `application/queries/`) — el record con los campos de entrada
|
|
38
|
+
6. **DTO de respuesta** (`application/dtos/{Entity}ResponseDto.java`) — campos que devuelve la API
|
|
39
|
+
7. **Application Mapper** (`application/mappers/{Entity}ApplicationMapper.java`) — métodos `toDto()` y `fromCommand()` existentes
|
|
40
|
+
8. **Aggregate Mapper** (`infrastructure/database/mappers/{Entity}Mapper.java`) — mapeo domain ↔ JPA
|
|
41
|
+
9. **JPA Repository** (`infrastructure/database/repositories/{Entity}JpaRepository.java`) — métodos Spring Data ya definidos
|
|
42
|
+
10. **Repository Impl** (`infrastructure/database/repositories/{Entity}RepositoryImpl.java`) — implementación del puerto
|
|
43
|
+
|
|
44
|
+
### Paso 2b — Si hay Temporal workflows
|
|
45
|
+
|
|
46
|
+
Detecta si el módulo tiene archivos `*WorkFlowImpl.java` en `application/usecases/`. Si existen, el módulo **orquesta** workflows que invocan activities distribuidas en múltiples bounded contexts.
|
|
47
|
+
|
|
48
|
+
**Lee en este orden:**
|
|
49
|
+
|
|
50
|
+
1. **`*WorkFlowImpl.java`** — identifica todas las activity stubs: nombre de la activity, su task queue (indica el módulo), input/output esperado, y si tiene compensación (Saga). Este archivo es la **spec funcional implícita** de cada activity: qué datos le llegan y qué retorna.
|
|
51
|
+
2. **Contratos cross-module** — Para cada activity de otro módulo, lee el contrato en `shared/domain/contracts/{targetModule}/`:
|
|
52
|
+
- `{Activity}Activity.java` — interfaz `@ActivityInterface` (firma del método)
|
|
53
|
+
- `{Activity}Input.java` — record con los campos de entrada
|
|
54
|
+
- `{Activity}Output.java` — record con los campos de retorno (si existe)
|
|
55
|
+
3. **Contratos locales** — Para activities del propio módulo, lee:
|
|
56
|
+
- `{module}/application/ports/{Activity}Activity.java` — interfaz
|
|
57
|
+
- `{module}/application/dtos/temporal/{Activity}Input.java` / `{Activity}Output.java`
|
|
58
|
+
4. **Implementaciones** — Lee cada `{Activity}ActivityImpl.java` en `{targetModule}/infrastructure/adapters/activities/` para saber si tiene lógica real o está pendiente (`UnsupportedOperationException` o `//todo`)
|
|
59
|
+
5. **`system/{module}.md`** — busca la sección del workflow que describe cada paso, precondiciones, y compensaciones
|
|
60
|
+
|
|
61
|
+
**Mapa de activities a construir:**
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
WorkFlowImpl → activity stub → task queue → módulo destino
|
|
65
|
+
↓
|
|
66
|
+
{module}/infrastructure/adapters/activities/{Activity}ActivityImpl.java
|
|
67
|
+
↓
|
|
68
|
+
¿Tiene UnsupportedOperationException o //todo? → PENDIENTE
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> **Regla cross-module:** Para implementar activity `X` que pertenece al módulo `Y`, necesitas leer también la entidad de dominio, repositorio y enums del módulo `Y` — la activity accede **solo** a la BD de su propio módulo.
|
|
72
|
+
|
|
73
|
+
### Paso 3 — Entender el caso de uso
|
|
74
|
+
|
|
75
|
+
Del archivo `.md` del módulo, extrae para el caso de uso concreto:
|
|
76
|
+
- **Tipo:** HTTP (endpoint REST) o Activity (invocado por Temporal)
|
|
77
|
+
- **Qué hace:** descripción funcional
|
|
78
|
+
- **Precondiciones:** qué debe cumplirse antes de ejecutar
|
|
79
|
+
- **Postcondiciones:** estado del sistema después de la ejecución
|
|
80
|
+
- **Invariantes verificados:** IDs de los invariantes del módulo que este caso de uso protege
|
|
81
|
+
- **Validaciones y errores:** excepciones específicas y códigos HTTP
|
|
82
|
+
- **Eventos emitidos:** domain events que se publican
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Anatomía de un Use Case handler
|
|
87
|
+
|
|
88
|
+
Todo handler sigue esta estructura:
|
|
89
|
+
|
|
90
|
+
```java
|
|
91
|
+
@ApplicationComponent
|
|
92
|
+
public class {UseCase}Handler implements CommandHandler<{Command}> | QueryHandler<{Query}, {Response}> {
|
|
93
|
+
|
|
94
|
+
// 1. Dependencias — solo repositorios de dominio y mappers
|
|
95
|
+
private final {Entity}Repository repository;
|
|
96
|
+
private final {Entity}ApplicationMapper mapper; // solo si necesita mapear DTOs
|
|
97
|
+
|
|
98
|
+
// 2. Constructor — inyección por constructor
|
|
99
|
+
public {UseCase}Handler({Entity}Repository repository, ...) { ... }
|
|
100
|
+
|
|
101
|
+
// 3. handle() — la lógica del caso de uso
|
|
102
|
+
@Override
|
|
103
|
+
@Transactional // comandos: @Transactional; queries: @Transactional(readOnly = true)
|
|
104
|
+
@LogExceptions
|
|
105
|
+
public {ReturnType} handle({CommandOrQuery} input) {
|
|
106
|
+
// Lógica del caso de uso
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Patrones de implementación por tipo de caso de uso
|
|
114
|
+
|
|
115
|
+
Lee el archivo `references/use-case-patterns.md` para los patrones detallados con código de ejemplo para cada tipo de caso de uso:
|
|
116
|
+
|
|
117
|
+
- **Query por ID** (GetEntity) — busca, mapea, retorna
|
|
118
|
+
- **Query con paginación** (FindAll) — Sort, Pageable, Page, PagedResponse
|
|
119
|
+
- **Query con filtros** (FindBy...) — métodos custom de repositorio
|
|
120
|
+
- **Command de estado** (Confirm, Cancel, Activate) — transición de estado vía método de negocio
|
|
121
|
+
- **Command de validación** (Create con unicidad) — buscar duplicados antes de crear
|
|
122
|
+
- **Command de actualización** (Update con merge) — PATCH semántica sin setters
|
|
123
|
+
- **Command con soft delete** (Delete) — `entity.softDelete()` + save
|
|
124
|
+
- **Activity de Temporal** — lógica similar pero invocada por workflow, no por REST
|
|
125
|
+
- **Command que emite eventos** — `raise()` dentro del método de negocio
|
|
126
|
+
- **Activity cross-module** — cómo leer el WorkFlowImpl como spec + implementar en módulo destino
|
|
127
|
+
- **Activity de compensación** — revertir operación previa (ReleaseStock, RefundPayment)
|
|
128
|
+
- **Activity void** — buscar + transicionar estado + persistir, sin retorno
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Reglas inviolables
|
|
133
|
+
|
|
134
|
+
### Capa de dominio
|
|
135
|
+
|
|
136
|
+
1. **NUNCA** agregar setters a entidades de dominio — usar métodos de negocio con nombre descriptivo
|
|
137
|
+
2. **NUNCA** agregar constructor vacío a entidades de dominio
|
|
138
|
+
3. **NUNCA** importar clases de Spring, JPA o infraestructura en el dominio
|
|
139
|
+
4. **NUNCA** usar anotaciones JSR-303 en entidades de dominio — las validaciones van en Commands/Queries
|
|
140
|
+
5. Las transiciones de estado se hacen vía `this.status.transitionTo(TargetStatus)` — el enum valida la transición
|
|
141
|
+
|
|
142
|
+
### Capa de aplicación
|
|
143
|
+
|
|
144
|
+
6. **NUNCA** inyectar `JpaRepository` directamente — usar la interfaz de dominio `{Entity}Repository`
|
|
145
|
+
7. **NUNCA** devolver entidades de dominio desde un handler — siempre mapear a DTO
|
|
146
|
+
8. **NUNCA** mapear campos de auditoría (`createdBy`, `updatedBy`) en DTOs de respuesta
|
|
147
|
+
9. **NUNCA** mapear campos `hidden: true` en DTOs de respuesta
|
|
148
|
+
10. **NUNCA** incluir campos `readOnly: true` en constructores de creación ni en `CreateCommand`
|
|
149
|
+
11. Usar `@Transactional` para commands y `@Transactional(readOnly = true)` para queries
|
|
150
|
+
|
|
151
|
+
### Capa de infraestructura
|
|
152
|
+
|
|
153
|
+
12. Nuevos métodos de repositorio se agregan **en 3 lugares**: interfaz de dominio → JPA Repository → RepositoryImpl
|
|
154
|
+
13. **NUNCA** mapear campos de auditoría heredados en el builder JPA — JPA Auditing los gestiona
|
|
155
|
+
14. Cuando hay soft delete, **NUNCA** usar `deleteById()` — usar `softDelete()` + `save()`
|
|
156
|
+
|
|
157
|
+
### Excepciones
|
|
158
|
+
|
|
159
|
+
15. Usar `NotFoundException` para recursos no encontrados → 404
|
|
160
|
+
16. Usar `BusinessException` para violaciones de reglas de negocio → 422
|
|
161
|
+
17. Usar `InvalidStateTransitionException` para transiciones inválidas → 409
|
|
162
|
+
18. Crear excepciones custom (e.g., `DuplicateSkuException`) solo cuando el `.md` las define explícitamente
|
|
163
|
+
|
|
164
|
+
### Temporal Activities
|
|
165
|
+
|
|
166
|
+
19. Cada activity accede **SOLO** a la base de datos de su propio módulo — nunca inyectar repositorios de otro bounded context
|
|
167
|
+
20. **NUNCA** agregar `@Transactional` en activities — Temporal gestiona reintentos y compensación
|
|
168
|
+
21. La implementación vive en `{module}/infrastructure/adapters/activities/` — implementa la interfaz `@ActivityInterface` + el marker `{Module}LightActivity` o `{Module}HeavyActivity`
|
|
169
|
+
22. Activities cross-module: el contrato (interfaz + Input + Output) está en `shared/domain/contracts/{module}/` — **no lo modifiques**, solo implementa el `ActivityImpl`
|
|
170
|
+
23. Activities locales: el contrato está en `{module}/application/ports/` + `{module}/application/dtos/temporal/`
|
|
171
|
+
24. Para activities de compensación (rollback): la lógica es el **inverso exacto** de la activity principal — si `ReserveStock` decrementa, `ReleaseStock` incrementa
|
|
172
|
+
25. El `WorkFlowImpl` es la **spec funcional implícita**: los datos que pasa como Input son los que la activity recibe, y el Output que extrae es lo que debe retornar
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Flujo de implementación paso a paso
|
|
177
|
+
|
|
178
|
+
### Para una Query custom (ej: FindProductsByCategory)
|
|
179
|
+
|
|
180
|
+
1. **Leer** el Query record — identificar los parámetros de entrada
|
|
181
|
+
2. **Agregar** el método al repositorio de dominio:
|
|
182
|
+
```java
|
|
183
|
+
// En {Entity}Repository.java
|
|
184
|
+
Page<Product> findByCategoryId(String categoryId, Pageable pageable);
|
|
185
|
+
```
|
|
186
|
+
3. **Agregar** el método al JPA Repository:
|
|
187
|
+
```java
|
|
188
|
+
// En {Entity}JpaRepository.java
|
|
189
|
+
Page<ProductJpa> findByCategoryId(String categoryId, Pageable pageable);
|
|
190
|
+
```
|
|
191
|
+
4. **Agregar** la implementación en RepositoryImpl:
|
|
192
|
+
```java
|
|
193
|
+
// En {Entity}RepositoryImpl.java
|
|
194
|
+
@Override
|
|
195
|
+
public Page<Product> findByCategoryId(String categoryId, Pageable pageable) {
|
|
196
|
+
return jpaRepository.findByCategoryId(categoryId, pageable).map(mapper::toDomain);
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
5. **Implementar** el handler:
|
|
200
|
+
```java
|
|
201
|
+
@Override
|
|
202
|
+
@Transactional(readOnly = true)
|
|
203
|
+
@LogExceptions
|
|
204
|
+
public PagedResponse<ProductResponseDto> handle(FindProductsByCategoryQuery query) {
|
|
205
|
+
Pageable pageable = PageRequest.of(query.page(), query.size(),
|
|
206
|
+
Sort.by(Sort.Direction.fromString(query.sortDirection()), query.sortBy()));
|
|
207
|
+
|
|
208
|
+
Page<Product> page = repository.findByCategoryId(query.categoryId(), pageable);
|
|
209
|
+
List<ProductResponseDto> content = page.getContent().stream()
|
|
210
|
+
.map(mapper::toDto)
|
|
211
|
+
.toList();
|
|
212
|
+
|
|
213
|
+
return PagedResponse.of(content, page.getNumber(), page.getSize(), page.getTotalElements());
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Para un Command de transición de estado (ej: CancelOrder)
|
|
218
|
+
|
|
219
|
+
1. **Leer** el Command record — identificar los parámetros
|
|
220
|
+
2. **Verificar** que el método de negocio existe en la entidad de dominio (ej: `cancel()`)
|
|
221
|
+
3. **Verificar** que la transición existe en el enum de estado
|
|
222
|
+
4. **Implementar** el handler:
|
|
223
|
+
```java
|
|
224
|
+
@Override
|
|
225
|
+
@Transactional
|
|
226
|
+
@LogExceptions
|
|
227
|
+
public void handle(CancelOrderCommand command) {
|
|
228
|
+
Order entity = repository.findById(command.id())
|
|
229
|
+
.orElseThrow(() -> new NotFoundException("Order not found with id: " + command.id()));
|
|
230
|
+
|
|
231
|
+
entity.cancel(); // Método de negocio — valida la transición internamente
|
|
232
|
+
repository.save(entity);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Para un Command con validación de unicidad (ej: CreateProduct con SKU único)
|
|
237
|
+
|
|
238
|
+
1. **Agregar** método de búsqueda al repositorio (3 archivos):
|
|
239
|
+
```java
|
|
240
|
+
Optional<Product> findBySku(String sku);
|
|
241
|
+
```
|
|
242
|
+
2. **Implementar** el handler con verificación previa:
|
|
243
|
+
```java
|
|
244
|
+
@Override
|
|
245
|
+
@Transactional
|
|
246
|
+
@LogExceptions
|
|
247
|
+
public void handle(CreateProductCommand command) {
|
|
248
|
+
repository.findBySku(command.sku()).ifPresent(existing -> {
|
|
249
|
+
throw new DuplicateSkuException("Product with SKU '" + command.sku() + "' already exists");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
Product entity = new Product(
|
|
253
|
+
command.name(), command.description(), command.sku(),
|
|
254
|
+
command.categoryId(), command.price(), command.unit(), command.imageUrl()
|
|
255
|
+
);
|
|
256
|
+
repository.save(entity);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Para una Temporal Activity (ej: CreateOrderFromCart en módulo orders)
|
|
261
|
+
|
|
262
|
+
1. **Leer** el contrato de la activity — interfaz + Input + Output en `shared/domain/contracts/{module}/` o `{module}/application/ports/`
|
|
263
|
+
2. **Leer** el `WorkFlowImpl` que la invoca — entender qué datos le pasa como input y qué espera como output
|
|
264
|
+
3. **Leer** la entidad de dominio del módulo **donde vive la implementación** (no del módulo orquestador)
|
|
265
|
+
4. **Leer** el repositorio de dominio del módulo destino
|
|
266
|
+
5. **Implementar** la lógica:
|
|
267
|
+
```java
|
|
268
|
+
@Component
|
|
269
|
+
@RequiredArgsConstructor
|
|
270
|
+
public class CreateOrderFromCartActivityImpl
|
|
271
|
+
implements CreateOrderFromCartActivity, OrdersLightActivity {
|
|
272
|
+
|
|
273
|
+
private final OrderRepository repository;
|
|
274
|
+
|
|
275
|
+
@Override
|
|
276
|
+
public CreateOrderFromCartOutput execute(CreateOrderFromCartInput input) {
|
|
277
|
+
// Construir entidad de dominio desde el input del workflow
|
|
278
|
+
Order order = new Order(
|
|
279
|
+
input.customerId(), input.totalAmount(),
|
|
280
|
+
new ShippingAddress(input.street(), input.city(), ...)
|
|
281
|
+
);
|
|
282
|
+
Order saved = repository.save(order);
|
|
283
|
+
return new CreateOrderFromCartOutput(saved.getId());
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Diferencias clave con handlers CQRS:**
|
|
289
|
+
- Anotado con `@Component` + `@RequiredArgsConstructor` (no `@ApplicationComponent`)
|
|
290
|
+
- Implementa dos interfaces: el contrato `{Activity}Activity` + el marker `{Module}Light/HeavyActivity`
|
|
291
|
+
- No usa `@Transactional` ni `@LogExceptions`
|
|
292
|
+
- Input/Output son records del contrato Temporal, no Commands/Queries
|
|
293
|
+
- Puede lanzar excepciones que Temporal captura para compensación (Saga)
|
|
294
|
+
|
|
295
|
+
### Para una Activity de compensación (ej: ReleaseStock — reverso de ReserveStock)
|
|
296
|
+
|
|
297
|
+
1. **Leer** la activity principal que compensa (ej: `ReserveStockActivityImpl`)
|
|
298
|
+
2. **Implementar** la lógica inversa:
|
|
299
|
+
```java
|
|
300
|
+
@Component
|
|
301
|
+
@RequiredArgsConstructor
|
|
302
|
+
public class ReleaseStockActivityImpl
|
|
303
|
+
implements ReleaseStockActivity, InventoryLightActivity {
|
|
304
|
+
|
|
305
|
+
private final ProductRepository repository;
|
|
306
|
+
|
|
307
|
+
@Override
|
|
308
|
+
public void execute(ReleaseStockInput input) {
|
|
309
|
+
// Inverso de ReserveStock: incrementar stock de cada item
|
|
310
|
+
for (StockReservationItem item : input.items()) {
|
|
311
|
+
Product product = repository.findById(item.productId())
|
|
312
|
+
.orElseThrow(() -> new NotFoundException("Product not found: " + item.productId()));
|
|
313
|
+
product.releaseStock(item.quantity());
|
|
314
|
+
repository.save(product);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
3. **Verificar** que el método de negocio inverso existe en la entidad de dominio (ej: `releaseStock()` si `reserveStock()` existe)
|
|
320
|
+
|
|
321
|
+
### Para implementar activities de múltiples módulos (workflow cross-module)
|
|
322
|
+
|
|
323
|
+
Cuando un workflow orquesta activities de N módulos, la implementación pendiente puede estar dispersa en cualquiera de ellos. Sigue este flujo:
|
|
324
|
+
|
|
325
|
+
1. **Lee** el `WorkFlowImpl` completo — extrae la lista de todas las activities y su task queue
|
|
326
|
+
2. **Agrupa** por módulo destino (la task queue indica el módulo: `ORDERS_LIGHT_TASK_QUEUE` → módulo `orders`)
|
|
327
|
+
3. **Para cada módulo**, lee su entidad de dominio, repositorio, y enums antes de implementar las activities de ese módulo
|
|
328
|
+
4. **Implementa** las activities de un módulo antes de pasar al siguiente — así el contexto del dominio está fresco
|
|
329
|
+
5. **Nunca** asumas los datos del input — verifica leyendo el record `{Activity}Input.java`
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Checklist antes de entregar
|
|
334
|
+
|
|
335
|
+
Después de implementar, verifica:
|
|
336
|
+
|
|
337
|
+
- [ ] El handler compila sin errores
|
|
338
|
+
- [ ] No queda `UnsupportedOperationException` ni `TODO` sin resolver
|
|
339
|
+
- [ ] Los imports son correctos (no hay imports de capas incorrectas)
|
|
340
|
+
- [ ] Si agregaste métodos al repositorio: están en los 3 archivos (interfaz, JPA, impl)
|
|
341
|
+
- [ ] Las excepciones usadas corresponden a las del `.md` del módulo
|
|
342
|
+
- [ ] Los campos de auditoría no se mapean en DTOs de respuesta
|
|
343
|
+
- [ ] Queries usan `@Transactional(readOnly = true)`
|
|
344
|
+
- [ ] Commands usan `@Transactional`
|
|
345
|
+
- [ ] El handler mantiene `@ApplicationComponent` y `@LogExceptions`
|
|
346
|
+
- [ ] Activities Temporal: usan `@Component` + `@RequiredArgsConstructor` (no `@ApplicationComponent`)
|
|
347
|
+
- [ ] Activities Temporal: implementan interfaz del contrato + marker `{Module}Light/HeavyActivity`
|
|
348
|
+
- [ ] Activities Temporal: no tienen `@Transactional`
|
|
349
|
+
- [ ] Activities Temporal: acceden solo al repositorio de su propio módulo
|
|
350
|
+
- [ ] Activities de compensación: lógica es el inverso exacto de la activity principal
|