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.
Files changed (151) hide show
  1. package/AGENTS.md +220 -5
  2. package/DOMAIN_YAML_GUIDE.md +188 -3
  3. package/FUTURE_FEATURES.md +33 -52
  4. package/QUICK_REFERENCE.md +8 -4
  5. package/bin/eva4j.js +70 -2
  6. package/config/defaults.json +1 -0
  7. package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
  8. package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
  9. package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
  10. package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
  11. package/docs/commands/EVALUATE_SYSTEM.md +290 -10
  12. package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
  13. package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
  14. package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
  15. package/docs/commands/INDEX.md +27 -3
  16. package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
  17. package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
  18. package/docs/prototype/system/RISKS.md +277 -0
  19. package/docs/prototype/system/customers.yaml +133 -0
  20. package/docs/prototype/system/inventory.yaml +109 -0
  21. package/docs/prototype/system/notifications.yaml +131 -0
  22. package/docs/prototype/system/orders.yaml +241 -0
  23. package/docs/prototype/system/payments.yaml +256 -0
  24. package/docs/prototype/system/products.yaml +168 -0
  25. package/docs/prototype/system/system.yaml +269 -0
  26. package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
  27. package/examples/domain-events.yaml +26 -0
  28. package/examples/domain-read-models.yaml +113 -0
  29. package/examples/system/customer.yaml +89 -0
  30. package/examples/system/orders.yaml +119 -0
  31. package/examples/system/product.yaml +27 -0
  32. package/examples/system/system.yaml +80 -0
  33. package/package.json +1 -1
  34. package/read-model-spec.md +664 -0
  35. package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
  36. package/src/agents/design-gap-analyst.agent.md +383 -0
  37. package/src/agents/design-reviewer-temporal.agent.md +412 -0
  38. package/src/agents/design-reviewer.agent.md +34 -5
  39. package/src/agents/implement-use-cases.prompt.md +179 -0
  40. package/src/agents/ux-gap-analyst.agent.md +412 -0
  41. package/src/commands/add-rabbitmq-client.js +261 -0
  42. package/src/commands/add-temporal-client.js +22 -2
  43. package/src/commands/build.js +267 -11
  44. package/src/commands/evaluate-system.js +700 -13
  45. package/src/commands/generate-entities.js +560 -24
  46. package/src/commands/generate-http-exchange.js +3 -0
  47. package/src/commands/generate-kafka-event.js +3 -0
  48. package/src/commands/generate-kafka-listener.js +3 -0
  49. package/src/commands/generate-rabbitmq-event.js +665 -0
  50. package/src/commands/generate-rabbitmq-listener.js +205 -0
  51. package/src/commands/generate-record.js +2 -2
  52. package/src/commands/generate-resource.js +4 -1
  53. package/src/commands/generate-temporal-activity.js +970 -33
  54. package/src/commands/generate-temporal-flow.js +98 -38
  55. package/src/commands/generate-temporal-system.js +708 -0
  56. package/src/commands/generate-usecase.js +4 -1
  57. package/src/skills/build-system-yaml/SKILL.md +343 -2
  58. package/src/skills/build-system-yaml/references/domain-yaml-spec.md +253 -26
  59. package/src/skills/build-system-yaml/references/module-spec.md +90 -9
  60. package/src/skills/build-system-yaml/references/system-yaml-spec.md +36 -0
  61. package/src/skills/build-temporal-system/SKILL.md +752 -0
  62. package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
  63. package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
  64. package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
  65. package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
  66. package/src/skills/implement-use-case/SKILL.md +350 -0
  67. package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
  68. package/src/skills/requirements-elicitation/SKILL.md +228 -0
  69. package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
  70. package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
  71. package/src/utils/bounded-context-diagram.js +844 -0
  72. package/src/utils/config-manager.js +4 -2
  73. package/src/utils/domain-validator.js +495 -17
  74. package/src/utils/naming.js +20 -0
  75. package/src/utils/system-validator.js +169 -11
  76. package/src/utils/system-yaml-parser.js +318 -0
  77. package/src/utils/temporal-validator.js +497 -0
  78. package/src/utils/validator.js +3 -1
  79. package/src/utils/yaml-to-entity.js +281 -9
  80. package/templates/aggregate/AggregateRepository.java.ejs +4 -0
  81. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +8 -0
  82. package/templates/aggregate/AggregateRoot.java.ejs +38 -4
  83. package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
  84. package/templates/aggregate/JpaAggregateRoot.java.ejs +4 -4
  85. package/templates/aggregate/JpaEntity.java.ejs +2 -2
  86. package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
  87. package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
  88. package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
  89. package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
  90. package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
  91. package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
  92. package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
  93. package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
  94. package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
  95. package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
  96. package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
  97. package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
  98. package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
  99. package/templates/base/root/AGENTS.md.ejs +1 -1
  100. package/templates/crud/DeleteCommandHandler.java.ejs +19 -1
  101. package/templates/crud/EndpointsController.java.ejs +1 -1
  102. package/templates/crud/ScaffoldCommand.java.ejs +5 -2
  103. package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
  104. package/templates/crud/ScaffoldQuery.java.ejs +5 -2
  105. package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
  106. package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
  107. package/templates/crud/UpdateCommandHandler.java.ejs +53 -2
  108. package/templates/evaluate/report.html.ejs +1447 -90
  109. package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
  110. package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
  111. package/templates/ports/PortAclMapper.java.ejs +35 -0
  112. package/templates/ports/PortFeignAdapter.java.ejs +7 -22
  113. package/templates/ports/PortFeignClient.java.ejs +4 -0
  114. package/templates/ports/PortResponseDto.java.ejs +1 -1
  115. package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
  116. package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
  117. package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
  118. package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
  119. package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
  120. package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
  121. package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
  122. package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
  123. package/templates/read-model/ReadModelDomain.java.ejs +46 -0
  124. package/templates/read-model/ReadModelJpa.java.ejs +58 -0
  125. package/templates/read-model/ReadModelJpaRepository.java.ejs +13 -0
  126. package/templates/read-model/ReadModelKafkaListener.java.ejs +64 -0
  127. package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
  128. package/templates/read-model/ReadModelRepository.java.ejs +42 -0
  129. package/templates/read-model/ReadModelRepositoryImpl.java.ejs +85 -0
  130. package/templates/read-model/ReadModelSyncHandler.java.ejs +54 -0
  131. package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
  132. package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
  133. package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
  134. package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
  135. package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
  136. package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
  137. package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
  138. package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
  139. package/templates/temporal-activity/NestedType.java.ejs +12 -0
  140. package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
  141. package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
  142. package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
  143. package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
  144. package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
  145. package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
  146. package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
  147. package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
  148. package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
  149. package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
  150. package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
  151. package/COMMAND_EVALUATION.md +0 -911
@@ -0,0 +1,441 @@
1
+ # Configuración Kafka para Producción
2
+
3
+ Guía de referencia para la configuración de `kafka.yaml` por entorno en proyectos generados con eva4j (Spring Kafka).
4
+
5
+ ---
6
+
7
+ ## Configuración generada por entorno
8
+
9
+ eva4j genera templates `kafka.yaml` diferenciados por entorno. Los valores de desarrollo local priorizan simplicidad; los de producción priorizan fiabilidad, seguridad y throughput.
10
+
11
+ > **Nota:** Si se despliega con el perfil `local` en producción, la aplicación usará `localhost:9092`, `trusted.packages: "*"`, y reintentos mínimos. Siempre activar el perfil `production` en entornos reales.
12
+
13
+ ---
14
+
15
+ ## Configuración completa recomendada
16
+
17
+ ### Desarrollo local (`parameters/local/kafka.yaml`)
18
+
19
+ ```yaml
20
+ spring:
21
+ kafka:
22
+ bootstrap-servers:
23
+ - localhost:9092
24
+ producer:
25
+ properties:
26
+ spring.json.add.type.headers: false
27
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
28
+ value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
29
+ retries: 3
30
+ consumer:
31
+ group-id: my-app-api-group
32
+ auto-offset-reset: earliest
33
+ enable-auto-commit: false
34
+ key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
35
+ value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
36
+ properties:
37
+ spring.json.trusted.packages: "*"
38
+ spring.json.use.type.headers: false
39
+ spring.json.value.default.type: com.example.shared.infrastructure.eventEnvelope.EventEnvelope
40
+ listener:
41
+ ack-mode: manual
42
+ concurrency: 3
43
+ retry:
44
+ max-attempts: 2
45
+ backoff-delay: 1500
46
+
47
+ kafka:
48
+ topic-defaults:
49
+ partitions: 3
50
+ replicas: 1
51
+ ```
52
+
53
+ ### Test (`parameters/test/kafka.yaml`)
54
+
55
+ ```yaml
56
+ spring:
57
+ kafka:
58
+ bootstrap-servers:
59
+ - localhost:9092
60
+ producer:
61
+ properties:
62
+ spring.json.add.type.headers: false
63
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
64
+ value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
65
+ retries: 1
66
+ consumer:
67
+ group-id: my-app-test-group
68
+ auto-offset-reset: earliest
69
+ enable-auto-commit: false
70
+ key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
71
+ value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
72
+ properties:
73
+ spring.json.trusted.packages: "*"
74
+ spring.json.use.type.headers: false
75
+ spring.json.value.default.type: com.example.shared.infrastructure.eventEnvelope.EventEnvelope
76
+ max.poll.records: 10
77
+ listener:
78
+ ack-mode: manual
79
+ concurrency: 1
80
+ retry:
81
+ max-attempts: 1
82
+ backoff-delay: 100
83
+
84
+ kafka:
85
+ topic-defaults:
86
+ partitions: 1
87
+ replicas: 1
88
+ ```
89
+
90
+ ### Producción (`parameters/production/kafka.yaml`)
91
+
92
+ ```yaml
93
+ spring:
94
+ kafka:
95
+ bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS}
96
+ producer:
97
+ properties:
98
+ spring.json.add.type.headers: false
99
+ enable.idempotence: true
100
+ max.in.flight.requests.per.connection: 5
101
+ delivery.timeout.ms: 120000
102
+ request.timeout.ms: 30000
103
+ linger.ms: 5
104
+ batch.size: 32768
105
+ compression.type: snappy
106
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
107
+ value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
108
+ acks: all
109
+ retries: 10
110
+ consumer:
111
+ group-id: ${spring.application.name}-group
112
+ auto-offset-reset: earliest
113
+ enable-auto-commit: false
114
+ key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
115
+ value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
116
+ properties:
117
+ spring.json.trusted.packages: "com.example.**"
118
+ spring.json.use.type.headers: false
119
+ spring.json.value.default.type: com.example.shared.infrastructure.eventEnvelope.EventEnvelope
120
+ max.poll.records: 50
121
+ max.poll.interval.ms: 300000
122
+ session.timeout.ms: 30000
123
+ heartbeat.interval.ms: 10000
124
+ fetch-min-size: 1
125
+ fetch-max-wait: 500
126
+ listener:
127
+ ack-mode: manual
128
+ concurrency: 5
129
+ retry:
130
+ max-attempts: 5
131
+ backoff-delay: 2000
132
+ backoff-multiplier: 2.0
133
+ backoff-max-delay: 30000
134
+ # SSL/SASL — descomentar si el cluster lo requiere
135
+ # security:
136
+ # protocol: SASL_SSL
137
+ # properties:
138
+ # sasl.mechanism: PLAIN
139
+ # sasl.jaas.config: >-
140
+ # org.apache.kafka.common.security.plain.PlainLoginModule required
141
+ # username="${KAFKA_USERNAME}"
142
+ # password="${KAFKA_PASSWORD}";
143
+ # ssl.truststore.location: ${KAFKA_TRUSTSTORE_LOCATION}
144
+ # ssl.truststore.password: ${KAFKA_TRUSTSTORE_PASSWORD}
145
+
146
+ kafka:
147
+ topic-defaults:
148
+ partitions: 3
149
+ replicas: 3
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Referencia de parámetros
155
+
156
+ ### Conexión
157
+
158
+ | Parámetro | Valor por defecto | Valor configurado (local) | Recomendado producción | Descripción |
159
+ |---|---|---|---|---|
160
+ | `bootstrap-servers` | `localhost:9092` | `localhost:9092` | `${KAFKA_BOOTSTRAP_SERVERS}` | Lista de brokers Kafka para el descubrimiento inicial del cluster. En producción debe venir de variable de entorno. Listar al menos 2-3 brokers para tolerancia a fallos: `broker1:9092,broker2:9092,broker3:9092`. |
161
+
162
+ > **Regla:** Nunca hardcodear direcciones de brokers en producción. Un solo broker en la lista es un punto único de fallo.
163
+
164
+ ---
165
+
166
+ ### Producer (publicación de eventos)
167
+
168
+ | Parámetro | Valor por defecto Spring | Valor configurado | Recomendado producción | Descripción |
169
+ |---|---|---|---|---|
170
+ | `key-serializer` | `StringSerializer` | `StringSerializer` | `StringSerializer` | Serializador para las claves de los mensajes Kafka. `StringSerializer` es el estándar para claves basadas en IDs de entidad. |
171
+ | `value-serializer` | `StringSerializer` | `JsonSerializer` | `JsonSerializer` | Serializador para el cuerpo del mensaje. `JsonSerializer` convierte los objetos Java a JSON automáticamente. |
172
+ | `spring.json.add.type.headers` | `true` | `false` | `false` | Si `true`, agrega headers `__TypeId__` con la clase Java del payload. Con `false`, el JSON es genérico y no acopla al consumidor con tipos Java del productor. **Crítico** para comunicación entre microservicios con diferentes classpaths. |
173
+ | `retries` | `2147483647` (MAX_INT) | `3` | `10` | Número de reintentos automáticos cuando falla el envío (error de red, líder no disponible, etc.). Con `3` en local es suficiente; en producción usar `10` combinado con `delivery.timeout.ms` para controlar el tiempo total de reintento. |
174
+ | `acks` | `all` (desde Kafka 3.0) | *no configurado* | `all` | Nivel de confirmación del broker. `0`: fire-and-forget. `1`: solo el líder confirma. `all` (`-1`): todos los ISR (in-sync replicas) confirman. **Obligatorio** para at-least-once delivery. |
175
+
176
+ #### Propiedades avanzadas del producer (producción)
177
+
178
+ | Propiedad | Valor por defecto Kafka | Recomendado | Descripción |
179
+ |---|---|---|---|
180
+ | `enable.idempotence` | `true` (Kafka 3.0+) | `true` | Garantiza que un mensaje reintentado no produzca duplicados en el broker. Requiere `acks=all` y `max.in.flight.requests.per.connection ≤ 5`. Es la base de **exactly-once semantics** en el productor. |
181
+ | `max.in.flight.requests.per.connection` | `5` | `5` | Número máximo de batches enviados sin confirmación. Con idempotencia activa, Kafka garantiza orden incluso con `5`. Sin idempotencia, usar `1` para mantener orden estricto. |
182
+ | `delivery.timeout.ms` | `120000` (2 min) | `120000` | Tiempo total máximo en ms para entregar un mensaje (incluyendo todos los reintentos). Si se agota, el envío falla con `TimeoutException`. Prevalece sobre `retries` × `retry.backoff.ms`. |
183
+ | `request.timeout.ms` | `30000` | `30000` | Tiempo máximo de espera por una respuesta del broker a un request individual. Si se excede, se considera el request como fallido y se contabiliza un reintento. |
184
+ | `linger.ms` | `0` | `5` | Milisegundos de espera antes de enviar un batch, acumulando más mensajes. `0` = envío inmediato (baja latencia). `5` = micro-batching que mejora throughput un 10-30% sin latencia perceptible. |
185
+ | `batch.size` | `16384` (16 KB) | `32768` (32 KB) | Tamaño máximo en bytes del batch de mensajes acumulados por partición. Incrementar mejora throughput en escenarios de alto volumen. |
186
+ | `compression.type` | `none` | `snappy` | Compresión de los batches. `snappy`: buen balance entre CPU y compresión (~50% reducción). `lz4`: más rápido, menos compresión. `gzip`: más compresión, más CPU. `zstd`: mejor ratio pero más CPU. |
187
+
188
+ ---
189
+
190
+ ### Consumer (consumo de eventos)
191
+
192
+ | Parámetro | Valor por defecto Spring | Valor configurado | Recomendado producción | Descripción |
193
+ |---|---|---|---|---|
194
+ | `group-id` | *ninguno* (obligatorio) | `test-eva-api-group` | `${spring.application.name}-group` | Identificador del consumer group. Todos los consumidores con el mismo `group-id` comparten la carga de los topics suscritos. **Parametrizar** con el nombre de la aplicación para que sea único por servicio. |
195
+ | `auto-offset-reset` | `latest` | `earliest` | `earliest` | Comportamiento cuando no hay offset previo almacenado. `earliest`: leer desde el inicio (no perder mensajes). `latest`: leer solo mensajes nuevos. `earliest` es más seguro para **no perder eventos** en el primer arranque o tras expiración de offsets. |
196
+ | `enable-auto-commit` | `true` | `false` | `false` | Si `true`, los offsets se confirman automáticamente cada `auto.commit.interval.ms` (5s por defecto). **Riesgo**: si la app falla después del auto-commit pero antes de procesar, el mensaje se pierde. `false` + `ack-mode: manual` es la combinación segura. |
197
+ | `key-deserializer` | `StringDeserializer` | `StringDeserializer` | `StringDeserializer` | Deserializador de claves. Debe coincidir con el `key-serializer` del productor. |
198
+ | `value-deserializer` | `StringDeserializer` | `JsonDeserializer` | `JsonDeserializer` | Deserializador del cuerpo. `JsonDeserializer` convierte el JSON a objetos Java usando Jackson. |
199
+
200
+ #### Propiedades del consumer
201
+
202
+ | Propiedad | Valor por defecto Kafka | Valor configurado | Recomendado producción | Descripción |
203
+ |---|---|---|---|---|
204
+ | `spring.json.trusted.packages` | *ninguno* (nada confiable) | `"*"` | Paquete base del proyecto | Paquetes Java cuyos tipos se permite deserializar. `"*"` confía en **todos los tipos** — riesgo de deserialización de clases maliciosas si un atacante inyecta un `__TypeId__` header. En producción, restringir al paquete del EventEnvelope: `"com.example.shared.infrastructure.eventEnvelope"`. |
205
+ | `spring.json.use.type.headers` | `true` | `false` | `false` | Si `true`, el deserializador usa el header `__TypeId__` para determinar la clase destino. Con `false`, usa el `default.type` fijo. **Combinado con** `add.type.headers: false` en el productor, asegura deserialización predecible sin acoplamiento de tipos. |
206
+ | `spring.json.value.default.type` | *ninguno* | `...EventEnvelope` | `...EventEnvelope` | Clase Java por defecto para deserializar el payload cuando `use.type.headers: false`. Todos los mensajes se deserializan como `EventEnvelope`, que es la envolvente estándar de eva4j. |
207
+ | `max.poll.records` | `500` | *no configurado* | `50` | Número máximo de registros devueltos por cada llamada a `poll()`. Un valor alto procesa más rápido pero aumenta el riesgo de timeout si el procesamiento es lento. `50` es un buen balance para procesamiento con lógica de negocio. |
208
+ | `max.poll.interval.ms` | `300000` (5 min) | *no configurado* | `300000` | Tiempo máximo entre llamadas a `poll()` antes de que el consumer sea considerado muerto y se dispare un rebalanceo. Aumentar si los handlers tardan mucho; reducir para detectar consumidores bloqueados más rápido. |
209
+ | `session.timeout.ms` | `45000` | *no configurado* | `30000` | Tiempo sin heartbeat antes de que el broker considere al consumer muerto. Debe ser `>= 3 × heartbeat.interval.ms`. |
210
+ | `heartbeat.interval.ms` | `3000` | *no configurado* | `10000` | Intervalo entre heartbeats del consumer al coordinator. Permite detectar consumers muertos. Debe ser `< session.timeout.ms / 3`. |
211
+ | `fetch-min-size` | `1` | *no configurado* | `1` | Bytes mínimos que el broker acumula antes de responder a un fetch. `1` = responder inmediatamente (baja latencia). |
212
+ | `fetch-max-wait` | `500` | *no configurado* | `500` | Milisegundos máximos de espera del broker hasta alcanzar `fetch-min-size`. Balancea latencia vs throughput. |
213
+
214
+ > **Seguridad:** `spring.json.trusted.packages: "*"` es aceptable **solo** cuando `spring.json.use.type.headers: false`, porque el deserializador ignora los headers de tipo y siempre usa `default.type`. Aun así, en producción se recomienda restringir como defensa en profundidad.
215
+
216
+ ---
217
+
218
+ ### Listener (contenedor de consumo)
219
+
220
+ | Parámetro | Valor por defecto Spring Kafka | Valor configurado | Recomendado producción | Descripción |
221
+ |---|---|---|---|---|
222
+ | `ack-mode` | `BATCH` | `manual` | `manual` | Modo de confirmación de offsets. `BATCH`: confirma automáticamente por batch. `MANUAL`: el código invoca `ack.acknowledge()` explícitamente. `MANUAL` garantiza que solo se confirme después del procesamiento exitoso. ✅ Correctamente configurado. |
223
+ | `concurrency` | `1` | `3` | `5` | Número de hilos consumers (uno por partición asignada). Debe ser `≤` número de particiones del topic. Con `3` en local es adecuado; en producción `5` — ajustar según la cantidad de particiones. |
224
+
225
+ #### Retry del listener
226
+
227
+ | Parámetro | Valor por defecto | Valor configurado | Recomendado producción | Descripción |
228
+ |---|---|---|---|---|
229
+ | `retry.max-attempts` | `3` | `2` | `5` | Número total de intentos (incluido el primero). Con `2`, solo hay **1 reintento** — insuficiente para errores transitorios de red o base de datos. `5` da margen sin saturar. |
230
+ | `retry.backoff-delay` | `1000` | `1500` | `2000` | Milisegundos de espera antes del primer reintento. `1500` es razonable; `2000` da más margen para recuperación de dependencias. |
231
+ | `retry.backoff-multiplier` | `1.0` (lineal) | *no configurado* | `2.0` | Factor multiplicador entre reintentos. Sin multiplicador, todos los reintentos esperan lo mismo (1500ms). Con `2.0`: 2s → 4s → 8s → 16s (exponential backoff). **Previene** la tormenta de reintentos en cascada bajo fallo sistémico. |
232
+ | `retry.backoff-max-delay` | `30000` | *no configurado* | `30000` | Tope máximo del intervalo entre reintentos en ms. Evita esperas excesivas con muchos reintentos y multiplicador alto. |
233
+
234
+ ---
235
+
236
+ ### Topics
237
+
238
+ | Clave YAML (kebab-case) | Valor (SCREAMING_SNAKE_CASE) | Descripción |
239
+ |---|---|---|
240
+ | `product-created` | `PRODUCT_CREATED` | Topic para eventos de creación de producto |
241
+ | `product-updated` | `PRODUCT_UPDATED` | Topic para eventos de actualización de producto |
242
+ | `product-deactivated` | `PRODUCT_DEACTIVATED` | Topic para eventos de desactivación de producto |
243
+ | `customer-created` | `CUSTOMER_CREATED` | Topic para eventos de creación de cliente |
244
+ | `customer-updated` | `CUSTOMER_UPDATED` | Topic para eventos de actualización de cliente |
245
+ | `order-placed` | `ORDER_PLACED` | Topic para eventos de órdenes colocadas |
246
+ | `order-cancelled` | `ORDER_CANCELLED` | Topic para eventos de cancelación de órdenes |
247
+ | `payment-approved` | `PAYMENT_APPROVED` | Topic para eventos de pago aprobado |
248
+ | `payment-failed` | `PAYMENT_FAILED` | Topic para eventos de pago fallido |
249
+
250
+ La convención de naming eva4j usa **kebab-case** como clave Spring (`${topics.order-placed}`) y **SCREAMING_SNAKE_CASE** como nombre real del topic en el cluster Kafka.
251
+
252
+ > **Nota sobre particiones y replicación:** Los topics se crean vía `NewTopic` beans en `KafkaConfig.java`. Las particiones y réplicas se leen dinámicamente de `kafka.topic-defaults.partitions` y `kafka.topic-defaults.replicas` desde `kafka.yaml`, con valores por defecto de `3` y `1` respectivamente. Para producción, el template genera `replicas: 3` — el cluster debe tener al menos 3 brokers con `min.insync.replicas: 2`.
253
+
254
+ ### Sección `kafka.topic-defaults`
255
+
256
+ Las particiones y réplicas de los `NewTopic` beans se parametrizan desde `kafka.yaml`:
257
+
258
+ | Entorno | `partitions` | `replicas` | Razón |
259
+ |---|---|---|---|
260
+ | local | 3 | 1 | Un solo broker en Docker |
261
+ | develop | 3 | 1 | Cluster de desarrollo mínimo |
262
+ | test | 1 | 1 | Tests deterministas, un solo consumer |
263
+ | production | 3 | 3 | Alta disponibilidad, tolerancia a fallo de broker |
264
+
265
+ `KafkaConfig.java` lee estos valores con:
266
+ ```java
267
+ @Value("${kafka.topic-defaults.partitions:3}")
268
+ private int defaultPartitions;
269
+
270
+ @Value("${kafka.topic-defaults.replicas:1}")
271
+ private short defaultReplicas;
272
+ ```
273
+
274
+ Cada `NewTopic` bean usa estos campos:
275
+ ```java
276
+ @Bean
277
+ public NewTopic orderPlacedTopic() {
278
+ return new NewTopic(orderPlacedTopic, defaultPartitions, defaultReplicas);
279
+ }
280
+ ```
281
+
282
+ ### Dimensionamiento de particiones
283
+
284
+ El número de particiones determina el **paralelismo máximo** de consumo. Cada partición es asignada a exactamente un consumer dentro del mismo group.
285
+
286
+ | Particiones | Consumers | Resultado |
287
+ |---|---|---|
288
+ | 3 | 1 | 1 consumer lee las 3 particiones (sin paralelismo) |
289
+ | 3 | 3 | 1 consumer por partición (óptimo) |
290
+ | 3 | 5 | 3 activos + **2 ociosos** (desperdicio de recursos) |
291
+ | 6 | 3 | 2 particiones por consumer (balanceado) |
292
+
293
+ > **Regla:** `particiones ≥ consumers`. Consumers que exceden el número de particiones quedan idle.
294
+
295
+ #### Guía de sizing por volumen
296
+
297
+ | Volumen de mensajes | Particiones recomendadas | Razón |
298
+ |---|---|---|
299
+ | < 100 msg/s | 3 | Mínimo para HA con 3 brokers |
300
+ | 100–1,000 msg/s | 3–6 | Balance entre paralelismo y overhead |
301
+ | 1,000–10,000 msg/s | 6–12 | Distribuir carga entre más consumers |
302
+ | > 10,000 msg/s | 12–30+ | Alto throughput, dimensionar según latencia objetivo |
303
+
304
+ > **Advertencia:** Las particiones de un topic **no se pueden reducir** después de crearlo — solo incrementar. Sobre-particionar (ej: 20 particiones para 50 msg/s) causa overhead innecesario: más file handles en los brokers, mayor uso de memoria, rebalanceos más lentos, y más leader elections. Empezar con 3–6 y escalar cuando las métricas de consumer lag lo justifiquen.
305
+ >
306
+ > Si los mensajes usan **keyed partitioning** (misma key → misma partición), cambiar el número de particiones redistribuye las keys — lo cual puede romper el ordenamiento por entidad.
307
+
308
+ ---
309
+
310
+ ## Seguridad (SSL/SASL)
311
+
312
+ ### Cuándo es necesario
313
+
314
+ | Escenario | Requiere SSL/SASL |
315
+ |---|---|
316
+ | Kafka en localhost / misma máquina | No |
317
+ | Kafka en red privada (VPC/VPN) | Recomendado |
318
+ | Kafka en red pública / cloud | **Obligatorio** |
319
+ | Confluent Cloud / Amazon MSK / Aiven | **Obligatorio** (SASL_SSL) |
320
+
321
+ ### Configuración SASL_SSL
322
+
323
+ ```yaml
324
+ spring:
325
+ kafka:
326
+ security:
327
+ protocol: SASL_SSL
328
+ properties:
329
+ sasl.mechanism: PLAIN
330
+ sasl.jaas.config: >-
331
+ org.apache.kafka.common.security.plain.PlainLoginModule required
332
+ username="${KAFKA_USERNAME}"
333
+ password="${KAFKA_PASSWORD}";
334
+ ssl.truststore.location: ${KAFKA_TRUSTSTORE_LOCATION}
335
+ ssl.truststore.password: ${KAFKA_TRUSTSTORE_PASSWORD}
336
+ ```
337
+
338
+ ### Mecanismos SASL disponibles
339
+
340
+ | Mecanismo | Uso típico | Descripción |
341
+ |---|---|---|
342
+ | `PLAIN` | Confluent Cloud, desarrollo | Username/password sobre SSL. Simple pero suficiente con TLS. |
343
+ | `SCRAM-SHA-256/512` | Clusters self-hosted | Challenge-response, más seguro que PLAIN sin SSL. |
344
+ | `OAUTHBEARER` | Enterprise / SSO | OAuth 2.0 tokens. Para integración con identity providers. |
345
+
346
+ ---
347
+
348
+ ## Dead Letter Topic (DLT)
349
+
350
+ Spring Kafka soporta Dead Letter Topics para mensajes que agotan los reintentos. A diferencia de RabbitMQ (donde la DLQ se configura en el broker), en Kafka la DLT se configura en el **consumer**.
351
+
352
+ eva4j genera automáticamente `KafkaConfig.java` con `ExponentialBackOff` y `DeadLetterPublishingRecoverer`. Los reintentos se configuran desde `kafka.yaml`:
353
+
354
+ ```java
355
+ @Value("${spring.kafka.listener.retry.max-attempts}")
356
+ private int maxAttempts;
357
+
358
+ @Value("${spring.kafka.listener.retry.backoff-delay}")
359
+ private long backoffDelay;
360
+
361
+ @Value("${spring.kafka.listener.retry.backoff-multiplier:1.0}")
362
+ private double backoffMultiplier;
363
+
364
+ @Value("${spring.kafka.listener.retry.backoff-max-delay:30000}")
365
+ private long backoffMaxDelay;
366
+
367
+ @Bean
368
+ public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(
369
+ ConsumerFactory<String, String> consumerFactory,
370
+ KafkaTemplate<Object, Object> kafkaTemplate) {
371
+
372
+ var factory = new ConcurrentKafkaListenerContainerFactory<String, String>();
373
+ factory.setConsumerFactory(consumerFactory);
374
+ factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
375
+
376
+ DeadLetterPublishingRecoverer recoverer = new DeadLetterPublishingRecoverer(
377
+ kafkaTemplate,
378
+ (record, ex) -> new TopicPartition(record.topic() + ".DLT", record.partition())
379
+ );
380
+
381
+ // ExponentialBackOff: con multiplier=1.0 (local) se comporta como FixedBackOff
382
+ // Con multiplier=2.0 (producción): 2s → 4s → 8s → 16s → 30s (max)
383
+ ExponentialBackOff backOff = new ExponentialBackOff(backoffDelay, backoffMultiplier);
384
+ backOff.setMaxInterval(backoffMaxDelay);
385
+ backOff.setMaxAttempts(maxAttempts);
386
+
387
+ DefaultErrorHandler errorHandler = new DefaultErrorHandler(recoverer, backOff);
388
+ factory.setCommonErrorHandler(errorHandler);
389
+ return factory;
390
+ }
391
+ ```
392
+
393
+ > Cuando un mensaje agota los reintentos, se publica automáticamente en el topic `{original-topic}.DLT`. No requiere configuración adicional.
394
+
395
+ ---
396
+
397
+ ## Diferencias por entorno
398
+
399
+ | Parámetro | local/develop | test | producción |
400
+ |---|---|---|---|
401
+ | `bootstrap-servers` | `localhost:9092` | `localhost:9092` | `${KAFKA_BOOTSTRAP_SERVERS}` |
402
+ | `group-id` | `{app}-api-group` | `{app}-test-group` | `${spring.application.name}-group` |
403
+ | `trusted.packages` | `"*"` | `"*"` | `"{packageName}.**"` |
404
+ | `security.protocol` | — | — | `SASL_SSL` (comentado) |
405
+ | `producer.acks` | *default* | *default* | `all` |
406
+ | `producer.retries` | `3` | `1` | `10` |
407
+ | `enable.idempotence` | *default* | *default* | `true` (explícito) |
408
+ | `compression.type` | — | — | `snappy` |
409
+ | `listener.concurrency` | `3` | `1` | `5` |
410
+ | `retry.max-attempts` | `2` | `1` | `5` |
411
+ | `retry.backoff-delay` | `1500` | `100` | `2000` |
412
+ | `retry.backoff-multiplier` | *1.0 (default)* | *1.0 (default)* | `2.0` |
413
+ | `retry.backoff-max-delay` | *30000 (default)* | *30000 (default)* | `30000` |
414
+ | `max.poll.records` | *500 (default)* | `10` | `50` |
415
+ | `topic-defaults.partitions` | `3` | `1` | `3` |
416
+ | `topic-defaults.replicas` | `1` | `1` | `3` |
417
+
418
+ ---
419
+
420
+ ## Checklist de producción
421
+
422
+ - [ ] `bootstrap-servers` en variable de entorno (lista de múltiples brokers)
423
+ - [ ] `group-id` parametrizado con `${spring.application.name}`
424
+ - [ ] `spring.json.trusted.packages` restringido al paquete del proyecto
425
+ - [ ] `acks: all` configurado en el producer
426
+ - [ ] `enable.idempotence: true` explícito
427
+ - [ ] `retries: 10` + `delivery.timeout.ms: 120000`
428
+ - [ ] `compression.type: snappy` para reducir ancho de banda
429
+ - [ ] `enable-auto-commit: false` + `ack-mode: manual`
430
+ - [ ] `retry.max-attempts: 5` con `backoff-multiplier: 2.0`
431
+ - [ ] `max.poll.records` ajustado según velocidad de procesamiento
432
+ - [ ] `session.timeout.ms` y `heartbeat.interval.ms` configurados
433
+ - [ ] SSL/SASL configurado si el broker no está en red privada
434
+ - [ ] Dead Letter Topic configurado para mensajes irrecuperables
435
+ - [ ] Al menos 3 particiones y factor de replicación 3 en topics de producción (`kafka.topic-defaults`)
436
+ - [ ] Monitoreo con métricas de consumer lag (Prometheus / Grafana / Confluent Control Center)
437
+
438
+ ---
439
+
440
+ **Versión:** 1.1
441
+ **Última actualización:** 2026-04-05
@@ -0,0 +1,227 @@
1
+ # Configuración RabbitMQ para Producción
2
+
3
+ Guía de referencia para la configuración de `rabbitmq.yaml` en entornos productivos con Spring Boot (Spring AMQP).
4
+
5
+ ---
6
+
7
+ ## Configuración completa recomendada
8
+
9
+ ```yaml
10
+ spring:
11
+ rabbitmq:
12
+ host: ${RABBITMQ_HOST}
13
+ port: ${RABBITMQ_PORT:5672}
14
+ username: ${RABBITMQ_USERNAME}
15
+ password: ${RABBITMQ_PASSWORD}
16
+ virtual-host: ${RABBITMQ_VHOST:/}
17
+ connection-timeout: 5000
18
+ requested-heartbeat: 60
19
+ channel-rpc-timeout: 10000
20
+ publisher-confirm-type: correlated
21
+ publisher-returns: true
22
+ ssl:
23
+ enabled: ${RABBITMQ_SSL_ENABLED:false}
24
+ verify-hostname: true
25
+ cache:
26
+ channel:
27
+ size: 25
28
+ checkout-timeout: 0
29
+ template:
30
+ mandatory: true
31
+ listener:
32
+ simple:
33
+ acknowledge-mode: manual
34
+ concurrency: 5
35
+ max-concurrency: 20
36
+ prefetch: 10
37
+ retry:
38
+ enabled: true
39
+ max-attempts: 5
40
+ initial-interval: 2000
41
+ multiplier: 2.0
42
+ max-interval: 30000
43
+ stateless: true
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Referencia de parámetros
49
+
50
+ ### Conexión básica
51
+
52
+ | Parámetro | Valor por defecto | Descripción |
53
+ |---|---|---|
54
+ | `host` | `localhost` | Dirección del broker RabbitMQ. En producción debe venir de variable de entorno. |
55
+ | `port` | `5672` | Puerto AMQP estándar. Usar `5671` cuando SSL está activo. |
56
+ | `username` | `guest` | Usuario de autenticación. **El usuario `guest` solo puede conectar desde `127.0.0.1`**; en producción se rechaza si el broker está en otro host. |
57
+ | `password` | `guest` | Contraseña. Siempre desde variable de entorno en producción. |
58
+ | `virtual-host` | `/` | Virtual host de RabbitMQ. Permite aislar colas y exchanges entre aplicaciones en un mismo broker. |
59
+
60
+ > **Regla:** Nunca hardcodear credenciales. Usar `${ENV_VAR}` sin valor por defecto en producción para forzar que la variable esté declarada.
61
+
62
+ ---
63
+
64
+ ### Timeouts y heartbeat
65
+
66
+ | Parámetro | Valor por defecto | Recomendado producción | Descripción |
67
+ |---|---|---|---|
68
+ | `connection-timeout` | `0` (sin límite) | `5000` | Milisegundos máximos para establecer la conexión TCP con el broker. Un valor de `0` puede bloquear el arranque indefinidamente si el broker no está disponible. |
69
+ | `requested-heartbeat` | `60` | `60` | Segundos entre latidos TCP. Permite detectar conexiones muertas cuando no hay tráfico. Si un lado no recibe un heartbeat en `2 × valor` segundos, cierra la conexión. |
70
+ | `channel-rpc-timeout` | `10000` | `10000` | Milisegundos de espera para operaciones síncronas sobre el canal (declarar colas, exchanges, etc.). Previene bloqueos al arranque. |
71
+
72
+ ---
73
+
74
+ ### SSL / TLS
75
+
76
+ | Parámetro | Valor por defecto | Descripción |
77
+ |---|---|---|
78
+ | `ssl.enabled` | `false` | Activa el cifrado TLS en la conexión con el broker. Obligatorio en cualquier red no completamente privada. |
79
+ | `ssl.verify-hostname` | `true` | Valida que el certificado del servidor corresponda al hostname al que se conecta. Previene ataques man-in-the-middle. Desactivar solo en entornos de pruebas con certificados autofirmados. |
80
+ | `ssl.key-store` | — | Ruta al keystore (`.p12` o `.jks`) con el certificado del cliente, si el broker requiere mutual TLS (mTLS). |
81
+ | `ssl.trust-store` | — | Ruta al truststore con los certificados de CA confiables para validar el certificado del broker. |
82
+
83
+ > Puerto estándar con SSL: `5671`.
84
+
85
+ ---
86
+
87
+ ### Caché de canales
88
+
89
+ | Parámetro | Valor por defecto | Recomendado producción | Descripción |
90
+ |---|---|---|---|
91
+ | `cache.channel.size` | `25` | `25` | Número de canales AMQP que se mantienen abiertos y reutilizables por conexión. Crear y destruir canales en cada operación es costoso; la caché elimina esa latencia. |
92
+ | `cache.channel.checkout-timeout` | `0` | `0` | Milisegundos de espera cuando todos los canales en caché están ocupados. `0` significa crear un canal adicional en lugar de esperar. Útil en picos de tráfico. |
93
+ | `cache.connection.mode` | `CHANNEL` | `CHANNEL` | `CHANNEL` = una sola conexión TCP con múltiples canales multiplexados (recomendado). `CONNECTION` = una conexión TCP por hilo (solo para casos muy específicos). |
94
+
95
+ ---
96
+
97
+ ### Publisher Confirms (garantía de entrega en publicación)
98
+
99
+ | Parámetro | Valor por defecto | Recomendado producción | Descripción |
100
+ |---|---|---|---|
101
+ | `publisher-confirm-type` | `none` | `correlated` | Modo de confirmación del broker al publicar mensajes. `NONE`: sin confirmación (fire-and-forget). `SIMPLE`: confirmación simple, bloquea el hilo. `CORRELATED`: confirmación asíncrona con callback, recomendada en producción. |
102
+ | `publisher-returns` | `false` | `true` | Si `true`, el broker notifica cuando un mensaje no puede enrutarse a ninguna cola. Requiere `template.mandatory: true`. |
103
+ | `template.mandatory` | `false` | `true` | Lanza excepción (o invoca `ReturnsCallback`) cuando un mensaje publicado no encuentra ninguna cola destino. Previene pérdida silenciosa de mensajes no enrutables. |
104
+
105
+ > `publisher-confirm-type: correlated` + `publisher-returns: true` + `template.mandatory: true` forman el trío de **at-least-once delivery** en el lado productor.
106
+
107
+ ---
108
+
109
+ ### Listener (consumidor)
110
+
111
+ | Parámetro | Valor por defecto | Recomendado producción | Descripción |
112
+ |---|---|---|---|
113
+ | `acknowledge-mode` | `auto` | `manual` | Modo de ACK de mensajes. `AUTO`: el framework hace ACK automático al recibir (riesgo de pérdida si el procesamiento falla). `MANUAL`: el código hace ACK/NACK explícito; garantiza que el mensaje no se descarte hasta ser procesado correctamente. |
114
+ | `concurrency` | `1` | `5` | Número mínimo de hilos consumers por listener. Determina el paralelismo base de consumo. |
115
+ | `max-concurrency` | igual a `concurrency` | `20` | Número máximo de hilos consumers que Spring puede crear dinámicamente bajo carga alta. Permite escalar sin reiniciar la aplicación. |
116
+ | `prefetch` | `250` | `10` | Cuántos mensajes sin ACK puede recibir cada consumer antes de que el broker deje de enviarle más. Un valor alto maximiza throughput pero aumenta el riesgo de perder mensajes en fallo. Un valor bajo (`1`–`10`) distribuye mejor la carga entre consumers. |
117
+
118
+ ---
119
+
120
+ ### Retry (reintentos en fallo)
121
+
122
+ | Parámetro | Valor por defecto | Recomendado producción | Descripción |
123
+ |---|---|---|---|
124
+ | `retry.enabled` | `false` | `true` | Activa el mecanismo de reintentos automáticos cuando el listener lanza una excepción. |
125
+ | `retry.max-attempts` | `3` | `5` | Número máximo de intentos totales (incluyendo el primero). Al agotar los intentos, el mensaje va a la Dead Letter Queue (si está configurada) o se descarta. |
126
+ | `retry.initial-interval` | `1000` | `2000` | Milisegundos de espera antes del primer reintento. |
127
+ | `retry.multiplier` | `1.0` (sin cambio) | `2.0` | Factor multiplicador del intervalo entre reintentos. Con `2.0` y `initial-interval: 2000` los intervalos serán: 2s → 4s → 8s → 16s (exponential backoff). Reduce la tormenta de reintentos en cascada. |
128
+ | `retry.max-interval` | `10000` | `30000` | Tope máximo del intervalo entre reintentos en milisegundos, independientemente del `multiplier`. Evita esperas excesivas con muchos reintentos. |
129
+ | `retry.stateless` | `true` | `true` | `true` para listeners sin estado (recomendado). `false` solo si el listener mantiene estado entre reintentos (raro). |
130
+
131
+ ---
132
+
133
+ ## Dead Letter Queue (DLQ)
134
+
135
+ La DLQ es el componente más crítico que **no se configura en el YAML** sino en la definición de colas en Java. Sin ella, los mensajes que superan `max-attempts` se pierden silenciosamente.
136
+
137
+ eva4j genera automáticamente la infraestructura DLQ **por módulo** usando un `TopicExchange` dedicado con sufijo `.dlx`. Este patrón aísla las DLQ de cada módulo, evitando que un error de configuración afecte a otros bounded contexts.
138
+
139
+ ```java
140
+ // Generado automáticamente por eva4j en RabbitMQConfig del módulo consumidor
141
+
142
+ // Exchange principal del módulo productor
143
+ @Bean
144
+ public TopicExchange ordersExchange() {
145
+ return new TopicExchange("orders", true, false);
146
+ }
147
+
148
+ // DLX dedicada por módulo (sufijo .dlx)
149
+ @Bean
150
+ public TopicExchange ordersDlxExchange() {
151
+ return new TopicExchange("orders.dlx", true, false);
152
+ }
153
+
154
+ // Cola principal con redirección a DLX del módulo
155
+ @Bean
156
+ public Queue orderPlacedQueue() {
157
+ return QueueBuilder.durable("ORDER_PLACED")
158
+ .withArgument("x-dead-letter-exchange", "orders.dlx")
159
+ .build();
160
+ }
161
+
162
+ // Binding de la cola principal al exchange del productor
163
+ @Bean
164
+ public Binding orderPlacedBinding() {
165
+ return BindingBuilder
166
+ .bind(orderPlacedQueue())
167
+ .to(ordersExchange())
168
+ .with("order.placed");
169
+ }
170
+
171
+ // Cola DLQ (sufijo .dlq)
172
+ @Bean
173
+ public Queue orderPlacedDlq() {
174
+ return QueueBuilder.durable("ORDER_PLACED.dlq").build();
175
+ }
176
+
177
+ // Binding de la DLQ al DLX con la misma routing key
178
+ @Bean
179
+ public Binding orderPlacedDlqBinding() {
180
+ return BindingBuilder
181
+ .bind(orderPlacedDlq())
182
+ .to(ordersDlxExchange())
183
+ .with("order.placed");
184
+ }
185
+ ```
186
+
187
+ | Argumento de cola | Descripción |
188
+ |---|---|
189
+ | `x-dead-letter-exchange` | Exchange DLX **del módulo** al que se enrutan los mensajes rechazados definitivamente. |
190
+ | `x-message-ttl` | (Opcional) Tiempo de vida en ms de mensajes en la cola principal antes de expirar a la DLQ. |
191
+
192
+ ---
193
+
194
+ ## Diferencias por entorno
195
+
196
+ | Parámetro | local/dev | test | producción |
197
+ |---|---|---|---|
198
+ | `host` | `localhost` | `localhost` | `${RABBITMQ_HOST}` |
199
+ | `username/password` | `guest/guest` | `guest/guest` | `${RABBITMQ_USERNAME}` / `${RABBITMQ_PASSWORD}` |
200
+ | `ssl.enabled` | `false` | `false` | `true` |
201
+ | `publisher-confirm-type` | `none` | `none` | `correlated` |
202
+ | `concurrency` | `3` | `1` | `5` |
203
+ | `max-concurrency` | — | — | `20` |
204
+ | `prefetch` | `250` (default) | `1` | `10` |
205
+ | `retry.max-attempts` | `3` | `3` | `5` |
206
+ | `retry.multiplier` | — | — | `2.0` |
207
+
208
+ ---
209
+
210
+ ## Checklist de producción
211
+
212
+ - [ ] Credenciales en variables de entorno (sin valores por defecto expuestos)
213
+ - [ ] Usuario dedicado (no `guest`) creado en el broker con permisos mínimos necesarios
214
+ - [ ] SSL habilitado (`ssl.enabled: true`, puerto `5671`)
215
+ - [ ] `acknowledge-mode: manual` configurado
216
+ - [ ] Dead Letter Queue declarada para cada cola consumida
217
+ - [ ] `publisher-confirm-type: correlated` + `publisher-returns: true` + `template.mandatory: true`
218
+ - [ ] `prefetch` ajustado (recomendado `10`, no el default `250`)
219
+ - [ ] `retry.multiplier` configurado (backoff exponencial)
220
+ - [ ] `max-concurrency` definido para escalar bajo carga
221
+ - [ ] `connection-timeout` y `requested-heartbeat` configurados
222
+ - [ ] Monitoreo de DLQ con alertas activas
223
+
224
+ ---
225
+
226
+ **Versión:** 1.0
227
+ **Última actualización:** 2026-04-04