¿Te has preguntado por qué algunos sistemas que usan patrones de Apache Kafka escalan bien mientras otros colapsan? La diferencia no está en la tecnología, sino en cómo la usas. Si estás diseñando arquitecturas basadas en eventos, presta atención a este artículo.


Contenidos
¿Qué son los patrones en Apache Kafka y por qué importan?
Cuando comencé a trabajar con Apache Kafka, lo primero que noté fue la falta de una «guía oficial» sobre cómo diseñar arquitecturas efectivas. Todos los recursos hablaban sobre productores, consumidores y topics, pero pocos en cómo conectar todo eso en sistemas reales. Ahí es donde los patrones entran en juego.
Los patrones de Kafka son formas probadas y repetidas de resolver problemas comunes en arquitecturas distribuidas orientadas a eventos. Implementarlos correctamente puede ser la diferencia entre un sistema estable, escalable y fácil de mantener, o uno lleno de cuellos de botella, redundancias innecesarias y comportamientos impredecibles.
1. Request–Reply en Kafka: Comunicación síncrona sobre un bus asíncrono
Uno de los primeros retos a los que te puedes enfrentar es a que dos microservicios se hablen en tiempo real, pero sin acoplarse directamente. La solución es implementar el patrón Request–Reply sobre Kafka, lo que al principio parece contradictorio porque Kafka es inherentemente asincrónico.
El truco está en:
- Generar un
correlationIdúnico en cada mensaje. - Publicar el mensaje en un topic de peticiones.
- Escuchar en un topic de respuestas que contenga ese
correlationId. - Agregar una expiración temporal en caso de no recibir respuesta.
Este patrón es ideal cuando necesitas confirmación de procesamiento, pero sin perder los beneficios del desacoplamiento. Eso sí, requiere diseñar una infraestructura robusta que soporte reintentos y errores parciales.
Aun asi, recuerda nunca usar un único topic para todas las respuestas para evitar saturar el sistema.
Productor (Request)
ProducerRecord<String, String> request = new ProducerRecord<>(
"request-topic", null, UUID.randomUUID().toString(),
"¿Cuál es el estado del pedido #123?",
HeadersBuilder.with("correlationId", "abc-123").with("replyTo", "reply-topic")
);
producer.send(request);
Consumidor + Replier
consumer.subscribe(Collections.singletonList("request-topic"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
String correlationId = record.headers().lastHeader("correlationId").value().toString();
String replyTo = record.headers().lastHeader("replyTo").value().toString();
String respuesta = "Pedido #123 está en reparto";
ProducerRecord<String, String> reply = new ProducerRecord<>(
replyTo, null, correlationId, respuesta,
HeadersBuilder.with("correlationId", correlationId)
);
producer.send(reply);
}
}
2. Event Chunk: dividir y recomponer batch eficientemente
Cuando trabajamos con eventos que contienen grandes volúmenes de datos (por ejemplo, cargas batch desde un ERP), el tamaño del mensaje puede superar los límites máximos permitidos por Kafka. Aquí entra el patrón Event Chunk.
Este patrón consiste en:
- Dividir el payload original en múltiples fragmentos más pequeños.
- Asignar un
chunkIdymessageIdpara agruparlos. - Reensamblar los chunks en el consumidor una vez recibidos todos.
Lo he aplicado en integraciones financieras donde se enviaban hasta 5,000 registros contables por evento. Fragmentar estos mensajes permite que los consumidores manejen los datos en paralelo. Este patrón también te obliga a mejorar la lógica de retry, ya que un solo chunk faltante puede retrasar todo el procesamiento.
Dividir un payload en fragmentos
String payload = largeJson;
int chunkSize = 900000; // menos de 1MB
List<String> chunks = new ArrayList<>();
for (int i = 0; i < payload.length(); i += chunkSize) {
chunks.add(payload.substring(i, Math.min(payload.length(), i + chunkSize)));
}
String eventId = UUID.randomUUID().toString();
for (int i = 0; i < chunks.size(); i++) {
ProducerRecord<String, String> chunkRecord = new ProducerRecord<>(
"chunked-events",
null,
eventId,
chunks.get(i),
HeadersBuilder
.with("chunkId", i + "")
.with("totalChunks", chunks.size() + "")
.with("eventId", eventId)
);
producer.send(chunkRecord);
}
4. Claim Check + Event Chunk: Escalabilidad y control
Una evolución del patrón anterior es combinarlo con el famoso Claim Check, donde solo se envía una referencia al contenido en Kafka, mientras que el contenido real se guarda en un almacenamiento externo (como Amazon S3 o un sistema de blobs).
Por ejemplo, puede ser útil en una solución e-commerce. Donde se almacenan los detalles de cada orden (incluyendo imágenes y adjuntos) fuera de Kafka, y en los eventos solo se incluye un claimCheckId. Esto reduce drásticamente el tamaño de los eventos y evita congestionar los topics.
Asegúrate aquí de que el sistema de almacenamiento externo tenga redundancia y latencia baja, porque cada consumidor necesitará acceder a él.
5. Arquitecturas dirigidas por eventos con Kafka
Adoptar event-driven architecture (EDA) con Kafka como backbone ha sido uno de los mayores cambios de paradigma que viví como arquitecto. Pasa de pensar en peticiones directas a diseñar flujos de eventos encadenados.
Por ejemplo, podemos tener lo siguiente:
- Servicio A emite
UsuarioRegistrado. - Servicio B escucha ese evento y envía
BienvenidaEnviada. - Servicio C escucha ambos y actualiza el CRM.
Diseñar este flujo requiere tener una coreografía de eventos y control de duplicados.
Recuerda que más eventos no siempre es mejor. Intenta modelar tu coreografía con la menor cantidad de eventos posible y de esta forma reducir el ruido y los errores de correlación.
6. Patrones de procesamiento con Kafka Streams: KStream, KTable y stateful processing
Aquí debes entender bien cuándo usar KStream vs KTable. Evitar errores típicos como intentar hacer joins con KStream donde se necesita un estado.
- Usa
KTablepara datos que se acumulan y se actualizan (como catálogos). - Usa
KStreampara flujos inmutables de eventos (como transacciones). - Usa stateful processing para operaciones de ventana y agregación.
Kafka Streams con KStream y KTable (Join)
StreamsBuilder builder = new StreamsBuilder();
KStream<String, OrderEvent> orders = builder.stream("orders", Consumed.with(Serdes.String(), orderSerde));
KTable<String, Customer> customers = builder.table("customers", Consumed.with(Serdes.String(), customerSerde));
KStream<String, EnrichedOrder> enriched = orders.join(
customers,
(order, customer) -> new EnrichedOrder(order, customer)
);
enriched.to("enriched-orders", Produced.with(Serdes.String(), enrichedOrderSerde));
8. Anti‑patrones comunes en Kafka: qué evitar
Kafka, si se usa mal, puede traer más problemas que soluciones. Aquí algunos anti‑patrones que he vivido en carne propia:
- Topics genéricos y sin control: evítalos. Un topic llamado “eventos” donde todo tipo de payload entra solo genera caos.
- Retry sin backoff: hacer reintentos infinitos en consumidores sin control puede saturar tus sistemas.
- Repartir eventos sin clave (key): esto rompe el orden y complica el procesamiento.
- Sobreingeniería en esquema de eventos: querer hacerlo “super genérico” lleva a eventos incomprensibles.
9. Buenas prácticas para trabajar con Kafka
Además de los patrones, hay una serie de buenas prácticas que han marcado la diferencia en todos mis proyectos con Kafka:
- Usa Avro + Schema Registry: para validar, versionar y evitar errores de compatibilidad.
- Diseña eventos idempotentes: que puedan ser procesados varias veces sin efectos secundarios.
- Distribuye con buenas claves: para que el particionamiento sea equilibrado.
- Monitoriza con herramientas como Prometheus y Grafana: entender el lag, throughput y errores en tiempo real es vital.
Configuración de idempotencia y acks en el productor Kafka:
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("acks", "all");
props.put("enable.idempotence", "true");
props.put("retries", Integer.MAX_VALUE);
props.put("max.in.flight.requests.per.connection", 5);
props.put("key.serializer", StringSerializer.class.getName());
props.put("value.serializer", StringSerializer.class.getName());
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
No dejes el manejo de errores como «algo que luego resolveremos». Un simple try/catch con logs no basta. Usa DLQs, alertas y dashboards desde el día 1.
Ddiseña con eventos, no con servicios. Kafka no es una base de datos, ni un broker de peticiones. Es el esqueleto de un sistema vivo que se comunica mediante hechos.




![Lee más sobre el artículo Mejores Cursos de Python [Actualizado]](https://aprenderbigdata.com/wp-content/uploads/Mejores-cursos-udemy-Python-300x169.jpg)
