<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Spring Builders: Ivan Garcia Sainz-Aja</title>
    <description>The latest articles on Spring Builders by Ivan Garcia Sainz-Aja (@ivangsa).</description>
    <link>https://springbuilders.dev/ivangsa</link>
    <image>
      <url>https://springbuilders.dev/images/Sxb-HGopmSBeJFm1m-6AktyAZIz5W8v8VyQ08Ex_JOg/rs:fill:90:90/g:sm/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy91/c2VyL3Byb2ZpbGVf/aW1hZ2UvNzQvMWFk/M2Q3ZDAtNzAyZi00/ODczLTg5ZWQtMWJi/MzQ5YTU3YmRmLmpw/Zw</url>
      <title>Spring Builders: Ivan Garcia Sainz-Aja</title>
      <link>https://springbuilders.dev/ivangsa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://springbuilders.dev/feed/ivangsa"/>
    <language>en</language>
    <item>
      <title>Externalize Spring-Modulith Events with Spring Cloud Stream</title>
      <dc:creator>Ivan Garcia Sainz-Aja</dc:creator>
      <pubDate>Sat, 08 Mar 2025 14:47:07 +0000</pubDate>
      <link>https://springbuilders.dev/ivangsa/externalize-spring-modulith-events-with-spring-cloud-stream-2jlc</link>
      <guid>https://springbuilders.dev/ivangsa/externalize-spring-modulith-events-with-spring-cloud-stream-2jlc</guid>
      <description>&lt;p&gt;Spring Modulith enables developers to build well-structured modular monoliths with built-in event capabilities. This allows teams to leverage Event-Driven Architecture patterns without immediately committing to a distributed system.&lt;/p&gt;

&lt;p&gt;It provides multiple &lt;a href="https://docs.spring.io/spring-modulith/reference/events.html#externalization.infrastructure"&gt;event externalizers&lt;/a&gt; out-of-the-box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kafka&lt;/strong&gt;: &lt;code&gt;spring-modulith-events-kafka&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AMQP&lt;/strong&gt;: &lt;code&gt;spring-modulith-events-amqp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JMS&lt;/strong&gt;: &lt;code&gt;spring-modulith-events-jms&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS SQS&lt;/strong&gt;: &lt;code&gt;spring-modulith-events-aws-sqs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS SNS&lt;/strong&gt;: &lt;code&gt;spring-modulith-events-aws-sns&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spring Messaging&lt;/strong&gt;: &lt;code&gt;spring-modulith-events-messaging&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While these built-in externalizers cover common use cases, there are scenarios where you need more flexibility and control.&lt;/p&gt;

&lt;p&gt;I'm happy to introduce a new library featuring a new Spring Modulith event externalizer for Spring Cloud Stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a New Library?
&lt;/h2&gt;

&lt;p&gt;This library addresses several key needs by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Leverage Spring Cloud Stream to support multiple message brokers at once, even inside the same application.&lt;/li&gt;
&lt;li&gt;Providing enhanced control over message headers and metadata.&lt;/li&gt;
&lt;li&gt;Supporting flexible payload serialization with both JSON and Avro.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/ZenWave360/spring-modulith-events-spring-cloud-stream"&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Using this library is straightforward. Here's what you need to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the Spring-Modulith Events Externalizer dependency to your project&lt;/li&gt;
&lt;li&gt;Include your preferred Spring Cloud Stream binder (Kafka, RabbitMQ, etc.)&lt;/li&gt;
&lt;li&gt;Configure Spring Cloud Stream bindings in application.yml&lt;/li&gt;
&lt;li&gt;Enable externalization with &lt;code&gt;@EnableSpringCloudStreamEventExternalization&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;ApplicationEventPublisher&lt;/code&gt; as normal, to publish POJOs, Avro or &lt;code&gt;Message&amp;lt;?&amp;gt;&lt;/code&gt; events&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. Add Core Dependency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.zenwave360.sdk&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-modulith-events-scs&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0-RC1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add Spring Cloud Stream Message Broker Binder
&lt;/h3&gt;

&lt;p&gt;Choose your preferred message broker. For Kafka:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.cloud&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-cloud-stream-binder-kafka&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Configure Bindings
&lt;/h3&gt;

&lt;p&gt;Configure your output bindings in &lt;code&gt;application.yml&lt;/code&gt;. We are going to configure two output bindings for different payload types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cloud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;bindings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# JSON events binding&lt;/span&gt;
        &lt;span class="na"&gt;customers-json-out-0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customers-json-topic&lt;/span&gt;
        &lt;span class="c1"&gt;# Avro events binding&lt;/span&gt;
        &lt;span class="na"&gt;customers-avro-out-0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customers-avro-topic&lt;/span&gt;
          &lt;span class="na"&gt;content-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/*+avro&lt;/span&gt;
  &lt;span class="c1"&gt;# Kafka-specific configuration&lt;/span&gt;
  &lt;span class="na"&gt;kafka&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;producer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;key-serializer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;org.apache.kafka.common.serialization.StringSerializer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A key advantage of using Spring Cloud Stream is the ability to configure multiple message brokers simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use different brokers (Kafka, RabbitMQ, etc.) in the same application&lt;/li&gt;
&lt;li&gt;Route different events to different brokers through configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Basic Configuration
&lt;/h2&gt;

&lt;p&gt;Enable Spring Cloud Stream externalization by adding the &lt;code&gt;@EnableSpringCloudStreamEventExternalization&lt;/code&gt; annotation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableSpringCloudStreamEventExternalization&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EventsConfiguration&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sending Events
&lt;/h2&gt;

&lt;h3&gt;
  
  
  POJO Events
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;ApplicationEventPublisher&lt;/code&gt; as you normally would in Spring Modulith:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Externalized&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"customers-json-out-0::#{#this.getId()}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// binding name and routing key&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerEvent&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Your POJO implementation&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerEventsProducer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ApplicationEventPublisher&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CustomerEventsProducer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApplicationEventPublisher&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publisher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;publishCustomerEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CustomerEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publishEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Sending the event&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Avro Events
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Add the Avro dependency:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.fasterxml.jackson.dataformat&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jackson-dataformat-avro&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Define your Avro event:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Externalized&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"customers-avro-out-0::#{#this.getId()}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// binding name and routing key&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerEvent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;SpecificRecordBase&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;SpecificRecord&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Your Avro implementation&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Publish events the same way as POJOs:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerEventsProducer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ApplicationEventPublisher&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;publishAvroEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CustomerEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publishEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Sending the event&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Routing Key Header
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;SpringCloudStreamEventExternalizer&lt;/code&gt; automatically maps routing keys to the appropriate message header based on your message broker:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Message Broker&lt;/th&gt;
&lt;th&gt;Header Name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Kafka&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kafka_messageKey&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RabbitMQ&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rabbit_routingKey&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kinesis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;partitionKey&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google PubSub&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pubsub_orderingKey&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure Event Hubs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;partitionKey&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;solace_messageKey&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apache Pulsar&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pulsar_key&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Event Serialization for Spring Modulith Publication Log
&lt;/h3&gt;

&lt;p&gt;Spring Modulith's Transactional Event Publication Log requires events to be serialized for database storage. This presents two challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Type Information: The default &lt;code&gt;JacksonEventSerializer&lt;/code&gt; loses generic type information for &lt;code&gt;Message&amp;lt;?&amp;gt;&lt;/code&gt; payloads&lt;/li&gt;
&lt;li&gt;Format Support: If you need to support Avro payloads, &lt;code&gt;JacksonEventSerializer&lt;/code&gt; does not play well with Avro GenericRecord/SpecificRecord&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This library addresses these challenges by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding a &lt;code&gt;_class&lt;/code&gt; field to preserve complete type information for &lt;code&gt;Message&amp;lt;?&amp;gt;&lt;/code&gt; payloads&lt;/li&gt;
&lt;li&gt;Supporting both POJO (JSON) and Avro serialization formats, through &lt;code&gt;AvroMapper&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enabling full deserialization back to original types&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avro serialization requires the &lt;code&gt;com.fasterxml.jackson.dataformat.avro.AvroMapper&lt;/code&gt; class to be present in the classpath. In order to use Avro serialization, you need to add &lt;code&gt;com.fasterxml.jackson.dataformat:jackson-dataformat-avro&lt;/code&gt; dependency to your project, as stated above&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending Spring Message Events
&lt;/h2&gt;

&lt;p&gt;For advanced control over message headers, you can send &lt;code&gt;Message&amp;lt;?&amp;gt;&lt;/code&gt; objects by including the &lt;code&gt;spring.cloud.stream.sendto.destination&lt;/code&gt; routing header. This header should point to your intended Spring Cloud Stream output binding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerEventsProducer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ApplicationEventPublisher&lt;/span&gt; &lt;span class="n"&gt;applicationEventPublisher&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CustomerEventsProducer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApplicationEventPublisher&lt;/span&gt; &lt;span class="n"&gt;applicationEventPublisher&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;applicationEventPublisher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;applicationEventPublisher&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sendCustomerEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CustomerEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CustomerEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MessageBuilder&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withPayload&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                     &lt;span class="c1"&gt;// supports both POJO and Avro payloads&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;SpringCloudStreamEventExternalizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"customers-json-out-0"&lt;/span&gt;              &lt;span class="c1"&gt;// target binding name&lt;/span&gt;
            &lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;applicationEventPublisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publishEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This header is automatically set when using ZenWave SDK AsyncAPI Generator.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Event Externalization and API Management with AsyncAPI
&lt;/h2&gt;

&lt;p&gt;While Spring Modulith's &lt;code&gt;@Externalized&lt;/code&gt; annotation provides a quick and convenient way to publish events, teams building event-driven systems often need additional capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Documentation: No built-in support for formal API documentation&lt;/li&gt;
&lt;li&gt;Schema Management: No friction to prevent breaking changes in event schemas that could impact consumers&lt;/li&gt;
&lt;li&gt;API Governance: No standardized way to enforce API design standards: naming conventions, versioning, headers/metadata...&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API-First Approach with AsyncAPI
&lt;/h3&gt;

&lt;p&gt;For teams following API-First practices, AsyncAPI offers a better approach to describe your Event-Driven Architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Formal API documentation&lt;/li&gt;
&lt;li&gt;Schema validation (Avro, JSON Schema)&lt;/li&gt;
&lt;li&gt;API governance and versioning&lt;/li&gt;
&lt;li&gt;Contract-first development&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Generation with ZenWave SDK
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.zenwave360.io/zenwave-sdk/plugins/asyncapi-spring-cloud-streams3/"&gt;ZenWave SDK AsyncAPI Generator&lt;/a&gt; can generate full SDKs from AsyncAPI definitions, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event Models/DTOs with full type safety&lt;/li&gt;
&lt;li&gt;Strongly-typed header objects with runtime population support&lt;/li&gt;
&lt;li&gt;Spring Cloud Stream event producers/consumers with transactional support via Spring Modulith&lt;/li&gt;
&lt;li&gt;Zero boilerplate code&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Already using &lt;code&gt;@Externalized&lt;/code&gt;? We're developing a tool to reverse engineer your events into AsyncAPI specifications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Example Implementation
&lt;/h3&gt;

&lt;p&gt;See it in action with this complete example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://springbuilders.dev/images/Ym2pv25728MT3olIY8QFtyFTWQGdesbG8m0_GQBPnE8/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy9nNWNv/d25rc2xkcWVtNTQw/d2JxNS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://springbuilders.dev/images/Ym2pv25728MT3olIY8QFtyFTWQGdesbG8m0_GQBPnE8/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy9nNWNv/d25rc2xkcWVtNTQw/d2JxNS5wbmc" alt="Transactional OutBox With AsyncAPI SpringCloud Stream And Spring Modulith" width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/EDALearn/EDA-TransactionalOutbox-Modulith-JPA"&gt;Playground Project&lt;/a&gt;: Full implementation with AsyncAPI + Avro&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.zenwave360.io/posts/TransactionalOutBoxWithAsyncAPIAndSpringModulith"&gt;Implementation Guide&lt;/a&gt;: Step-by-step tutorial&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Benefits Over Built-in Externalization
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Broker Flexibility&lt;/strong&gt;: Connect to any message broker supported by Spring Cloud Stream&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Header Control&lt;/strong&gt;: Simple configuration of message headers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Serialization Formats&lt;/strong&gt;: Built-in support for JSON and Avro&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AsyncAPI Integration&lt;/strong&gt;: Seamless integration with Spring Modulith Events and Spring Cloud Stream through &lt;a href="https://www.zenwave360.io/zenwave-sdk/plugins/asyncapi-spring-cloud-streams3/"&gt;ZenWave SDK AsyncAPI Generator&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;Visit &lt;a href="https://github.com/ZenWave360/spring-modulith-events-spring-cloud-stream"&gt;GitHub repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We welcome contributions and feedback from the community! 🚀&lt;/p&gt;




&lt;p&gt;Originally published at: &lt;a href="https://www.zenwave360.io/posts/Spring-Modulith-Events-Spring-Cloud-Stream-Externalizer/"&gt;https://www.zenwave360.io/posts/Spring-Modulith-Events-Spring-Cloud-Stream-Externalizer/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>modulith</category>
      <category>events</category>
      <category>springcloudstream</category>
      <category>asyncapi</category>
    </item>
    <item>
      <title>Implementing a Transactional OutBox With AsyncAPI, SpringModulith and ZenWaveSDK (No Code Required)</title>
      <dc:creator>Ivan Garcia Sainz-Aja</dc:creator>
      <pubDate>Sat, 08 Mar 2025 14:38:37 +0000</pubDate>
      <link>https://springbuilders.dev/ivangsa/implementing-a-transactional-outbox-with-asyncapi-springmodulith-and-zenwavesdk-13fp</link>
      <guid>https://springbuilders.dev/ivangsa/implementing-a-transactional-outbox-with-asyncapi-springmodulith-and-zenwavesdk-13fp</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When processes span multiple services without shared transaction boundaries, ensuring atomicity and consistency in distributed systems is challenging.&lt;/p&gt;

&lt;p&gt;Distributed transactions with Two-Phase Commits (2PC) are complex, introduce significant performance overhead, and may not even be feasible for certain services.&lt;/p&gt;

&lt;p&gt;The Outbox Pattern solves this by using a Database Transaction to store events in a dedicated "outbox" table within the same transaction as a database update. These events are then published to external systems, such as an email service or message broker, ensuring eventual consistency without the need for distributed transactions.&lt;/p&gt;

&lt;p&gt;In this post, we’ll explore how we can implement a Transactional Outbox Pattern to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persist data to a supported transactional database (e.g., SQL or MongoDB).&lt;/li&gt;
&lt;li&gt;Send events to an external message broker like Kafka or RabbitMQ using Spring Cloud Stream.&lt;/li&gt;
&lt;li&gt;Leverage Spring Modulith Events transactional features.&lt;/li&gt;
&lt;li&gt;Use ZenWaveSDK Code Generator for AsyncAPI so you don’t need to write a single line of boilerplate code for the transactional outbox and event publishing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://springbuilders.dev/images/nE6l_hTc7Bukeqz-_QfnCiEL720uZjT2ZC0e3WovgSE/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy9lYTdw/b2dqdjltMWl1aTMw/dHp4Zi5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://springbuilders.dev/images/nE6l_hTc7Bukeqz-_QfnCiEL720uZjT2ZC0e3WovgSE/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy9lYTdw/b2dqdjltMWl1aTMw/dHp4Zi5wbmc" alt="Transactional OutBox With AsyncAPI SpringCloud Stream And Spring Modulith" width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Playground Project
&lt;/h2&gt;

&lt;p&gt;Because working software is worth more than a thousand words, we’ll use a fully functional playground project that you can explore and test yourself.&lt;/p&gt;

&lt;p&gt;We'll use the following project as our playground: &lt;a href="https://github.com/EDALearn/EDA-TransactionalOutbox-Modulith-JPA"&gt;EDA-TransactionalOutbox-Modulith-JPA&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This project provides a simple API for managing customer details, including REST endpoints for CRUD operations. These operations publish event notifications to a Kafka topic, using Avro as the payload format.&lt;/p&gt;

&lt;p&gt;Key components of the project include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAPI definition file&lt;/strong&gt;: &lt;a href="https://github.com/ZenWave360/zenwave-playground/blob/main/zenwave-jpa-example/src/main/resources/public/apis/openapi.yml"&gt;openapi.yml&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AsyncAPI definition file&lt;/strong&gt;: &lt;a href="https://github.com/ZenWave360/zenwave-playground/blob/main/zenwave-jpa-example/src/main/resources/public/apis/asyncapi.yml"&gt;asyncapi.yml&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avro schemas for events&lt;/strong&gt;: &lt;a href="https://github.com/ZenWave360/zenwave-playground/tree/main/zenwave-jpa-example/src/main/resources/public/apis/avro"&gt;Avro schemas&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Distributed Transactions Problem and The Outbox Pattern
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://springbuilders.dev/images/7qHuxD4GBTkQJpxaSl8piuBmiyeOo8t1LSQIhHDBaFM/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy85cWc5/OGZha2lmNW9wM3Ax/amw2MS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://springbuilders.dev/images/7qHuxD4GBTkQJpxaSl8piuBmiyeOo8t1LSQIhHDBaFM/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy85cWc5/OGZha2lmNW9wM3Ax/amw2MS5wbmc" alt="Customer Events PlantUML" width="521" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When managing a &lt;code&gt;Customer&lt;/code&gt; entity, we encounter a significant challenge: ensuring the atomicity of persisting customer details in the database and publishing a related event to an external message broker. Since the database and the message broker do not share a transaction boundary, this can lead to potential inconsistencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Scenarios Highlighting the Problem:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Event Sent Before Database Transaction Commits&lt;/strong&gt;:&lt;br&gt;
If the event is published before the database transaction is committed, a rollback of the transaction would leave the system in an inconsistent state, as the published event cannot be undone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Event Sent After Database Transaction Commits&lt;/strong&gt;:&lt;br&gt;
If the event is published after the database transaction is committed, there’s no guarantee the event will actually be sent. A service crash or network failure could prevent the event from being published, resulting in data inconsistency.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="nf"&gt;createCustomer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request to save Customer: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customerRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Persist to DB&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customerEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eventsMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asCustomerEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;eventsProducer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onCustomerEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerEvent&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Emit Event to external Broker&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the transactional outbox pattern, instead of directly sending the event to the external system, the call to &lt;code&gt;eventsProducer.onCustomerEvent(customerEvent)&lt;/code&gt; stores the event in a dedicated outbox table as part of the same transaction as the database update. An external process then reads from the outbox table and publishes the event to the message broker.&lt;/p&gt;

&lt;p&gt;However, implementing this external process, managing the outbox table, and ensuring that events are published once and in the correct order can be complex and error-prone.&lt;/p&gt;

&lt;p&gt;Fortunately, tools like &lt;a href="https://docs.spring.io/spring-modulith/reference/events.html#publication-registry"&gt;Spring Modulith’s Event Publication Registry&lt;/a&gt; and the &lt;a href="https://www.zenwave360.io/zenwave-sdk/plugins/asyncapi-spring-cloud-streams3/"&gt;ZenWaveSDK Code Generator for AsyncAPI&lt;/a&gt; simplify this process, so you can focus only on implementing your business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  ZenWaveSDK Code Generation for AsyncAPI and Spring Cloud Streams
&lt;/h2&gt;

&lt;p&gt;ZenWaveSDK generates all the boilerplate code needed to send and receive events using Spring Cloud Stream from an AsyncAPI definition file.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/d5xddNWYR5I"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;From your AsyncAPI definition file, ZenWaveSDK generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DTO/Models&lt;/strong&gt; for your event payloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typed Header Objects&lt;/strong&gt; to ensure consistency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Intention-revealing" Java interfaces&lt;/strong&gt; with methods named explicitly after your operationIds.&lt;/li&gt;
&lt;li&gt;A lightweight implementation using Spring Cloud Streams that can be &lt;code&gt;@Autowire&lt;/code&gt;d directly into your services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can then configure Spring Cloud Stream to send and receive messages using any of the &lt;a href="https://docs.spring.io/spring-cloud-stream/reference/index.html"&gt;supported binders&lt;/a&gt;, such as Kafka or RabbitMQ. With ZenWaveSDK, you don’t need to write any boilerplate code, allowing you to focus entirely on your business logic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ZenWave SDK Maven Plugin Configuration
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.zenwave360.zenwave-sdk&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;zenwave-sdk-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${zenwave.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;inputSpec&amp;gt;&lt;/span&gt;${project.basedir}/src/main/resources/public/apis/asyncapi.yml&lt;span class="nt"&gt;&amp;lt;/inputSpec&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;addCompileSourceRoot&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/addCompileSourceRoot&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;addTestCompileSourceRoot&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/addTestCompileSourceRoot&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- DTOs --&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- we skip DTOs generation in this case b/c we are using Avro for that --&amp;gt;&lt;/span&gt;

      &lt;span class="c"&gt;&amp;lt;!-- Generate PROVIDER --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;generate-asyncapi&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;generate-sources&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;generate&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;generatorName&amp;gt;&lt;/span&gt;spring-cloud-streams3&lt;span class="nt"&gt;&amp;lt;/generatorName&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;configOptions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;role&amp;gt;&lt;/span&gt;provider&lt;span class="nt"&gt;&amp;lt;/role&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;imperative&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;transactionalOutbox&amp;gt;&lt;/span&gt;modulith&lt;span class="nt"&gt;&amp;lt;/transactionalOutbox&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- using Spring Modulith implementation --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;modelPackage&amp;gt;&lt;/span&gt;${asyncApiModelPackage}&lt;span class="nt"&gt;&amp;lt;/modelPackage&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;producerApiPackage&amp;gt;&lt;/span&gt;${asyncApiProducerApiPackage}&lt;span class="nt"&gt;&amp;lt;/producerApiPackage&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;consumerApiPackage&amp;gt;&lt;/span&gt;${asyncApiConsumerApiPackage}&lt;span class="nt"&gt;&amp;lt;/consumerApiPackage&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/configOptions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
      // ...
    &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;*Avro Maven Plugin Configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.avro&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;avro-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.11.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;schema&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;generate-sources&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sourceDirectory&amp;gt;&lt;/span&gt;${project.basedir}/src/main/resources/public/apis/avro&lt;span class="nt"&gt;&amp;lt;/sourceDirectory&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;outputDirectory&amp;gt;&lt;/span&gt;${project.basedir}/target/generated-sources/avro&lt;span class="nt"&gt;&amp;lt;/outputDirectory&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;imports&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;import&amp;gt;&lt;/span&gt;${project.basedir}/src/main/resources/public/apis/avro/PaymentMethodType.avsc&lt;span class="nt"&gt;&amp;lt;/import&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;import&amp;gt;&lt;/span&gt;${project.basedir}/src/main/resources/public/apis/avro/PaymentMethod.avsc&lt;span class="nt"&gt;&amp;lt;/import&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;import&amp;gt;&lt;/span&gt;${project.basedir}/src/main/resources/public/apis/avro/Address.avsc&lt;span class="nt"&gt;&amp;lt;/import&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/imports&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To achieve this, we configure the &lt;code&gt;zenwave-sdk-maven-plugin&lt;/code&gt; and, in this case, also the &lt;code&gt;avro-maven-plugin&lt;/code&gt; in the &lt;code&gt;pom.xml&lt;/code&gt; file. This setup ensures that the necessary code is generated in the &lt;code&gt;target/generated-sources&lt;/code&gt; folder as part of your build process.&lt;/p&gt;

&lt;p&gt;Since this configuration runs automatically during the build, every time you update your AsyncAPI definition file, you can be sure that your code remains in sync with your API definition.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://springbuilders.dev/images/ioPVWYea2Z3Nk22OyIuw45REat9OZh8KfjZ0mQjB0UU/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy82NXV0/N2FrdGllMHJjMG5j/YTR2YS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://springbuilders.dev/images/ioPVWYea2Z3Nk22OyIuw45REat9OZh8KfjZ0mQjB0UU/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy82NXV0/N2FrdGllMHJjMG5j/YTR2YS5wbmc" alt="ZenWaveSDK Target Generated Sources" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And because we configured the &lt;code&gt;transactionalOutbox&lt;/code&gt; option to &lt;code&gt;modulith&lt;/code&gt;, ZenWaveSDK will generate the code to use Spring Modulith Events Publication Registry to manage the transactional outbox for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://springbuilders.dev/images/jdEYKWIQa_IFMzDIfbzhnOjE9t9JqtrMlAQB4tRBATE/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy8wdmt5/bmVsdmkzaWk2bHM5/MTNnbS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://springbuilders.dev/images/jdEYKWIQa_IFMzDIfbzhnOjE9t9JqtrMlAQB4tRBATE/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy8wdmt5/bmVsdmkzaWk2bHM5/MTNnbS5wbmc" alt="ZenWaveSDK Transactional OutBox -Modulith" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring Modulith Events Publication Registry
&lt;/h2&gt;

&lt;p&gt;Because Event Externalization is enabled, and &lt;code&gt;Message&amp;lt;?&amp;gt;&lt;/code&gt; objects are configured for externalization (see below), these events are also stored transactionally in the Spring Modulith Event Publication Registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;modulith.events.externalization.enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;modulith.events.jdbc.schema-initialization.enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;modulith.events.republish-outstanding-events-on-restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://springbuilders.dev/images/sf4tNoRCliU-aQ_mt0OymYBrwhAjYVNTjSga5VX6f4U/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy9xaXBh/N3lmeDhwNW5uaDZ0/NGV3eS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://springbuilders.dev/images/sf4tNoRCliU-aQ_mt0OymYBrwhAjYVNTjSga5VX6f4U/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy9xaXBh/N3lmeDhwNW5uaDZ0/NGV3eS5wbmc" alt="Modulith Event Publication Registry" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Spring Cloud Streams to Externalize Modulith Events
&lt;/h2&gt;

&lt;p&gt;Now that Spring Modulith is managing our &lt;code&gt;Message&amp;lt;?&amp;gt;&lt;/code&gt; events, we need to configure one of the many supported &lt;a href="https://docs.spring.io/spring-modulith/reference/events.html#externalization"&gt;event externalizers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If we were publishing POJOs in JSON format, we could use &lt;code&gt;spring-modulith-events-kafka&lt;/code&gt; to externalize events to a Kafka topic. However, since we want to externalize &lt;code&gt;Message&amp;lt;?&amp;gt;&lt;/code&gt; objects with Avro payloads, we will use &lt;code&gt;io.zenwave360.sdk:spring-modulith-events-scs&lt;/code&gt;, which supports serializing and deserializing &lt;code&gt;Message&amp;lt;?&amp;gt;&lt;/code&gt; objects, with or without Avro payloads.&lt;/p&gt;

&lt;p&gt;For more details, see &lt;a href="https://github.com/ZenWave360/spring-modulith-events-spring-cloud-stream"&gt;ZenWave360 Spring Modulith Events for Spring Cloud Stream&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To enable this, add the following dependencies to your &lt;code&gt;pom.xml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- Spring Cloud Stream Externalization for Message&amp;lt;?&amp;gt; --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.zenwave360.sdk&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-modulith-events-scs&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${spring-modulith-events-scs.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- Needed for serializing Avro payloads to db storage as json --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.fasterxml.jackson.dataformat&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jackson-dataformat-avro&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;@EnableSpringCloudStreamEventExternalization&lt;/code&gt; to our Spring Boot Configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.zenwave360.modulith.events.scs.config.EnableSpringCloudStreamEventExternalization&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableSpringCloudStreamEventExternalization&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExternalizationConfiguration&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will route programmatically route our &lt;code&gt;Message&amp;lt;?&amp;gt;&lt;/code&gt; objects to the correct Spring Cloud Stream binding, but you don't need to worry about any of this as it is handled automatically behind the scenes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// this is some of what you get when adding @EnableSpringCloudStreamEventExternalization&lt;/span&gt;
&lt;span class="nd"&gt;@AutoConfiguration&lt;/span&gt;
&lt;span class="nd"&gt;@AutoConfigureAfter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EventExternalizationAutoConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@ConditionalOnProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"spring.modulith.events.externalization.enabled"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;havingValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;matchIfMissing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MessageExternalizationConfiguration&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nc"&gt;EventExternalizationConfiguration&lt;/span&gt; &lt;span class="nf"&gt;eventExternalizationConfiguration&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EventExternalizationConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;externalizing&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;annotatedAsExternalized&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;getTarget&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;RoutingTarget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forTarget&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getTarget&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;withoutKey&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getTarget&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHeaders&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SpringCloudStreamEventExternalizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automagic Transactional OutBox Implementation
&lt;/h2&gt;

&lt;p&gt;Our original code is now &lt;em&gt;automagically&lt;/em&gt; implementing the Transactional OutBox pattern using Spring Modulith Events Publication Registry and Spring Cloud Stream, all made possible by the ZenWaveSDK AsyncAPI Code Generator. 🚀🚀🚀&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="nf"&gt;createCustomer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request to save Customer: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customerRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Persist to DB&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customerEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eventsMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asCustomerEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;eventsProducer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onCustomerEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerEvent&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Emit Event to external Broker via Tx OutBox&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Originally published at: &lt;a href="https://www.zenwave360.io/posts/TransactionalOutBoxWithAsyncAPIAndSpringModulith/"&gt;https://www.zenwave360.io/posts/TransactionalOutBoxWithAsyncAPIAndSpringModulith/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>asyncapi</category>
      <category>zenwavesdk</category>
      <category>springcloudstream</category>
      <category>modulith</category>
    </item>
  </channel>
</rss>
