# Desarrollo de aplicaciones orientadas a eventos con Kafka y Docker


Con el auge de los microservicios, las arquitecturas orientadas a eventos se han vuelto cada vez más populares.
[Apache Kafka](https://kafka.apache.org/), una plataforma distribuida de transmisión de eventos, suele ser el
núcleo de estas arquitecturas. Desafortunadamente, configurar y desplegar tu propia instancia de Kafka para desarrollo
suele ser complicado. Por suerte, Docker y los contenedores lo hacen mucho más fácil.

En esta guía, aprenderás a:

1. Usar Docker para iniciar un clúster de Kafka
2. Conectar una aplicación no contenerizada al clúster
3. Conectar una aplicación contenerizada al clúster
4. Desplegar Kafka-UI para ayudar con la resolución de problemas y la depuración

## Requisitos previos

Se requieren los siguientes requisitos previos para seguir esta guía práctica:

- [Docker Desktop](https://www.docker.com/products/docker-desktop/)
- [Node.js](https://nodejs.org/en/download/package-manager) y [yarn](https://yarnpkg.com/)
- Conocimiento básico de Kafka y Docker

## Iniciar Kafka

A partir de [Kafka 3.3](https://www.confluent.io/blog/apache-kafka-3-3-0-new-features-and-updates/), el despliegue de Kafka se simplificó enormemente al no requerir Zookeeper gracias a KRaft (Kafka Raft). Con KRaft, configurar una instancia de Kafka para desarrollo local es mucho más sencillo. Con el lanzamiento de [Kafka 3.8](https://www.confluent.io/blog/introducing-apache-kafka-3-8/), ahora está disponible una nueva imagen de Docker [kafka-native](https://hub.docker.com/r/apache/kafka-native), que proporciona un inicio significativamente más rápido y un menor consumo de memoria.

> [!TIP]
>
> Esta guía utilizará la imagen apache/kafka, ya que incluye muchas secuencias de comandos (scripts) útiles para gestionar y trabajar con Kafka. Sin embargo, es posible que prefieras utilizar la imagen apache/kafka-native, ya que se inicia más rápido y requiere menos recursos.

### Iniciar Kafka

Inicia un clúster de Kafka básico siguiendo estos pasos. Este ejemplo iniciará un clúster exponiendo el puerto 9092 en el host para permitir que una aplicación que se ejecuta de forma nativa se conecte a él.

1. Inicia un contenedor de Kafka ejecutando el siguiente comando:

   ```console
   $ docker run -d --name=kafka -p 9092:9092 apache/kafka
   ```

2. Una vez descargada la imagen, tendrás una instancia de Kafka en funcionamiento en uno o dos segundos.

3. La imagen apache/kafka incluye varios scripts útiles en el directorio `/opt/kafka/bin`. Ejecuta el siguiente comando para verificar que el clúster está en funcionamiento y obtener su ID de clúster:

   ```console
   $ docker exec -ti kafka /opt/kafka/bin/kafka-cluster.sh cluster-id --bootstrap-server :9092
   ```

   Al hacerlo, se producirá una salida similar a la siguiente:

   ```plaintext
   Cluster ID: 5L6g3nShT-eMCtK--X86sw
   ```

4. Crea un tema (topic) de ejemplo y publica (produce) algunos mensajes ejecutando el siguiente comando:

   ```console
   $ docker exec -ti kafka /opt/kafka/bin/kafka-console-producer.sh --bootstrap-server :9092 --topic demo
   ```

   Después de ejecutarlo, puedes introducir un mensaje por línea. Por ejemplo, ingresa algunos mensajes, uno por línea. Algunos ejemplos podrían ser:

   ```plaintext
   First message
   ```

   Y

   ```plaintext
   Second message
   ```

   Presiona `enter` para enviar el último mensaje y luego presiona `ctrl`+`c` cuando termines. Los mensajes se publicarán en Kafka.

5. Confirma que los mensajes se publicaron en el clúster consumiendo los mensajes:

   ```console
   $ docker exec -ti kafka /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server :9092 --topic demo --from-beginning
   ```

   Deberías ver tus mensajes en la salida:

   ```plaintext
   First message
   Second message
   ```

   Si lo deseas, puedes abrir otra terminal, publicar más mensajes y ver cómo aparecen en el consumidor.

   Cuando termines, presiona `ctrl`+`c` para detener la lectura de mensajes.

Ya tienes un clúster de Kafka ejecutándose localmente y has validado que puedes conectarte a él.

## Conectarse a Kafka desde una aplicación no contenerizada

Ahora que has demostrado que puedes conectarte a la instancia de Kafka desde la línea de comandos, es hora de conectarte al clúster desde una aplicación. En este ejemplo, utilizarás un proyecto Node simple que usa la biblioteca [KafkaJS](https://github.com/tulios/kafkajs).

Dado que el clúster se ejecuta localmente y está expuesto en el puerto 9092, la aplicación puede conectarse al clúster en localhost:9092 (ya que se ejecuta de forma nativa y no en un contenedor en este momento). Una vez conectada, esta aplicación de ejemplo registrará los mensajes que consuma del tema `demo`. Además, cuando se ejecute en modo de desarrollo, también creará el tema si no lo encuentra.

1. Si no tienes el clúster de Kafka ejecutándose del paso anterior, ejecuta el siguiente comando para iniciar una instancia de Kafka:

   ```console
   $ docker run -d --name=kafka -p 9092:9092 apache/kafka
   ```

2. Clona el [repositorio de GitHub](https://github.com/dockersamples/kafka-development-node) localmente.

   ```console
   $ git clone https://github.com/dockersamples/kafka-development-node.git
   ```

3. Navega dentro del proyecto.

   ```console
   cd kafka-development-node/app
   ```

4. Instala las dependencias usando yarn.

   ```console
   $ yarn install
   ```

5. Inicia la aplicación usando `yarn dev`. Esto establecerá la variable de entorno `NODE_ENV` a `development` y usará `nodemon` para vigilar los cambios de archivos.

   ```console
   $ yarn dev
   ```

6. Con la aplicación en ejecución, registrará los mensajes recibidos en la consola. En una nueva terminal, publica algunos mensajes usando el siguiente comando:

   ```console
   $ docker exec -ti kafka /opt/kafka/bin/kafka-console-producer.sh --bootstrap-server :9092 --topic demo
   ```

   Y luego envía un mensaje al clúster:

   ```plaintext
   Test message
   ```

   Recuerda presionar `ctrl+c` cuando termines para dejar de producir mensajes.

## Conectarse a Kafka tanto desde contenedores como desde aplicaciones nativas

Ahora que tienes una aplicación que se conecta a Kafka a través de su puerto expuesto, es hora de explorar qué cambios se necesitan para conectarse a Kafka desde otro contenedor. Para hacerlo, ahora ejecutarás la aplicación desde un contenedor en lugar de hacerlo de forma nativa.

Pero antes de hacerlo, es importante comprender cómo funcionan los oyentes (listeners) de Kafka y cómo ayudan a los clientes a conectarse.

### Comprendiendo los oyentes (listeners) de Kafka

Cuando un cliente se conecta a un clúster de Kafka, en realidad se conecta a un "broker". Aunque los brokers tienen muchas funciones, una de ellas es admitir el equilibrio de carga de los clientes. Cuando un cliente se conecta, el broker devuelve un conjunto de URLs de conexión que el cliente debe usar para producir o consumir mensajes. ¿Cómo se configuran estas URLs de conexión?

Cada instancia de Kafka tiene un conjunto de listeners y advertised listeners. Los "listeners" son los puertos y direcciones a los que Kafka se enlaza, y los "advertised listeners" configuran cómo deben conectarse los clientes al clúster. La URL de conexión que recibe un cliente se basa en a qué listener se conecta.

### Definiendo los oyentes (listeners)

Para que esto tenga sentido, veamos cómo debe configurarse Kafka para admitir dos escenarios de conexión:

1. Conexiones del host (aquellas que pasan a través del puerto mapeado del host): estas deberán conectarse utilizando `localhost`.
2. Conexiones de Docker (aquellas que provienen de las redes de Docker): estas no pueden conectarse utilizando `localhost`, sino el alias de red (o dirección DNS) del servicio de Kafka.

Dado que existen dos métodos diferentes de conexión para los clientes, se requieren dos oyentes diferentes: `HOST` y `DOCKER`. El oyente `HOST` les indicará a los clientes que se conecten usando `localhost:9092`, mientras que el oyente `DOCKER` les informará que se conecten usando `kafka:9093`. Observa que esto significa que Kafka escucha tanto en el puerto 9092 como en el 9093. Sin embargo, solo el oyente del host necesita ser expuesto al host.

![Diagrama que muestra los oyentes DOCKER y HOST y cómo se exponen al host y a las redes de Docker](/guides/images/kafka-1.webp)

Para configurar esto, el archivo `compose.yaml` de Kafka necesita una configuración adicional. Una vez que comiences a sobrescribir algunos de los valores predeterminados, también deberás especificar algunas otras opciones para que funcione el modo KRaft.

```yaml
services:
  kafka:
    image: apache/kafka-native
    ports:
      - "9092:9092"
    environment:
      # Configura oyentes tanto para la comunicación de docker como del host
      KAFKA_LISTENERS: CONTROLLER://localhost:9091,HOST://0.0.0.0:9092,DOCKER://0.0.0.0:9093
      KAFKA_ADVERTISED_LISTENERS: HOST://localhost:9092,DOCKER://kafka:9093
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,DOCKER:PLAINTEXT,HOST:PLAINTEXT

      # Configuraciones requeridas para el modo KRaft
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9091

      # Oyente a utilizar para la comunicación entre brokers
      KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER

      # Requerido para un clúster de un solo nodo
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
```

Pruébalo siguiendo los pasos a continuación.

1. Si tienes la aplicación de Node ejecutándose del paso anterior, detenla presionando `ctrl`+`c` en la terminal.

2. Si tienes el clúster de Kafka ejecutándose del apartado anterior, detén ese contenedor con el siguiente comando:

   ```console
   $ docker rm -f kafka
   ```

3. Inicia el entorno de Compose ejecutando el siguiente comando en la raíz del directorio del proyecto clonado:

   ```console
   $ docker compose up
   ```

   Después de un momento, la aplicación estará en funcionamiento.

4. En el entorno de Compose hay otro servicio que se puede utilizar para publicar mensajes. Abre tu navegador en [http://localhost:3000](http://localhost:3000). A medida que escribas un mensaje y envíes el formulario, deberías ver el mensaje de registro de que la aplicación lo ha recibido.

   Esto ayuda a demostrar cómo un enfoque contenerizado facilita la adición de servicios adicionales para ayudar a probar y solucionar problemas en tu aplicación.

## Agregar visualización del clúster

Una vez que comiences a utilizar contenedores en tu entorno de desarrollo, te darás cuenta de la facilidad de agregar servicios adicionales enfocados únicamente en ayudar al desarrollo, como visualizadores y otros servicios auxiliares. Como ya tienes Kafka en ejecución, podría ser útil visualizar lo que ocurre en el clúster de Kafka. Para hacerlo, puedes ejecutar la aplicación web [Kafbat UI](https://github.com/kafbat/kafka-ui).

Para agregarla a tu propio proyecto (ya se encuentra en la aplicación de demostración), solo necesitas agregar la siguiente configuración a tu archivo Compose:

```yaml
services:
  kafka-ui:
    image: kafbat/kafka-ui:main
    ports:
      - 8080:8080
    environment:
      DYNAMIC_CONFIG_ENABLED: "true"
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9093
    depends_on:
      - kafka
```

Luego, una vez que se inicie el entorno de Compose, puedes abrir tu navegador en [http://localhost:8080](http://localhost:8080) y navegar para ver detalles adicionales sobre el clúster, verificar los consumidores, publicar mensajes de prueba y más.

## Pruebas con Kafka

Si estás interesado en aprender cómo puedes integrar Kafka fácilmente en tus pruebas de integración, consulta la guía [Pruebas de oyentes Kafka en Spring Boot usando Testcontainers](https://testcontainers.com/guides/testing-spring-boot-kafka-listener-using-testcontainers/). Esta guía te enseñará cómo usar Testcontainers para gestionar el ciclo de vida de los contenedores de Kafka en tus pruebas.

## Conclusión

Al usar Docker, puedes simplificar el proceso de desarrollo y prueba de aplicaciones orientadas a eventos con Kafka. Los contenedores simplifican el proceso de configuración y despliegue de los distintos servicios que necesitas para el desarrollo. Y una vez definidos en Compose, todos los miembros del equipo pueden beneficiarse de esta facilidad de uso.

En caso de que te lo hayas perdido antes, todo el código de la aplicación de ejemplo se puede encontrar en [dockersamples/kafka-development-node](https://github.com/dockersamples/kafka-development-node).

