Implementando un Outbox Transaccional con AsyncAPI, SpringModulith y ZenWaveSDK

Aprende a implementar un Outbox Transaccional con AsyncAPI, SpringModulith y ZenWaveSDK, garantizando la consistencia y externalización eficiente de eventos en arquitecturas distribuidas.
Ilustración futurista de una arquitectura basada en eventos, con nodos de datos interconectados, procesamiento de mensajes y computación en la nube. La imagen destaca el flujo seguro y eficiente de eventos en un sistema distribuido.

Índice

  1. Introducción
  2. Proyecto Playground de ejemplo
  3. Problema de trnasacciones distribuidas y el patrón Outbox
  4. Generación de Código ZenWaveSDK para AsyncAPI y Spring Cloud Streams
  5. Registro de Publicación de Eventos de Spring Modulith
  6. Usando Spring Cloud Stream para Externalizar Eventos de Spring Modulith
  7. Implementación Automática del Patrón Outbox Transaccional
  8. Conclusión
  9. Glosario
  10. Referencias bibliográficas

1. Introducción

En sistemas distribuidos, donde múltiples servicios operan sin una transacción compartida, garantizar la atomicidad y la consistencia es un desafío.

Las transacciones distribuidas con Two-Phase Commit (2PC) son complejas, costosas en rendimiento y, en muchos casos, inviables.

El Patrón Outbox Transaccional resuelve este problema almacenando eventos en una tabla "outbox" dentro de la misma transacción que actualiza la base de datos. Luego, estos eventos se envían a sistemas externos, como servicios de correo electrónico o brokers de mensajería, garantizando consistencia eventual sin necesidad de transacciones distribuidas.

En este artículo, aprenderás a implementar un Patrón Outbox Transaccional para:

  • Persistir datos en una base de datos transaccional (SQL o MongoDB).
  • Enviar eventos a un broker de mensajería externo como Kafka o RabbitMQ con Spring Cloud Stream.
  • Aprovechar las capacidades transaccionales de eventos en Spring Modulith.
  • Usar ZenWaveSDK para AsyncAPI, eliminando la escritura de código boilerplate para la integración del outbox y la publicación de eventos.
Diagrama de arquitectura de un Outbox Transaccional con AsyncAPI, ZenWaveSDK, SpringModulith, Spring Cloud Stream y Kafka. Muestra cómo los eventos son generados, almacenados y publicados a un broker de mensajería.
Flujo de procesamiento de eventos en un Outbox Transaccional utilizando AsyncAPI para el diseño, ZenWaveSDK para la generación de código, SpringModulith para el registro de eventos y Spring Cloud Stream para su publicación en Kafka.

2. Proyecto Playground de Ejemplo

Nada mejor que ver el software en acción. Para ello, utilizaremos un proyecto de prueba completamente funcional, diseñado para explorar e implementar el Patrón Outbox Transaccional en un entorno real.

Este es el proyecto que usaremos como entorno de pruebas: EDA-TransactionalOutbox-Modulith-JPA.

El proyecto proporciona una API sencilla para gestionar clientes, con endpoints REST para operaciones CRUD. Además, genera eventos que se publican en Kafka utilizando Avro como formato de datos.

Proyecto seleccionado:

👉 EDA-TransactionalOutbox-Modulith-JPA

Características principales:

  1. API REST para gestión de clientes → Proporciona endpoints CRUD para operaciones con clientes.
  2. Generación y publicación de eventos → Emite eventos en Kafka utilizando Avro como formato de datos.
  3. Implementación del Patrón Outbox → Basado en Spring Modulith y Spring Cloud Stream para garantizar consistencia transaccional.

Los componentes clave del proyecto son:

3. Problema de Transacciones Distribuidas y el Patrón Outbox

En arquitecturas distribuidas, garantizar la atomicidad y consistencia en sistemas que dependen de múltiples servicios y bases de datos representa un desafío importante.

Cuando una aplicación necesita persistir datos en una base de datos y, al mismo tiempo, publicar un evento en un sistema externo como Kafka o RabbitMQ, pueden ocurrir dos problemas críticos:

  1. El evento se publica antes de que la transacción se confirme → Si la operación en la base de datos falla y se revierte, el evento ya habrá sido enviado, generando una inconsistencia en los datos.
  2. El evento se publica después de la transacción → Si el servicio falla en ese momento, la base de datos tendrá los datos actualizados, pero el evento nunca se enviará, causando una pérdida de información.

3.1 Gestión de Eventos en la Entidad Customer y el Problema de la Atomicidad

Cuando una aplicación debe persistir datos en la base de datos y, al mismo tiempo, publicar un evento en un sistema de mensajería (Kafka, RabbitMQ, etc.), surge un problema de atomicidad:

  • Las operaciones en la base de datos son transaccionales, lo que significa que pueden confirmarse (commit) o revertirse (rollback).
  • La publicación de eventos en un broker de mensajes no forma parte de la transacción de la base de datos.
  • Esto puede generar inconsistencias si la aplicación falla en un momento crítico.

A continuación, se muestra un ejemplo de código que no utiliza el Patrón Outbox Transaccional, lo que puede generar inconsistencias:

Ejemplo de Código Sin Outbox Transaccional

@Service
public class CustomerService {
    // ...

    @Transactional
    public Customer createCustomer(Customer input) {
        log.debug("Request to save Customer: {}", input);

        var customer = mapper.update(new Customer(), input);
        customer = customerRepository.save(customer); // Persistir en la base de datos

        // 🚨 Problema: Si la transacción falla, el evento ya se envió
        sendCustomerEvent(customer); // Emitir evento manualmente sin outbox transaccional

        return customer;
    }

    private void sendCustomerEvent(Customer customer) {
        var customerEvent = eventsMapper.asCustomerEvent(customer);
        messageBroker.send(customerEvent); // Publicación sin garantías transaccionales
    }
}

En este código, si el evento customerEvent se envía antes de que la transacción se confirme, y la base de datos falla, el evento habrá sido publicado sin respaldo en la base de datos.

3.2 Escenarios clave que destacan el problema

  1. El evento puede enviarse antes de que la transacción se confirme

    • Si messageBroker.send(customerEvent) se ejecuta antes de finalizar la transacción y luego ocurre un rollback, el evento ya habrá sido publicado, pero la base de datos no tendrá el registro del cliente.
  2. Si la transacción en la base de datos falla, el evento aún se publica

    • Esto genera inconsistencia, ya que el evento llega a otros servicios sin que el dato realmente exista en la base de datos.
  3. No hay un mecanismo de recuperación automática

    • Si la aplicación falla en medio del proceso, los eventos podrían perderse o duplicarse.

3.3 Solución con el Patrón Outbox Transaccional

El Patrón Outbox Transaccional resuelve este problema al almacenar los eventos en una tabla intermedia ("outbox"), dentro de la misma transacción que la actualización de la base de datos.

Luego, un proceso externo lee esta tabla y publica los eventos en el sistema de mensajería, asegurando:

Atomicidad → Si la transacción se revierte, el evento también se descarta.
Fiabilidad → Los eventos solo se publican cuando la transacción es exitosa.
Entrega garantizada → En caso de fallo del servicio, los eventos siguen pendientes en la tabla "outbox" y pueden reenviarse.

Diagrama UML

📌 El siguiente diagrama representa la relación entre la entidad Customer, el servicio CustomerService y el evento CustomerEvent, en un sistema basado en eventos y principios de Domain-Driven Design (DDD).*

Diagrama UML que representa la relación entre la entidad Customer, el servicio CustomerService y el evento CustomerEvent en un sistema basado en DDD y eventos.
Diagrama UML de la entidad Customer, el servicio CustomerService y el evento CustomerEvent. Muestra la relación entre los elementos en un sistema basado en eventos y el patrón Domain-Driven Design (DDD).

4. Generación de Código ZenWaveSDK para AsyncAPI y Spring Cloud Streams

ZenWaveSDK genera, a partir de un archivo de definición AsyncAPI, todo el código boilerplate necesario para el envío y recepción de eventos con Spring Cloud Stream.

A partir de la definición AsyncAPI, ZenWaveSDK genera:

  • DTOs/Modelos para los payloads de tus eventos.
  • Objetos de Cabecera Tipados para garantizar consistencia.
  • Interfaces Java con nombres explícitos, basados en tus operationIds.
  • Una implementación ligera con Spring Cloud Stream, que puede ser @Autowire directamente en tus servicios.

Luego, puedes configurar Spring Cloud Stream para enviar y recibir mensajes usando cualquiera de los binders soportados, como Kafka o RabbitMQ. Con ZenWaveSDK, no necesitas escribir código boilerplate, lo que te permite enfocarte completamente en tu lógica de negocio.

4.1. Configuración del Plugin Maven de ZenWaveSDK

Este fragmento de código muestra la configuración del plugin Maven de ZenWaveSDK, utilizado para generar código basado en AsyncAPI de manera automatizada.

El plugin procesa la definición de AsyncAPI (asyncapi.yml) y genera código para Spring Cloud Stream, facilitando la implementación de eventos en arquitecturas reactivas o transaccionales.

En este caso, la configuración:

  • Habilita la generación de código para un proveedor de eventos (role=provider).
  • Utiliza el patrón Outbox Transaccional con Spring Modulith (transactionalOutbox=modulith).
  • Genera APIs de productor y consumidor con paquetes específicos (producerApiPackage, consumerApiPackage).

Esta configuración permite a los desarrolladores publicar y consumir eventos sin necesidad de escribir código repetitivo (boilerplate), optimizando la integración con sistemas de mensajería como Kafka o RabbitMQ.

Ejemplo de configuración en pom.xml

<plugin>
    <!-- Configuración del plugin ZenWave SDK para Maven -->
    <groupId>io.github.zenwave360.zenwave-sdk</groupId>
    <artifactId>zenwave-sdk-maven-plugin</artifactId>
    <version>${zenwave.version}</version>

    <configuration>
        <!-- Especificación de entrada (archivo AsyncAPI) -->
        <inputSpec>${project.basedir}/src/main/resources/public/apis/asyncapi.yml</inputSpec>

        <!-- Agregar las fuentes generadas al classpath de compilación -->
        <addCompileSourceRoot>true</addCompileSourceRoot>
        <addTestCompileSourceRoot>true</addTestCompileSourceRoot>
    </configuration>

    <executions>
        <!--
            Generación de DTOs deshabilitada porque se usa Avro 
            para definir los modelos de datos de eventos.
        -->

        <!-- Configuración para generar código basado en AsyncAPI -->
        <execution>
            <id>generate-asyncapi</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <!-- Especifica el generador a utilizar -->
                <generatorName>spring-cloud-streams3</generatorName>
                <configOptions>
                    <!-- Define el rol del código generado como proveedor de eventos -->
                    <role>provider</role>

                    <!-- Estilo de implementación: imperativo -->
                    <style>imperative</style>

                    <!-- Usa una implementación de Outbox Transaccional basada en Spring Modulith -->
                    <transactionalOutbox>modulith</transactionalOutbox>

                    <!-- Paquetes de modelos y APIs generados -->
                    <modelPackage>${asyncApiModelPackage}</modelPackage>
                    <producerApiPackage>${asyncApiProducerApiPackage}</producerApiPackage>
                    <consumerApiPackage>${asyncApiConsumerApiPackage}</consumerApiPackage>
                </configOptions>
            </configuration>
        </execution>
    </executions>

    <dependencies>
        <!-- Dependencias necesarias (definidas en otro lugar) -->
        <!-- Ejemplo: Dependencias del SDK y de Spring Cloud Stream -->
    </dependencies>
</plugin>

4.2. Configuración del Plugin Maven de Avro

Este fragmento de código configura el plugin Maven de Avro para generar automáticamente clases Java a partir de esquemas Avro (.avsc).

El plugin procesa archivos Avro ubicados en el directorio src/main/resources/public/apis/avro y genera las clases correspondientes en target/generated-sources/avro.

Además, se importan archivos Avro adicionales (PaymentMethodType.avsc, PaymentMethod.avsc, Address.avsc), lo que permite modelar estructuras de datos reutilizables en la aplicación.

Gracias a esta configuración, los desarrolladores pueden trabajar con objetos Java fuertemente tipados en lugar de manipular datos en formato Avro manualmente, facilitando la integración con Apache Kafka, sistemas de mensajería o almacenamiento de eventos.

<plugin>
    <!-- Configuración del plugin Maven para Avro -->
    <groupId>org.apache.avro</groupId>
    <artifactId>avro-maven-plugin</artifactId>
    <version>1.11.1</version>

    <executions>
        <execution>
            <goals>
                <!-- Generación de clases Java a partir de esquemas Avro -->
                <goal>schema</goal>
            </goals>
            <!-- Fase en la que se ejecutará la generación de código -->
            <phase>generate-sources</phase>
        </execution>
    </executions>

    <configuration>
        <!-- Directorio donde se encuentran los archivos Avro de entrada -->
        <sourceDirectory>${project.basedir}/src/main/resources/public/apis/avro</sourceDirectory>

        <!-- Directorio donde se generarán los archivos Java resultantes -->
        <outputDirectory>${project.basedir}/target/generated-sources/avro</outputDirectory>

        <!-- Lista de archivos Avro adicionales que deben ser importados -->
        <imports>
            <import>${project.basedir}/src/main/resources/public/apis/avro/PaymentMethodType.avsc</import>
            <import>${project.basedir}/src/main/resources/public/apis/avro/PaymentMethod.avsc</import>
            <import>${project.basedir}/src/main/resources/public/apis/avro/Address.avsc</import>
        </imports>
    </configuration>
</plugin>

4.3. Generación automática de código con Maven y Avro

Para ello, configuramos el zenwave-sdk-maven-plugin y, en este caso, también el avro-maven-plugin en el archivo pom.xml. Esta configuración garantiza que el código necesario se genere automáticamente en la carpeta target/generated-sources como parte del proceso de construcción.

Dado que esta configuración se ejecuta de forma automática durante la compilación, cada vez que actualices tu archivo de definición AsyncAPI, el código generado se mantendrá siempre sincronizado con la definición de tu API.

Captura de pantalla del entorno de desarrollo IntelliJ IDEA mostrando la implementación de CustomerServiceImpl.java. Se destaca la inyección de ICustomerEventsProducer en el servicio y su ubicación en generated-sources, dentro del paquete io.zenwave360.example.core.outbound.events.
Diagrama que muestra la relación entre CustomerServiceImpl y ICustomerEventsProducer. La flecha roja indica la ubicación del código generado dentro de target/generated-sources/zenwave en el proyecto.

Además, al configurar la opción transactionalOutbox como modulith, ZenWaveSDK generará automáticamente el código necesario para integrar el Registro de Publicación de Eventos de Spring Modulith, permitiendo gestionar el outbox transaccional de forma automática.

Captura de pantalla del entorno de desarrollo IntelliJ IDEA mostrando la configuración del pom.xml y el código generado CustomerEventsProducer.java. Se resalta la relación entre la configuración del plugin Maven de ZenWaveSDK y la generación de código en generated-sources.
Diagrama que muestra cómo la configuración de transactionalOutbox=modulith en pom.xml genera automáticamente la clase CustomerEventsProducer.java, que se encarga de publicar eventos mediante applicationEventPublisher.

5. Registro de Publicación de Eventos de Spring Modulith

Al habilitar la Externalización de Eventos y configurar los objetos Message<?> para su externalización, Spring Modulith almacena estos eventos de manera transaccional en el Registro de Publicación de Eventos antes de enviarlos a un sistema de mensajería externo.

Esto permite garantizar la consistencia eventual y la confiabilidad en la entrega de eventos, evitando la pérdida de información en arquitecturas distribuidas.

5.1. Configuración en application.yml para habilitar la externalización de eventos

Añade la siguiente configuración en el archivo application.yml para activar el almacenamiento transaccional de eventos en la base de datos:

spring:
  modulith.events.externalization.enabled: true
  modulith.events.jdbc.schema-initialization.enabled: true
  modulith.events.republish-outstanding-events-on-restart: true
  • externalization.enabled: true → Habilita la externalización de eventos en Spring Modulith.
  • jdbc.schema-initialization.enabled: true → Inicializa automáticamente el esquema de base de datos para almacenar eventos.
  • republish-outstanding-events-on-restart: true → Reenvía eventos pendientes en caso de reinicio del servicio.

5.2. Consulta de eventos almacenados en la base de datos

La siguiente captura de pantalla muestra una consulta SQL en la tabla event_publication, utilizada por Spring Modulith para almacenar eventos de manera transaccional antes de ser publicados en un broker de mensajería como Kafka o RabbitMQ.

Captura de pantalla de una consulta SQL en una base de datos que muestra la tabla event_publication, donde se almacenan eventos externalizados con Spring Modulith. Se observa un evento JSON con metadatos y payload, incluyendo direcciones y métodos de pago.
Consulta en una base de datos que muestra la tabla event_publication, utilizada por Spring Modulith para almacenar eventos externalizados de manera transaccional antes de ser enviados a un broker de mensajería.

Se puede observar un evento en formato JSON, que incluye:

  • Metadatos del evento (headers).
  • Payload del evento, con información de direcciones y métodos de pago.

Esta estructura permite que los eventos sean persistentes y confiables, garantizando su procesamiento en sistemas externos sin riesgo de pérdida.

5.3 Beneficios de esta configuración

Implementar el Registro de Publicación de Eventos de Spring Modulith ofrece ventajas clave en arquitecturas basadas en eventos, asegurando un procesamiento confiable y eficiente.

1. Seguridad y consistencia transaccional

  • Los eventos se almacenan en la base de datos de forma transaccional antes de enviarse a un sistema externo.
  • Se evitan inconsistencias entre la base de datos y el broker de mensajería.

2. Recuperación automática de eventos

  • Los eventos pendientes se republican automáticamente en caso de reinicio de la aplicación.
  • Evita la pérdida de eventos debido a fallos en el sistema o interrupciones de red.

3. Integración sin esfuerzo con sistemas de mensajería

  • Spring Modulith gestiona la externalización de eventos automáticamente, sin necesidad de código manual.
  • Compatible con Apache Kafka, RabbitMQ y otros sistemas de mensajería, asegurando una integración fluida.

4. Escalabilidad y optimización del rendimiento

  • Mejora la eficiencia del procesamiento de eventos en arquitecturas distribuidas.
  • Reduce la carga en la base de datos al manejar eventos de forma asíncrona y optimizada.

6. Usando Spring Cloud Stream para Externalizar Eventos de Spring Modulith

Una vez que Spring Modulith gestiona nuestros eventos de tipo Message<?>, el siguiente paso es configurar un externalizador de eventos para enviarlos a un sistema de mensajería externo.

Si los eventos se publicaran en formato JSON, podríamos utilizar spring-modulith-events-kafka para enviarlos a Kafka. Sin embargo, dado que en este caso queremos externalizar objetos Message<?> con payloads en Avro, utilizaremos:

👉 io.zenwave360.sdk:spring-modulith-events-scs, que admite la serialización y deserialización de Message<?>, con o sin payloads en Avro.

📌 Para más detalles, consulta la documentación de ZenWave360 Spring Modulith Events para Spring Cloud Stream.

6.1. Configuración en pom.xml

Para habilitar esta funcionalidad, añade las siguientes dependencias en tu archivo pom.xml:

<!-- Spring Cloud Stream Externalization para Message<?> -->
<dependency>
    <groupId>io.zenwave360.sdk</groupId>
    <artifactId>spring-modulith-events-scs</artifactId>
    <version>${spring-modulith-events-scs.version}</version>
</dependency>

<!-- Necesario para serializar payloads Avro a JSON en almacenamiento DB -->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-avro</artifactId>
</dependency>

¿Qué hace esta configuración?

  1. Habilita la externalización automática de eventos en Spring Modulith mediante Spring Cloud Stream.
  2. Permite la serialización de eventos en formato Avro sin perder compatibilidad con sistemas que manejan JSON.
  3. Optimiza la integración con Kafka, RabbitMQ y otros sistemas de mensajería, facilitando la transmisión de eventos.

6.2. Habilitación de la Externalización de Eventos en Spring Boot

Para activar la externalización automática de eventos en Spring Boot, añade la anotación @EnableSpringCloudStreamEventExternalization en la configuración de tu aplicación.

Ejemplo de configuración en Spring Boot

import io.zenwave360.modulith.events.scs.config.EnableSpringCloudStreamEventExternalization;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableSpringCloudStreamEventExternalization
class ExternalizationConfiguration {
}

¿Qué hace esta configuración?

  1. Habilita la externalización de eventos en Spring Modulith, permitiendo su publicación en sistemas de mensajería como Kafka o RabbitMQ.
  2. Enruta automáticamente los objetos Message<?> al binding correcto de Spring Cloud Stream, sin necesidad de configuración manual adicional.
  3. Facilita la integración con arquitecturas basadas en eventos, asegurando la transmisión de eventos de manera eficiente y escalable.

6.3. Configuración interna de Spring para la Externalización de Eventos

Cuando se añade la anotación @EnableSpringCloudStreamEventExternalization, Spring Boot configura automáticamente la externalización de eventos mediante la clase MessageExternalizationConfiguration.

Este mecanismo permite detectar y enrutar eventos Message<?> sin necesidad de configuración manual adicional, asegurando su correcta publicación en Kafka, RabbitMQ u otros sistemas de mensajería.

Código interno de configuración en Spring Boot

@AutoConfiguration
@AutoConfigureAfter(EventExternalizationAutoConfiguration.class)
@ConditionalOnProperty(
        name = "spring.modulith.events.externalization.enabled",
        havingValue = "true",
        matchIfMissing = true
)
public class MessageExternalizationConfiguration {

    @Bean
    EventExternalizationConfiguration eventExternalizationConfiguration() {
        return EventExternalizationConfiguration.externalizing()
            .select(event -> annotatedAsExternalized().test(event)
                    || event instanceof Message<?> && getTarget(event) != null)
            .route(Message.class, event -> RoutingTarget.forTarget(getTarget(event)).withoutKey())
            .build();
    }

    private String getTarget(Object event) {
        if (event instanceof Message<?> message) {
            return message.getHeaders()
                .get(SpringCloudStreamEventExternalizer.SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER, String.class);
        }
        return null;
    }
}

¿Qué hace este código?

  1. Habilita la externalización de eventos en Spring Modulith cuando la propiedad spring.modulith.events.externalization.enabled está activada.
  2. Detecta eventos marcados como externalizables (Message<?>) y los enruta automáticamente a su destino en Spring Cloud Stream.
  3. Extrae la cabecera SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER para determinar a qué sistema de mensajería debe enviarse el evento.
  4. Permite la publicación automática de eventos en Kafka, RabbitMQ o cualquier otro binder compatible con Spring Cloud Stream.

7. Implementación Automática del Patrón Outbox Transaccional

A pesar de sus beneficios, implementar y gestionar manualmente la tabla "outbox" puede ser complejo. Se debe asegurar que los eventos:

  1. Se publiquen en el orden correcto
  2. Se envíen una sola vez
  3. Sean consumidos sin duplicados

Gracias a la combinación de Spring Modulith, Spring Cloud Stream y el Generador de Código AsyncAPI de ZenWaveSDK, la aplicación implementa automáticamente el Patrón Outbox Transaccional.

Esta integración permite:

  1. Persistir eventos de manera transaccional en la base de datos antes de enviarlos a un sistema externo.
  2. Externalizar eventos automáticamente sin necesidad de código adicional.
  3. Garantizar la consistencia eventual en arquitecturas distribuidas, asegurando la entrega confiable de eventos.

Afortunadamente, herramientas como:
🔹 Registro de Publicación de Eventos de Spring Modulith
🔹 Generador de Código ZenWaveSDK para AsyncAPI

Simplifican la implementación del patrón Outbox Transaccional, permitiéndote enfocarte en la lógica de negocio sin preocuparte por la infraestructura de mensajería.

7.1 Ejemplo de Implementación en CustomerService

El siguiente código muestra cómo un servicio puede persistir un cliente y emitir un evento de manera transaccional mediante el Patrón Outbox, garantizando que los eventos solo se publiquen si la transacción se confirma exitosamente.

@Service
public class CustomerService {
    private final CustomerRepository customerRepository;
    private final EventsMapper eventsMapper;
    private final CustomerEventsProducer eventsProducer;

    public CustomerService(CustomerRepository customerRepository, 
                           EventsMapper eventsMapper, 
                           CustomerEventsProducer eventsProducer) {
        this.customerRepository = customerRepository;
        this.eventsMapper = eventsMapper;
        this.eventsProducer = eventsProducer;
    }

    @Transactional
    public Customer createCustomer(Customer input) {
        log.debug("Request to save Customer: {}", input);

        var customer = mapper.update(new Customer(), input);
        customer = customerRepository.save(customer); // Persistir en la base de datos

        // ✅ Integración automática con Outbox Transaccional
        var customerEvent = eventsMapper.asCustomerEvent(customer);
        eventsProducer.onCustomerEvent(customerEvent); // Publicación gestionada por Spring Modulith

        return customer;
    }
}

¿Qué hace este código?

  1. Garantiza atomicidad

    • El evento solo se publica si la transacción de la base de datos se confirma exitosamente.
    • Si ocurre un fallo en la persistencia del cliente, el evento no se enviará, evitando inconsistencias.
  2. Automatiza la publicación de eventos

    • El evento se almacena en la tabla event_publication de Spring Modulith.
    • Un proceso de publicación en segundo plano lo envía automáticamente a Kafka, RabbitMQ u otro sistema de mensajería.
  3. Elimina la necesidad de código manual para manejar eventos

    • No es necesario preocuparse por reintentos manuales ni problemas de duplicación de eventos.
    • La publicación es gestionada de manera segura y transaccional por Spring Modulith.

8. Conclusión

La implementación del Patrón Outbox Transaccional con herramientas como Spring Modulith y Spring Cloud Stream garantiza la consistencia y confiabilidad en el procesamiento de eventos dentro de arquitecturas distribuidas.

Este enfoque permite:
Mantener la atomicidad entre la persistencia de datos y la publicación de eventos.
Evitar la pérdida o duplicación de eventos, incluso en escenarios de fallos del sistema.
Simplificar la integración con brokers de mensajería como Kafka y RabbitMQ.

Además, gracias a herramientas como ZenWaveSDK, la automatización del código basado en AsyncAPI reduce la necesidad de escribir código repetitivo (boilerplate), facilitando la implementación de Spring Modulith y la externalización de eventos con Spring Cloud Stream.

El resultado es un sistema más confiable, escalable y fácil de mantener, donde los desarrolladores pueden enfocarse en la lógica de negocio en lugar de preocuparse por la gestión de eventos y transacciones distribuidas.

9. Glosario

AsyncAPI
Especificación diseñada para documentar y definir APIs asíncronas, similar a OpenAPI pero enfocada en eventos y mensajería en arquitecturas basadas en eventos.
Avro
Formato de serialización de datos desarrollado por Apache que permite almacenar y transmitir datos estructurados de manera eficiente, comúnmente usado en Apache Kafka.
Binder
Componente de Spring Cloud Stream que permite conectar aplicaciones con sistemas de mensajería como Kafka o RabbitMQ mediante una abstracción.
Outbox Transaccional
Patrón de diseño que garantiza la consistencia eventual en arquitecturas distribuidas almacenando eventos en una tabla de base de datos antes de enviarlos a un sistema externo.
Spring Cloud Stream
Framework de Spring que facilita la integración con sistemas de mensajería, proporcionando una abstracción sobre brokers como Kafka y RabbitMQ.
Spring Modulith
Extensión de Spring Boot que ayuda a estructurar aplicaciones monolíticas de manera modular, facilitando la gestión de eventos internos y su externalización.
ZenWaveSDK
Generador de código que facilita la implementación de APIs basadas en AsyncAPI, eliminando la necesidad de escribir código repetitivo para la integración con sistemas de mensajería.
Registro de Publicación de Eventos
Componente de Spring Modulith que almacena eventos de manera transaccional antes de ser externalizados a un sistema de mensajería.
Externalización de Eventos
Proceso mediante el cual eventos generados en una aplicación se envían a sistemas externos como Kafka o RabbitMQ para garantizar comunicación entre servicios.

10. Referencias bibliográficas

  1. Richardson, C., & Smith, F. (2021). Microservices Patterns: With Examples in Java. Manning Publications.

    • Aborda patrones de microservicios, incluyendo el Patrón Outbox, externalización de eventos y mensajería con Kafka y RabbitMQ.
  2. Pardon, J. (2022). Spring Boot Modularity: Developing Modular Monoliths with Spring Boot and Spring Modulith. Apress.

    • Explica cómo estructurar aplicaciones monolíticas modulares con Spring Modulith, incluyendo la gestión de eventos y la externalización.
  3. Humble, J., & Farley, D. (2010). Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation. Addison-Wesley.

    • Describe estrategias para automatizar pipelines de entrega y cómo los patrones como el Outbox Transaccional facilitan la consistencia en arquitecturas distribuidas.
  4. Garg, N. (2021). Designing Event-Driven Systems: Concepts and Patterns for Streaming Services with Apache Kafka. O’Reilly Media.

    • Profundiza en el diseño de sistemas basados en eventos, con una fuerte cobertura de Kafka, Avro y Spring Cloud Stream.
  5. Newman, S. (2021). Building Microservices: Designing Fine-Grained Systems (2nd Edition). O’Reilly Media.

    • Explica cómo usar Spring Cloud Stream y AsyncAPI para definir y gestionar eventos en arquitecturas distribuidas.
  6. Di Francesco, M. (2018). Enterprise Integration Patterns with Spring Integration: Transport, Messaging, and Event-Driven Architecture. Packt Publishing.

    • Cubre conceptos clave de integración basada en eventos, Spring Cloud Stream y externalización de eventos con Spring.

Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

He leído y acepto la política de privacidad

Información básica acerca de la protección de datos

  • Responsable: IZERTIS S.A.
  • Finalidad: Envío información de carácter administrativa, técnica, organizativa y/o comercial sobre los productos y servicios sobre los que se nos consulta.
  • Legitimación: Consentimiento del interesado
  • Destinatarios: Otras empresas del Grupo IZERTIS. Encargados del tratamiento.
  • Derechos: Acceso, rectificación, supresión, cancelación, limitación y portabilidad de los datos.
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad

Iván García Sáinz es un Arquitecto de Software con amplia experiencia en tecnologías como Java, Spring Boot y Spring Cloud. Especializado en Domain-Driven Design (DDD) y arquitecturas dirigidas por eventos, Iván se dedica a crear soluciones de software que destacan por su claridad y eficiencia. Además de su labor profesional, Iván forma parte del Comité Técnico de Dirección de la especificación AsyncAPI, contribuyendo activamente al desarrollo y promoción de estándares para APIs asíncronas. Es el fundador de ZenWave 360º, una plataforma orientada a facilitar la adopción de enfoques API-First mediante herramientas innovadoras como el ZenWave SDK.

¿Quieres publicar en Adictos al trabajo?

Te puede interesar

Tutoriales
Evita errores y pérdida de alineación en tus APIs asíncronas. Descubre cómo AsyncAPI y ZenWave SDK pueden ayudarte a prevenir el API Drift mediante generación de código automatizada y buenas prácticas DevOps.
Tutoriales
La segmentación predictiva en tiempo real optimiza la publicidad digital con IA y datos masivos, mejorando conversión y eficiencia en la inversión publicitaria.