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,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
|