serializeToMap(Object payload) {
+ ObjectNode objectNode = avroMapper.valueToTree(payload);
+ objectNode.remove("specificData"); // TODO: remove this recursively
+ return avroMapper.convertValue(objectNode, Map.class);
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/MessageEventSerializer.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/MessageEventSerializer.java
new file mode 100644
index 00000000..ca388be1
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/MessageEventSerializer.java
@@ -0,0 +1,160 @@
+package org.springframework.modulith.events.scs;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.TreeNode;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.modulith.events.core.EventSerializer;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MessageEventSerializer implements EventSerializer {
+
+ protected Logger log = LoggerFactory.getLogger(getClass());
+
+ private final ObjectMapper jacksonMapper;
+
+ /**
+ * Controls whether to store headers types to be used for deserialization. Default is true.
+ */
+ private boolean processHeaderTypes = true;
+
+ public MessageEventSerializer(ObjectMapper jacksonMapper) {
+ this.jacksonMapper = jacksonMapper;
+ }
+
+ public void setProcessHeaderTypes(boolean processHeaderTypes) {
+ this.processHeaderTypes = processHeaderTypes;
+ }
+
+ /**
+ * Convert the payload to a Map so it can be serialized to JSON.
+ *
+ * Subclasses (i.e AvroSerializer) can override this method to customize the serialization.
+ *
+ * @param payload
+ * @return
+ */
+ protected Map serializeToMap(Object payload) {
+ ObjectNode objectNode = jacksonMapper.valueToTree(payload);
+ return jacksonMapper.convertValue(objectNode, Map.class);
+ }
+
+ @Override
+ public Object serialize(Object event) {
+ if (event instanceof Message> message) {
+ Map serializedMessage = new HashMap<>();
+ serializedMessage.put("headers", message.getHeaders());
+ serializedMessage.put("_header_types", extractHeaderTypes(message.getHeaders()));
+
+ var payload = serializeToMap(message.getPayload());
+ payload.put("_class", message.getPayload().getClass().getName());
+ serializedMessage.put("payload", payload);
+
+ return jacksonSerialize(serializedMessage);
+ }
+ return jacksonSerialize(event);
+ }
+
+ @Override
+ public T deserialize(Object serialized, Class type) {
+ try {
+ return unsafeDeserialize(serialized, type);
+ }
+ catch (JsonProcessingException | ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private T unsafeDeserialize(Object serialized, Class type)
+ throws JsonProcessingException, ClassNotFoundException {
+ if (Message.class.isAssignableFrom(type)) {
+ JsonNode node = jacksonMapper.readTree(serialized.toString());
+ JsonNode headersNode = node.get("headers");
+ Map headers = jacksonMapper.convertValue(headersNode, Map.class);
+
+ if (processHeaderTypes) {
+ JsonNode headerTypesNode = node.get("_header_types");
+ Map headerTypes = jacksonMapper.convertValue(headerTypesNode, Map.class);
+ processHeaderTypes(headerTypes, headers);
+ }
+
+ JsonNode payloadNode = node.get("payload");
+ Object payload = null;
+ if (payloadNode.get("_class") != null) {
+ Class> payloadType = Class.forName(payloadNode.get("_class").asText());
+ if (payloadNode instanceof ObjectNode objectNode) {
+ objectNode.remove("_class");
+ }
+ payload = deserializePayload(payloadNode, payloadType);
+ }
+ else {
+ payload = deserializePayload(payloadNode, Object.class);
+ }
+ return (T) MessageBuilder.createMessage(payload, new MessageHeaders(headers));
+ }
+ return jacksonDeserialize(serialized, type);
+ }
+
+ protected T deserializePayload(TreeNode payloadNode, Class payloadType) throws JsonProcessingException {
+ return jacksonMapper.treeToValue(payloadNode, payloadType);
+ }
+
+ protected Object jacksonSerialize(Object event) {
+ try {
+ var map = serializeToMap(event);
+ return jacksonMapper.writeValueAsString(map);
+ }
+ catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected T jacksonDeserialize(Object serialized, Class type) {
+ try {
+ JsonNode node = jacksonMapper.readTree(serialized.toString());
+ return (T) jacksonMapper.readerFor(type).readValue(node);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected Map extractHeaderTypes(Map headers) {
+ Map headerTypes = new HashMap<>();
+ headers.forEach((key, value) -> headerTypes.put(key, value.getClass().getName()));
+ return headerTypes;
+ }
+
+ protected void processHeaderTypes(Map headerTypes, Map headers) {
+ if (headerTypes == null) {
+ return;
+ }
+ headers.forEach((key, value) -> {
+ if (headerTypes.containsKey(key)) {
+ var headerType = headerTypes.get(key);
+ try {
+ if (value instanceof String) {
+ headers.put(key, jacksonMapper.convertValue(value, Class.forName(headerType)));
+ }
+ else {
+ headers.put(key, jacksonDeserialize(value, Class.forName(headerType)));
+ }
+ }
+ catch (Exception e) {
+ log.error("Failed to process header: {key: {}, value: {}, type: {}}. Error: {}", key, value,
+ headerType, e.getMessage());
+ }
+ }
+ });
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/SpringCloudStreamEventExternalizer.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/SpringCloudStreamEventExternalizer.java
new file mode 100644
index 00000000..16dfac75
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/SpringCloudStreamEventExternalizer.java
@@ -0,0 +1,98 @@
+package org.springframework.modulith.events.scs;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.cloud.stream.binder.BinderFactory;
+import org.springframework.cloud.stream.config.BindingServiceProperties;
+import org.springframework.cloud.stream.function.StreamBridge;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.modulith.events.EventExternalizationConfiguration;
+import org.springframework.modulith.events.RoutingTarget;
+import org.springframework.modulith.events.support.BrokerRouting;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiFunction;
+
+public class SpringCloudStreamEventExternalizer implements BiFunction> {
+
+ private static final Logger log = LoggerFactory.getLogger(SpringCloudStreamEventExternalizer.class);
+
+ public static final String SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER = "spring.cloud.stream.sendto.destination";
+
+ private final EventExternalizationConfiguration configuration;
+
+ private final StreamBridge streamBridge;
+
+ private final BindingServiceProperties bindingServiceProperties;
+
+ private final BinderFactory binderFactory;
+
+ private final EvaluationContext context;
+
+ public SpringCloudStreamEventExternalizer(EventExternalizationConfiguration configuration,
+ EvaluationContext context, StreamBridge streamBridge, BindingServiceProperties bindingServiceProperties,
+ BinderFactory binderFactory) {
+ this.configuration = configuration;
+ this.context = context;
+ this.streamBridge = streamBridge;
+ this.bindingServiceProperties = bindingServiceProperties;
+ this.binderFactory = binderFactory;
+ }
+
+ @Override
+ public CompletableFuture> apply(RoutingTarget routingTarget, Object event) {
+ var routing = BrokerRouting.of(routingTarget, context);
+
+ var target = getTarget(event, routingTarget);
+ var keyHeaderValue = routing.getKey(event);
+ var keyHeaderName = getKeyHeaderName(target, bindingServiceProperties, binderFactory);
+
+ var headersMap = event instanceof Message ? new LinkedHashMap<>(((Message>) event).getHeaders())
+ : new LinkedHashMap();
+ if (keyHeaderValue != null && keyHeaderName != null) {
+ if (!headersMap.containsKey(keyHeaderName)) {
+ log.debug("Adding key header to message: {} = {}", keyHeaderName, keyHeaderValue);
+ headersMap.put(keyHeaderName, keyHeaderValue);
+ }
+ }
+ var payload = event instanceof Message> ? ((Message>) event).getPayload() : event;
+ var message = MessageBuilder.withPayload(payload).copyHeaders(headersMap).build();
+
+ log.debug("Sending event to Spring Cloud Stream target: {}", target);
+ var result = streamBridge.send(target, message);
+ return CompletableFuture.completedFuture(result);
+ }
+
+ protected static final Map messageKeyHeaders = Map.of(
+ "org.springframework.cloud.stream.binder.kafka.KafkaMessageChannelBinder", "kafka_messageKey",
+ "org.springframework.cloud.stream.binder.rabbit.RabbitMessageChannelBinder", "rabbit_routingKey",
+ "org.springframework.cloud.stream.binder.kinesis.KinesisMessageChannelBinder", "partitionKey",
+ "org.springframework.cloud.stream.binder.pubsub.PubSubMessageChannelBinder", "pubsub_orderingKey",
+ "org.springframework.cloud.stream.binder.eventhubs.EventHubsMessageChannelBinder", "partitionKey",
+ "org.springframework.cloud.stream.binder.solace.SolaceMessageChannelBinder", "solace_messageKey",
+ "org.springframework.cloud.stream.binder.pulsar.PulsarMessageChannelBinder", "pulsar_key");
+
+ protected String getKeyHeaderName(String channelName, BindingServiceProperties bindingServiceProperties,
+ BinderFactory binderFactory) {
+ String binderConfigurationName = bindingServiceProperties.getBinder(channelName);
+ var binder = binderFactory.getBinder(binderConfigurationName, MessageChannel.class);
+ if (binder == null) {
+ return null;
+ }
+ return messageKeyHeaders.get(binder.getClass().getName());
+ }
+
+ protected String getTarget(Object event, RoutingTarget routingTarget) {
+ if (event instanceof Message> message) {
+ var target = message.getHeaders().get(SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER, String.class);
+ return target != null ? target : routingTarget.getTarget();
+ }
+ return routingTarget.getTarget();
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/EnableSpringCloudStreamEventExternalization.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/EnableSpringCloudStreamEventExternalization.java
new file mode 100644
index 00000000..36d5a548
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/EnableSpringCloudStreamEventExternalization.java
@@ -0,0 +1,18 @@
+package org.springframework.modulith.events.scs.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Configuration
+@Import({ SpringCloudStreamEventExternalizerConfiguration.class, MessageEventSerializerConfiguration.class,
+ MessageExternalizationConfiguration.class })
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnableSpringCloudStreamEventExternalization {
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageEventSerializerConfiguration.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageEventSerializerConfiguration.java
new file mode 100644
index 00000000..3c208772
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageEventSerializerConfiguration.java
@@ -0,0 +1,38 @@
+package org.springframework.modulith.events.scs.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.avro.AvroMapper;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.modulith.events.scs.AvroEventSerializer;
+import org.springframework.modulith.events.scs.MessageEventSerializer;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.modulith.events.config.EventExternalizationAutoConfiguration;
+import org.springframework.modulith.events.core.EventSerializer;
+
+@Configuration
+@AutoConfigureAfter(EventExternalizationAutoConfiguration.class)
+@ConditionalOnProperty(name = "spring.modulith.events.externalization.enabled", havingValue = "true",
+ matchIfMissing = true)
+public class MessageEventSerializerConfiguration {
+
+ @Bean
+ @Primary
+ @ConditionalOnClass(AvroMapper.class)
+ public EventSerializer avroEventSerializer(ObjectMapper mapper) {
+ return new AvroEventSerializer(mapper);
+ }
+
+ @Bean
+ @Primary
+ @ConditionalOnMissingClass("com.fasterxml.jackson.dataformat.avro.AvroMapper")
+ public EventSerializer messageEventSerializer(ObjectMapper mapper) {
+ return new MessageEventSerializer(mapper);
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageExternalizationConfiguration.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageExternalizationConfiguration.java
new file mode 100644
index 00000000..6e7d3034
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/MessageExternalizationConfiguration.java
@@ -0,0 +1,36 @@
+package org.springframework.modulith.events.scs.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.modulith.events.scs.SpringCloudStreamEventExternalizer;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.messaging.Message;
+import org.springframework.modulith.events.EventExternalizationConfiguration;
+import org.springframework.modulith.events.RoutingTarget;
+import org.springframework.modulith.events.config.EventExternalizationAutoConfiguration;
+
+@Configuration
+@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 -> EventExternalizationConfiguration.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;
+ }
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/SpringCloudStreamEventExternalizerConfiguration.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/SpringCloudStreamEventExternalizerConfiguration.java
new file mode 100644
index 00000000..d02a680f
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/config/SpringCloudStreamEventExternalizerConfiguration.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.modulith.events.scs.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.modulith.events.scs.SpringCloudStreamEventExternalizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.cloud.stream.binder.BinderFactory;
+import org.springframework.cloud.stream.config.BindingServiceProperties;
+import org.springframework.cloud.stream.function.StreamBridge;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.modulith.events.EventExternalizationConfiguration;
+import org.springframework.modulith.events.config.EventExternalizationAutoConfiguration;
+import org.springframework.modulith.events.support.DelegatingEventExternalizer;
+
+@Configuration
+@AutoConfigureAfter(EventExternalizationAutoConfiguration.class)
+@ConditionalOnClass(StreamBridge.class)
+@ConditionalOnProperty(name = "spring.modulith.events.externalization.enabled", havingValue = "true",
+ matchIfMissing = true)
+public class SpringCloudStreamEventExternalizerConfiguration {
+
+ private static final Logger log = LoggerFactory.getLogger(SpringCloudStreamEventExternalizerConfiguration.class);
+
+ @Bean
+ DelegatingEventExternalizer springCloudStreamMessageExternalizer(EventExternalizationConfiguration configuration,
+ StreamBridge streamBridge, BeanFactory factory, BindingServiceProperties bindingServiceProperties,
+ BinderFactory binderFactory) {
+ log.debug("Registering domain event externalization to Spring Cloud Stream…");
+
+ var context = new StandardEvaluationContext();
+ context.setBeanResolver(new BeanFactoryResolver(factory));
+
+ return new DelegatingEventExternalizer(configuration, new SpringCloudStreamEventExternalizer(configuration,
+ context, streamBridge, bindingServiceProperties, binderFactory));
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/package-info.java b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/package-info.java
new file mode 100644
index 00000000..992b3b00
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/main/java/org/springframework/modulith/events/scs/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Spring Cloud Stream event externalization support.
+ */
+@org.springframework.lang.NonNullApi
+package org.springframework.modulith.events.scs;
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/AvroEventSerializerTest.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/AvroEventSerializerTest.java
new file mode 100644
index 00000000..2d673be5
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/AvroEventSerializerTest.java
@@ -0,0 +1,94 @@
+package org.springframework.modulith.events.scs;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.modulith.events.scs.dtos.avro.CustomerEvent;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.modulith.events.core.EventSerializer;
+
+public class AvroEventSerializerTest {
+
+ private EventSerializer eventSerializer;
+
+ @BeforeEach
+ public void setUp() {
+ eventSerializer = new AvroEventSerializer(new ObjectMapper());
+ }
+
+ @Test
+ public void testSerializeMessage() {
+ var customerEvent = new CustomerEvent();
+ customerEvent.setName("John Doe");
+ Message> message = MessageBuilder.withPayload(customerEvent).setHeader("headerKey", "headerValue").build();
+
+ Object serialized = eventSerializer.serialize(message);
+
+ Assertions.assertTrue(serialized instanceof String);
+ Assertions.assertTrue(serialized.toString().contains("\"name\":\"John Doe\","));
+ Assertions.assertTrue(serialized.toString()
+ .contains("\"_class\":\"org.springframework.modulith.events.scs.dtos.avro.CustomerEvent\""));
+ }
+
+ @Test
+ public void testDeserializeObject() {
+ String serializedObject = """
+ {
+ "name" : "John Doe"
+ }
+ """;
+ CustomerEvent deserialized = eventSerializer.deserialize(serializedObject, CustomerEvent.class);
+
+ Assertions.assertEquals("John Doe", deserialized.getName());
+ }
+
+ @Test
+ public void testDeserializeMessage() throws JsonProcessingException, ClassNotFoundException {
+ String serializedMessage = """
+ {
+ "headers" : {
+ "headerKey" : "headerValue",
+ "id" : "cc76cfb5-4ba3-5bf1-f652-9f44dfc24c85",
+ "timestamp" : 1735318992520
+ },
+ "payload" : {
+ "name" : "John Doe",
+ "addresses" : [ ],
+ "paymentMethods" : [ ],
+ "_class" : "org.springframework.modulith.events.scs.dtos.avro.CustomerEvent"
+ }
+ }
+ """;
+ Message> deserialized = eventSerializer.deserialize(serializedMessage, Message.class);
+
+ Assertions.assertTrue(deserialized.getPayload() instanceof CustomerEvent);
+ }
+
+ @Test
+ public void testSerializeDeserializeMessage() throws JsonProcessingException, ClassNotFoundException {
+ var customerEvent = new CustomerEvent();
+ customerEvent.setName("John Doe");
+ Message> message = MessageBuilder.withPayload(customerEvent).setHeader("headerKey", "headerValue").build();
+
+ Object serialized = eventSerializer.serialize(message);
+ Message> deserialized = eventSerializer.deserialize(serialized, Message.class);
+
+ Assertions.assertEquals(message.getPayload().getClass(), deserialized.getPayload().getClass());
+ }
+
+ @Test
+ public void testSerializeDeserializeObject() throws JsonProcessingException, ClassNotFoundException {
+ var customerEvent = new CustomerEvent();
+ customerEvent.setName("John Doe");
+
+ Object serialized = eventSerializer.serialize(customerEvent);
+ Object deserialized = eventSerializer.deserialize(serialized, CustomerEvent.class);
+
+ Assertions.assertEquals(CustomerEvent.class, deserialized.getClass());
+ Assertions.assertEquals(customerEvent.getName(), ((CustomerEvent) deserialized).getName());
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/MessageEventSerializerTest.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/MessageEventSerializerTest.java
new file mode 100644
index 00000000..3047feaf
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/MessageEventSerializerTest.java
@@ -0,0 +1,109 @@
+package org.springframework.modulith.events.scs;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.modulith.events.scs.dtos.json.CustomerEvent;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.modulith.events.core.EventSerializer;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public class MessageEventSerializerTest {
+
+ private ObjectMapper objectMapper;
+
+ private EventSerializer eventSerializer;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper();
+ eventSerializer = new MessageEventSerializer(objectMapper);
+ }
+
+ @Test
+ public void testSerializeMessage() {
+ var customerEvent = new CustomerEvent().withName("John Doe");
+ Message> message = MessageBuilder.withPayload(customerEvent).setHeader("headerKey", "headerValue").build();
+
+ Object serialized = eventSerializer.serialize(message);
+
+ Assertions.assertTrue(serialized instanceof String);
+ Assertions.assertTrue(serialized.toString().contains("\"payload\":{\"name\":\"John Doe\","));
+ Assertions.assertTrue(serialized.toString()
+ .contains("\"_class\":\"org.springframework.modulith.events.scs.dtos.json.CustomerEvent\""));
+ }
+
+ @Test
+ public void testSerializeObject() {
+ String serializedObject = """
+ {
+ "name" : "John Doe"
+ }
+ """;
+ CustomerEvent deserialized = eventSerializer.deserialize(serializedObject, CustomerEvent.class);
+
+ Assertions.assertEquals("John Doe", deserialized.getName());
+ }
+
+ @Test
+ public void testDeserializeMessage() throws JsonProcessingException, ClassNotFoundException {
+ String serializedMessage = """
+ {
+ "headers" : {
+ "headerKey" : "headerValue",
+ "id" : "cc76cfb5-4ba3-5bf1-f652-9f44dfc24c85",
+ "timestamp" : 1735318992520
+ },
+ "_header_types" : {
+ "headerkey" : "java.lang.String",
+ "id" : "java.util.UUID",
+ "timestamp" : "java.lang.Long"
+ },
+ "payload" : {
+ "name" : "John Doe",
+ "addresses" : [ ],
+ "paymentMethods" : [ ],
+ "_class" : "org.springframework.modulith.events.scs.dtos.json.CustomerEvent"
+ }
+ }
+ """;
+ Message> deserialized = eventSerializer.deserialize(serializedMessage, Message.class);
+
+ Assertions.assertTrue(deserialized.getPayload() instanceof CustomerEvent);
+ Assertions.assertTrue(deserialized.getHeaders().get("id") instanceof UUID);
+ }
+
+ @Test
+ public void testSerializeDeserializeMessage() throws JsonProcessingException, ClassNotFoundException {
+ var customerEvent = new CustomerEvent().withName("John Doe");
+ Message> message = MessageBuilder.withPayload(customerEvent)
+ .setHeader("headerKey", "headerValue")
+ .setHeader("uuid", UUID.randomUUID())
+ .setHeader("long", System.currentTimeMillis())
+ .setHeader("number", BigDecimal.ONE)
+ .build();
+
+ Object serialized = eventSerializer.serialize(message);
+ Message> deserialized = eventSerializer.deserialize(serialized, Message.class);
+
+ Assertions.assertEquals(message.getPayload().getClass(), deserialized.getPayload().getClass());
+ Assertions.assertEquals(UUID.class, deserialized.getHeaders().get("uuid").getClass());
+ Assertions.assertEquals(BigDecimal.class, deserialized.getHeaders().get("number").getClass());
+ }
+
+ @Test
+ public void testSerializeDeserializeObject() throws JsonProcessingException, ClassNotFoundException {
+ var customerEvent = new CustomerEvent().withName("John Doe");
+
+ Object serialized = eventSerializer.serialize(customerEvent);
+ Object deserialized = eventSerializer.deserialize(serialized, CustomerEvent.class);
+
+ Assertions.assertEquals(CustomerEvent.class, deserialized.getClass());
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSAvroEventExternalizerTest.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSAvroEventExternalizerTest.java
new file mode 100644
index 00000000..87e70a9f
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSAvroEventExternalizerTest.java
@@ -0,0 +1,75 @@
+package org.springframework.modulith.events.scs;
+
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.stream.function.StreamBridge;
+import org.springframework.messaging.Message;
+import org.springframework.modulith.events.scs.dtos.avro.*;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.awaitility.Awaitility.await;
+import static org.mockito.Mockito.verify;
+
+@SpringBootTest(classes = { TestsConfiguration.class })
+@Transactional
+public class SCSAvroEventExternalizerTest {
+
+ @Autowired
+ TestsConfiguration.CustomerEventsProducer customerEventsProducer;
+
+ @MockitoSpyBean
+ private StreamBridge streamBridge;
+
+ @Test
+ void testExternalizeAvroEvent() throws InterruptedException {
+ var event = new CustomerEvent();
+ event.setId(1L);
+ event.setName("John Doe");
+ event.setEmail("me@email.com");
+ event.setAddresses(List.of(new Address("Main St", "City")));
+ event.setPaymentMethods(List.of(new PaymentMethod(1, PaymentMethodType.MASTERCARD, "1234")));
+
+ customerEventsProducer.onCustomerEventAvroMessage(event);
+
+ // Wait for the event to be externalized
+ await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {
+ verify(streamBridge).send(Mockito.eq("customers-avro-out-0"), Mockito.argThat(message -> {
+ if (message instanceof Message>) {
+ var payload = ((Message>) message).getPayload();
+ return payload instanceof CustomerEvent && "John Doe".equals(((CustomerEvent) payload).getName());
+ }
+ return false;
+ }));
+ });
+ }
+
+ @Test
+ void testExternalizeAvroPojo() throws InterruptedException {
+ var event = new ExternalizedCustomerEvent();
+ event.setId(1L);
+ event.setName("John Doe Externalized");
+ event.setEmail("me@email.com");
+ event.setAddresses(List.of(new Address("Main St", "City")));
+ event.setPaymentMethods(List.of(new PaymentMethod(1, PaymentMethodType.MASTERCARD, "1234")));
+ customerEventsProducer.onCustomerEventAvroPojo(event);
+
+ // Wait for the event to be externalized
+ await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {
+ verify(streamBridge).send(Mockito.eq("customers-avro-externalized-out-0"), Mockito.argThat(message -> {
+ if (message instanceof Message>) {
+ var payload = ((Message>) message).getPayload();
+ return payload instanceof ExternalizedCustomerEvent
+ && "John Doe Externalized".equals(((ExternalizedCustomerEvent) payload).getName());
+ }
+ return false;
+ }));
+ });
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSJsonEventExternalizerTest.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSJsonEventExternalizerTest.java
new file mode 100644
index 00000000..cdd734a4
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/SCSJsonEventExternalizerTest.java
@@ -0,0 +1,66 @@
+package org.springframework.modulith.events.scs;
+
+import org.springframework.modulith.events.scs.dtos.json.CustomerEvent;
+import org.springframework.modulith.events.scs.dtos.json.ExternalizedCustomerEvent;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.stream.function.StreamBridge;
+import org.springframework.messaging.Message;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.awaitility.Awaitility.await;
+import static org.mockito.Mockito.verify;
+
+@SpringBootTest(classes = { TestsConfiguration.class })
+@Transactional
+public class SCSJsonEventExternalizerTest {
+
+ @Autowired
+ TestsConfiguration.CustomerEventsProducer customerEventsProducer;
+
+ @MockitoSpyBean
+ private StreamBridge streamBridge;
+
+ @Test
+ void testExternalizeJsonMessage() throws InterruptedException {
+ var event = new CustomerEvent().withName("John Doe");
+ customerEventsProducer.onCustomerEventJsonMessage(event);
+
+ // Wait for the event to be externalized
+ await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {
+ verify(streamBridge).send(Mockito.eq("customers-json-out-0"), Mockito.argThat(message -> {
+ if (message instanceof Message>) {
+ var payload = ((Message>) message).getPayload();
+ return payload instanceof CustomerEvent
+ && "John Doe".equals(((CustomerEvent) payload).getName());
+ }
+ return false;
+ }));
+ });
+ }
+
+ @Test
+ void testExternalizeJsonPojo() throws InterruptedException {
+ var event = new ExternalizedCustomerEvent().withName("John Doe Externalized");
+ customerEventsProducer.onCustomerEventJsonPojo(event);
+
+ // Wait for the event to be externalized
+ await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {
+ verify(streamBridge).send(Mockito.eq("customers-json-externalized-out-0"), Mockito.argThat(message -> {
+ if (message instanceof Message>) {
+ var payload = ((Message>) message).getPayload();
+ return payload instanceof CustomerEvent
+ && "John Doe Externalized".equals(((CustomerEvent) payload).getName());
+ }
+ return false;
+ }));
+ });
+ }
+
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/TestsConfiguration.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/TestsConfiguration.java
new file mode 100644
index 00000000..3fb9624e
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/TestsConfiguration.java
@@ -0,0 +1,87 @@
+package org.springframework.modulith.events.scs;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.cloud.stream.schema.avro.AvroSchemaMessageConverter;
+import org.springframework.cloud.stream.schema.avro.AvroSchemaServiceManagerImpl;
+import org.springframework.modulith.events.scs.config.EnableSpringCloudStreamEventExternalization;
+import org.springframework.modulith.events.scs.dtos.avro.CustomerEvent;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.annotation.Bean;
+import org.springframework.kafka.test.EmbeddedKafkaBroker;
+import org.springframework.kafka.test.EmbeddedKafkaZKBroker;
+import org.springframework.kafka.test.context.EmbeddedKafka;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.modulith.events.scs.dtos.avro.ExternalizedCustomerEvent;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+@EnableAutoConfiguration
+@EnableSpringCloudStreamEventExternalization
+@EmbeddedKafka(partitions = 1)
+@EnableTransactionManagement
+public class TestsConfiguration {
+
+ @Bean
+ public ObjectMapper objectMapper() {
+ return new ObjectMapper(); // Customize if needed
+ }
+
+ @Bean
+ EmbeddedKafkaBroker embeddedKafkaBroker() {
+ return new EmbeddedKafkaZKBroker(1, true, 1);
+ }
+
+ @Bean
+ CustomerEventsProducer customerEventsProducer(ApplicationEventPublisher applicationEventPublisher) {
+ return new CustomerEventsProducer(applicationEventPublisher);
+ }
+
+ @Bean
+ public static AvroSchemaMessageConverter avroSchemaMessageConverter() {
+ return new AvroSchemaMessageConverter(new AvroSchemaServiceManagerImpl());
+ }
+
+ static class CustomerEventsProducer {
+
+ private final ApplicationEventPublisher applicationEventPublisher;
+
+ public CustomerEventsProducer(ApplicationEventPublisher applicationEventPublisher) {
+ this.applicationEventPublisher = applicationEventPublisher;
+ }
+
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void onCustomerEventJsonMessage(org.springframework.modulith.events.scs.dtos.json.CustomerEvent event) {
+ Message message = MessageBuilder
+ .withPayload(event)
+ .setHeader(SpringCloudStreamEventExternalizer.SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER,
+ "customers-json-out-0") // <- target binding name
+ .build();
+ applicationEventPublisher.publishEvent(message);
+ }
+
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void onCustomerEventJsonPojo(org.springframework.modulith.events.scs.dtos.json.CustomerEvent event) {
+ applicationEventPublisher.publishEvent(event);
+ }
+
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void onCustomerEventAvroMessage(CustomerEvent event) {
+ Message message = MessageBuilder
+ .withPayload(event)
+ .setHeader(SpringCloudStreamEventExternalizer.SPRING_CLOUD_STREAM_SENDTO_DESTINATION_HEADER,
+ "customers-avro-out-0") // <- target binding name
+ .build();
+ applicationEventPublisher.publishEvent(message);
+ }
+
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void onCustomerEventAvroPojo(ExternalizedCustomerEvent event) {
+ applicationEventPublisher.publishEvent(event);
+ }
+
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/Address.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/Address.java
new file mode 100644
index 00000000..c6f45596
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/Address.java
@@ -0,0 +1,412 @@
+/**
+ * Autogenerated by Avro
+ *
+ * DO NOT EDIT DIRECTLY
+ */
+package org.springframework.modulith.events.scs.dtos.avro;
+
+import org.apache.avro.message.BinaryMessageDecoder;
+import org.apache.avro.message.BinaryMessageEncoder;
+import org.apache.avro.message.SchemaStore;
+import org.apache.avro.specific.SpecificData;
+import org.apache.avro.util.Utf8;
+
+@org.apache.avro.specific.AvroGenerated
+public class Address extends org.apache.avro.specific.SpecificRecordBase
+ implements org.apache.avro.specific.SpecificRecord {
+
+ private static final long serialVersionUID = 7121751969643202111L;
+
+ public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse(
+ "{\"type\":\"record\",\"name\":\"Address\",\"namespace\":\"org.springframework.modulith.events.scs.dtos.avro\",\"fields\":[{\"name\":\"street\",\"type\":\"string\"},{\"name\":\"city\",\"type\":\"string\"}]}");
+
+ public static org.apache.avro.Schema getClassSchema() {
+ return SCHEMA$;
+ }
+
+ private static final SpecificData MODEL$ = new SpecificData();
+
+ private static final BinaryMessageEncoder ENCODER = new BinaryMessageEncoder<>(MODEL$, SCHEMA$);
+
+ private static final BinaryMessageDecoder DECODER = new BinaryMessageDecoder<>(MODEL$, SCHEMA$);
+
+ /**
+ * Return the BinaryMessageEncoder instance used by this class.
+ * @return the message encoder used by this class
+ */
+ public static BinaryMessageEncoder getEncoder() {
+ return ENCODER;
+ }
+
+ /**
+ * Return the BinaryMessageDecoder instance used by this class.
+ * @return the message decoder used by this class
+ */
+ public static BinaryMessageDecoder getDecoder() {
+ return DECODER;
+ }
+
+ /**
+ * Create a new BinaryMessageDecoder instance for this class that uses the specified
+ * {@link SchemaStore}.
+ * @param resolver a {@link SchemaStore} used to find schemas by fingerprint
+ * @return a BinaryMessageDecoder instance for this class backed by the given
+ * SchemaStore
+ */
+ public static BinaryMessageDecoder createDecoder(SchemaStore resolver) {
+ return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver);
+ }
+
+ /**
+ * Serializes this Address to a ByteBuffer.
+ * @return a buffer holding the serialized data for this instance
+ * @throws java.io.IOException if this instance could not be serialized
+ */
+ public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException {
+ return ENCODER.encode(this);
+ }
+
+ /**
+ * Deserializes a Address from a ByteBuffer.
+ * @param b a byte buffer holding serialized data for an instance of this class
+ * @return a Address instance decoded from the given buffer
+ * @throws java.io.IOException if the given bytes could not be deserialized into an
+ * instance of this class
+ */
+ public static Address fromByteBuffer(java.nio.ByteBuffer b) throws java.io.IOException {
+ return DECODER.decode(b);
+ }
+
+ private CharSequence street;
+
+ private CharSequence city;
+
+ /**
+ * Default constructor. Note that this does not initialize fields to their default
+ * values from the schema. If that is desired then one should use
+ * newBuilder()
.
+ */
+ public Address() {
+ }
+
+ /**
+ * All-args constructor.
+ * @param street The new value for street
+ * @param city The new value for city
+ */
+ public Address(CharSequence street, CharSequence city) {
+ this.street = street;
+ this.city = city;
+ }
+
+ @Override
+ public SpecificData getSpecificData() {
+ return MODEL$;
+ }
+
+ @Override
+ public org.apache.avro.Schema getSchema() {
+ return SCHEMA$;
+ }
+
+ // Used by DatumWriter. Applications should not call.
+ @Override
+ public Object get(int field$) {
+ switch (field$) {
+ case 0:
+ return street;
+ case 1:
+ return city;
+ default:
+ throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ // Used by DatumReader. Applications should not call.
+ @Override
+ @SuppressWarnings(value = "unchecked")
+ public void put(int field$, Object value$) {
+ switch (field$) {
+ case 0:
+ street = (CharSequence) value$;
+ break;
+ case 1:
+ city = (CharSequence) value$;
+ break;
+ default:
+ throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ /**
+ * Gets the value of the 'street' field.
+ * @return The value of the 'street' field.
+ */
+ public CharSequence getStreet() {
+ return street;
+ }
+
+ /**
+ * Sets the value of the 'street' field.
+ * @param value the value to set.
+ */
+ public void setStreet(CharSequence value) {
+ this.street = value;
+ }
+
+ /**
+ * Gets the value of the 'city' field.
+ * @return The value of the 'city' field.
+ */
+ public CharSequence getCity() {
+ return city;
+ }
+
+ /**
+ * Sets the value of the 'city' field.
+ * @param value the value to set.
+ */
+ public void setCity(CharSequence value) {
+ this.city = value;
+ }
+
+ /**
+ * Creates a new Address RecordBuilder.
+ * @return A new Address RecordBuilder
+ */
+ public static Address.Builder newBuilder() {
+ return new Address.Builder();
+ }
+
+ /**
+ * Creates a new Address RecordBuilder by copying an existing Builder.
+ * @param other The existing builder to copy.
+ * @return A new Address RecordBuilder
+ */
+ public static Address.Builder newBuilder(
+ Address.Builder other) {
+ if (other == null) {
+ return new Address.Builder();
+ }
+ else {
+ return new Address.Builder(other);
+ }
+ }
+
+ /**
+ * Creates a new Address RecordBuilder by copying an existing Address instance.
+ * @param other The existing instance to copy.
+ * @return A new Address RecordBuilder
+ */
+ public static Address.Builder newBuilder(
+ Address other) {
+ if (other == null) {
+ return new Address.Builder();
+ }
+ else {
+ return new Address.Builder(other);
+ }
+ }
+
+ /**
+ * RecordBuilder for Address instances.
+ */
+ @org.apache.avro.specific.AvroGenerated
+ public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase
+ implements org.apache.avro.data.RecordBuilder {
+
+ private CharSequence street;
+
+ private CharSequence city;
+
+ /** Creates a new Builder */
+ private Builder() {
+ super(SCHEMA$, MODEL$);
+ }
+
+ /**
+ * Creates a Builder by copying an existing Builder.
+ * @param other The existing Builder to copy.
+ */
+ private Builder(Address.Builder other) {
+ super(other);
+ if (isValidValue(fields()[0], other.street)) {
+ this.street = data().deepCopy(fields()[0].schema(), other.street);
+ fieldSetFlags()[0] = other.fieldSetFlags()[0];
+ }
+ if (isValidValue(fields()[1], other.city)) {
+ this.city = data().deepCopy(fields()[1].schema(), other.city);
+ fieldSetFlags()[1] = other.fieldSetFlags()[1];
+ }
+ }
+
+ /**
+ * Creates a Builder by copying an existing Address instance
+ * @param other The existing instance to copy.
+ */
+ private Builder(Address other) {
+ super(SCHEMA$, MODEL$);
+ if (isValidValue(fields()[0], other.street)) {
+ this.street = data().deepCopy(fields()[0].schema(), other.street);
+ fieldSetFlags()[0] = true;
+ }
+ if (isValidValue(fields()[1], other.city)) {
+ this.city = data().deepCopy(fields()[1].schema(), other.city);
+ fieldSetFlags()[1] = true;
+ }
+ }
+
+ /**
+ * Gets the value of the 'street' field.
+ * @return The value.
+ */
+ public CharSequence getStreet() {
+ return street;
+ }
+
+ /**
+ * Sets the value of the 'street' field.
+ * @param value The value of 'street'.
+ * @return This builder.
+ */
+ public Address.Builder setStreet(CharSequence value) {
+ validate(fields()[0], value);
+ this.street = value;
+ fieldSetFlags()[0] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'street' field has been set.
+ * @return True if the 'street' field has been set, false otherwise.
+ */
+ public boolean hasStreet() {
+ return fieldSetFlags()[0];
+ }
+
+ /**
+ * Clears the value of the 'street' field.
+ * @return This builder.
+ */
+ public Address.Builder clearStreet() {
+ street = null;
+ fieldSetFlags()[0] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'city' field.
+ * @return The value.
+ */
+ public CharSequence getCity() {
+ return city;
+ }
+
+ /**
+ * Sets the value of the 'city' field.
+ * @param value The value of 'city'.
+ * @return This builder.
+ */
+ public Address.Builder setCity(CharSequence value) {
+ validate(fields()[1], value);
+ this.city = value;
+ fieldSetFlags()[1] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'city' field has been set.
+ * @return True if the 'city' field has been set, false otherwise.
+ */
+ public boolean hasCity() {
+ return fieldSetFlags()[1];
+ }
+
+ /**
+ * Clears the value of the 'city' field.
+ * @return This builder.
+ */
+ public Address.Builder clearCity() {
+ city = null;
+ fieldSetFlags()[1] = false;
+ return this;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Address build() {
+ try {
+ Address record = new Address();
+ record.street = fieldSetFlags()[0] ? this.street : (CharSequence) defaultValue(fields()[0]);
+ record.city = fieldSetFlags()[1] ? this.city : (CharSequence) defaultValue(fields()[1]);
+ return record;
+ }
+ catch (org.apache.avro.AvroMissingFieldException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new org.apache.avro.AvroRuntimeException(e);
+ }
+ }
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private static final org.apache.avro.io.DatumWriter WRITER$ = (org.apache.avro.io.DatumWriter) MODEL$
+ .createDatumWriter(SCHEMA$);
+
+ @Override
+ public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException {
+ WRITER$.write(this, SpecificData.getEncoder(out));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static final org.apache.avro.io.DatumReader READER$ = (org.apache.avro.io.DatumReader) MODEL$
+ .createDatumReader(SCHEMA$);
+
+ @Override
+ public void readExternal(java.io.ObjectInput in) throws java.io.IOException {
+ READER$.read(this, SpecificData.getDecoder(in));
+ }
+
+ @Override
+ protected boolean hasCustomCoders() {
+ return true;
+ }
+
+ @Override
+ public void customEncode(org.apache.avro.io.Encoder out) throws java.io.IOException {
+ out.writeString(this.street);
+
+ out.writeString(this.city);
+
+ }
+
+ @Override
+ public void customDecode(org.apache.avro.io.ResolvingDecoder in) throws java.io.IOException {
+ org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff();
+ if (fieldOrder == null) {
+ this.street = in.readString(this.street instanceof Utf8 ? (Utf8) this.street : null);
+
+ this.city = in.readString(this.city instanceof Utf8 ? (Utf8) this.city : null);
+
+ }
+ else {
+ for (int i = 0; i < 2; i++) {
+ switch (fieldOrder[i].pos()) {
+ case 0:
+ this.street = in.readString(this.street instanceof Utf8 ? (Utf8) this.street : null);
+ break;
+
+ case 1:
+ this.city = in.readString(this.city instanceof Utf8 ? (Utf8) this.city : null);
+ break;
+
+ default:
+ throw new java.io.IOException("Corrupt ResolvingDecoder.");
+ }
+ }
+ }
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/CustomerEvent.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/CustomerEvent.java
new file mode 100644
index 00000000..9e4cbc32
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/CustomerEvent.java
@@ -0,0 +1,896 @@
+/**
+ * Autogenerated by Avro
+ *
+ * DO NOT EDIT DIRECTLY
+ */
+package org.springframework.modulith.events.scs.dtos.avro;
+
+import org.apache.avro.message.BinaryMessageDecoder;
+import org.apache.avro.message.BinaryMessageEncoder;
+import org.apache.avro.message.SchemaStore;
+import org.apache.avro.specific.SpecificData;
+import org.apache.avro.util.Utf8;
+
+@org.apache.avro.specific.AvroGenerated
+public class CustomerEvent extends org.apache.avro.specific.SpecificRecordBase
+ implements org.apache.avro.specific.SpecificRecord {
+
+ private static final long serialVersionUID = -8546638608361810155L;
+
+ public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse(
+ "{\"type\":\"record\",\"name\":\"CustomerEvent\",\"namespace\":\"org.springframework.modulith.events.scs.dtos.avro\",\"fields\":[{\"name\":\"name\",\"type\":\"string\",\"doc\":\"Customer name\"},{\"name\":\"email\",\"type\":\"string\",\"doc\":\"\"},{\"name\":\"addresses\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"Address\",\"fields\":[{\"name\":\"street\",\"type\":\"string\"},{\"name\":\"city\",\"type\":\"string\"}]},\"java-class\":\"java.util.List\"}},{\"name\":\"id\",\"type\":[\"null\",\"long\"]},{\"name\":\"version\",\"type\":[\"null\",\"int\"]},{\"name\":\"paymentMethods\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"PaymentMethod\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"type\",\"type\":{\"type\":\"enum\",\"name\":\"PaymentMethodType\",\"symbols\":[\"VISA\",\"MASTERCARD\"]}},{\"name\":\"cardNumber\",\"type\":\"string\"}]},\"java-class\":\"java.util.List\"}}]}");
+
+ public static org.apache.avro.Schema getClassSchema() {
+ return SCHEMA$;
+ }
+
+ private static final SpecificData MODEL$ = new SpecificData();
+
+ private static final BinaryMessageEncoder ENCODER = new BinaryMessageEncoder<>(MODEL$, SCHEMA$);
+
+ private static final BinaryMessageDecoder DECODER = new BinaryMessageDecoder<>(MODEL$, SCHEMA$);
+
+ /**
+ * Return the BinaryMessageEncoder instance used by this class.
+ * @return the message encoder used by this class
+ */
+ public static BinaryMessageEncoder getEncoder() {
+ return ENCODER;
+ }
+
+ /**
+ * Return the BinaryMessageDecoder instance used by this class.
+ * @return the message decoder used by this class
+ */
+ public static BinaryMessageDecoder getDecoder() {
+ return DECODER;
+ }
+
+ /**
+ * Create a new BinaryMessageDecoder instance for this class that uses the specified
+ * {@link SchemaStore}.
+ * @param resolver a {@link SchemaStore} used to find schemas by fingerprint
+ * @return a BinaryMessageDecoder instance for this class backed by the given
+ * SchemaStore
+ */
+ public static BinaryMessageDecoder createDecoder(SchemaStore resolver) {
+ return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver);
+ }
+
+ /**
+ * Serializes this CustomerEvent to a ByteBuffer.
+ * @return a buffer holding the serialized data for this instance
+ * @throws java.io.IOException if this instance could not be serialized
+ */
+ public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException {
+ return ENCODER.encode(this);
+ }
+
+ /**
+ * Deserializes a CustomerEvent from a ByteBuffer.
+ * @param b a byte buffer holding serialized data for an instance of this class
+ * @return a CustomerEvent instance decoded from the given buffer
+ * @throws java.io.IOException if the given bytes could not be deserialized into an
+ * instance of this class
+ */
+ public static CustomerEvent fromByteBuffer(java.nio.ByteBuffer b) throws java.io.IOException {
+ return DECODER.decode(b);
+ }
+
+ /** Customer name */
+ private CharSequence name;
+
+ private CharSequence email;
+
+ private java.util.List addresses;
+
+ private Long id;
+
+ private Integer version;
+
+ private java.util.List paymentMethods;
+
+ /**
+ * Default constructor. Note that this does not initialize fields to their default
+ * values from the schema. If that is desired then one should use
+ * newBuilder()
.
+ */
+ public CustomerEvent() {
+ }
+
+ /**
+ * All-args constructor.
+ * @param name Customer name
+ * @param email The new value for email
+ * @param addresses The new value for addresses
+ * @param id The new value for id
+ * @param version The new value for version
+ * @param paymentMethods The new value for paymentMethods
+ */
+ public CustomerEvent(CharSequence name, CharSequence email,
+ java.util.List addresses, Long id, Integer version,
+ java.util.List paymentMethods) {
+ this.name = name;
+ this.email = email;
+ this.addresses = addresses;
+ this.id = id;
+ this.version = version;
+ this.paymentMethods = paymentMethods;
+ }
+
+ @Override
+ public SpecificData getSpecificData() {
+ return MODEL$;
+ }
+
+ @Override
+ public org.apache.avro.Schema getSchema() {
+ return SCHEMA$;
+ }
+
+ // Used by DatumWriter. Applications should not call.
+ @Override
+ public Object get(int field$) {
+ switch (field$) {
+ case 0:
+ return name;
+ case 1:
+ return email;
+ case 2:
+ return addresses;
+ case 3:
+ return id;
+ case 4:
+ return version;
+ case 5:
+ return paymentMethods;
+ default:
+ throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ // Used by DatumReader. Applications should not call.
+ @Override
+ @SuppressWarnings(value = "unchecked")
+ public void put(int field$, Object value$) {
+ switch (field$) {
+ case 0:
+ name = (CharSequence) value$;
+ break;
+ case 1:
+ email = (CharSequence) value$;
+ break;
+ case 2:
+ addresses = (java.util.List) value$;
+ break;
+ case 3:
+ id = (Long) value$;
+ break;
+ case 4:
+ version = (Integer) value$;
+ break;
+ case 5:
+ paymentMethods = (java.util.List) value$;
+ break;
+ default:
+ throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ /**
+ * Gets the value of the 'name' field.
+ * @return Customer name
+ */
+ public CharSequence getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the 'name' field. Customer name
+ * @param value the value to set.
+ */
+ public void setName(CharSequence value) {
+ this.name = value;
+ }
+
+ /**
+ * Gets the value of the 'email' field.
+ * @return The value of the 'email' field.
+ */
+ public CharSequence getEmail() {
+ return email;
+ }
+
+ /**
+ * Sets the value of the 'email' field.
+ * @param value the value to set.
+ */
+ public void setEmail(CharSequence value) {
+ this.email = value;
+ }
+
+ /**
+ * Gets the value of the 'addresses' field.
+ * @return The value of the 'addresses' field.
+ */
+ public java.util.List getAddresses() {
+ return addresses;
+ }
+
+ /**
+ * Sets the value of the 'addresses' field.
+ * @param value the value to set.
+ */
+ public void setAddresses(java.util.List value) {
+ this.addresses = value;
+ }
+
+ /**
+ * Gets the value of the 'id' field.
+ * @return The value of the 'id' field.
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the 'id' field.
+ * @param value the value to set.
+ */
+ public void setId(Long value) {
+ this.id = value;
+ }
+
+ /**
+ * Gets the value of the 'version' field.
+ * @return The value of the 'version' field.
+ */
+ public Integer getVersion() {
+ return version;
+ }
+
+ /**
+ * Sets the value of the 'version' field.
+ * @param value the value to set.
+ */
+ public void setVersion(Integer value) {
+ this.version = value;
+ }
+
+ /**
+ * Gets the value of the 'paymentMethods' field.
+ * @return The value of the 'paymentMethods' field.
+ */
+ public java.util.List getPaymentMethods() {
+ return paymentMethods;
+ }
+
+ /**
+ * Sets the value of the 'paymentMethods' field.
+ * @param value the value to set.
+ */
+ public void setPaymentMethods(java.util.List value) {
+ this.paymentMethods = value;
+ }
+
+ /**
+ * Creates a new CustomerEvent RecordBuilder.
+ * @return A new CustomerEvent RecordBuilder
+ */
+ public static CustomerEvent.Builder newBuilder() {
+ return new CustomerEvent.Builder();
+ }
+
+ /**
+ * Creates a new CustomerEvent RecordBuilder by copying an existing Builder.
+ * @param other The existing builder to copy.
+ * @return A new CustomerEvent RecordBuilder
+ */
+ public static CustomerEvent.Builder newBuilder(
+ CustomerEvent.Builder other) {
+ if (other == null) {
+ return new CustomerEvent.Builder();
+ }
+ else {
+ return new CustomerEvent.Builder(other);
+ }
+ }
+
+ /**
+ * Creates a new CustomerEvent RecordBuilder by copying an existing CustomerEvent
+ * instance.
+ * @param other The existing instance to copy.
+ * @return A new CustomerEvent RecordBuilder
+ */
+ public static CustomerEvent.Builder newBuilder(
+ CustomerEvent other) {
+ if (other == null) {
+ return new CustomerEvent.Builder();
+ }
+ else {
+ return new CustomerEvent.Builder(other);
+ }
+ }
+
+ /**
+ * RecordBuilder for CustomerEvent instances.
+ */
+ @org.apache.avro.specific.AvroGenerated
+ public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase
+ implements org.apache.avro.data.RecordBuilder {
+
+ /** Customer name */
+ private CharSequence name;
+
+ private CharSequence email;
+
+ private java.util.List addresses;
+
+ private Long id;
+
+ private Integer version;
+
+ private java.util.List paymentMethods;
+
+ /** Creates a new Builder */
+ private Builder() {
+ super(SCHEMA$, MODEL$);
+ }
+
+ /**
+ * Creates a Builder by copying an existing Builder.
+ * @param other The existing Builder to copy.
+ */
+ private Builder(CustomerEvent.Builder other) {
+ super(other);
+ if (isValidValue(fields()[0], other.name)) {
+ this.name = data().deepCopy(fields()[0].schema(), other.name);
+ fieldSetFlags()[0] = other.fieldSetFlags()[0];
+ }
+ if (isValidValue(fields()[1], other.email)) {
+ this.email = data().deepCopy(fields()[1].schema(), other.email);
+ fieldSetFlags()[1] = other.fieldSetFlags()[1];
+ }
+ if (isValidValue(fields()[2], other.addresses)) {
+ this.addresses = data().deepCopy(fields()[2].schema(), other.addresses);
+ fieldSetFlags()[2] = other.fieldSetFlags()[2];
+ }
+ if (isValidValue(fields()[3], other.id)) {
+ this.id = data().deepCopy(fields()[3].schema(), other.id);
+ fieldSetFlags()[3] = other.fieldSetFlags()[3];
+ }
+ if (isValidValue(fields()[4], other.version)) {
+ this.version = data().deepCopy(fields()[4].schema(), other.version);
+ fieldSetFlags()[4] = other.fieldSetFlags()[4];
+ }
+ if (isValidValue(fields()[5], other.paymentMethods)) {
+ this.paymentMethods = data().deepCopy(fields()[5].schema(), other.paymentMethods);
+ fieldSetFlags()[5] = other.fieldSetFlags()[5];
+ }
+ }
+
+ /**
+ * Creates a Builder by copying an existing CustomerEvent instance
+ * @param other The existing instance to copy.
+ */
+ private Builder(CustomerEvent other) {
+ super(SCHEMA$, MODEL$);
+ if (isValidValue(fields()[0], other.name)) {
+ this.name = data().deepCopy(fields()[0].schema(), other.name);
+ fieldSetFlags()[0] = true;
+ }
+ if (isValidValue(fields()[1], other.email)) {
+ this.email = data().deepCopy(fields()[1].schema(), other.email);
+ fieldSetFlags()[1] = true;
+ }
+ if (isValidValue(fields()[2], other.addresses)) {
+ this.addresses = data().deepCopy(fields()[2].schema(), other.addresses);
+ fieldSetFlags()[2] = true;
+ }
+ if (isValidValue(fields()[3], other.id)) {
+ this.id = data().deepCopy(fields()[3].schema(), other.id);
+ fieldSetFlags()[3] = true;
+ }
+ if (isValidValue(fields()[4], other.version)) {
+ this.version = data().deepCopy(fields()[4].schema(), other.version);
+ fieldSetFlags()[4] = true;
+ }
+ if (isValidValue(fields()[5], other.paymentMethods)) {
+ this.paymentMethods = data().deepCopy(fields()[5].schema(), other.paymentMethods);
+ fieldSetFlags()[5] = true;
+ }
+ }
+
+ /**
+ * Gets the value of the 'name' field. Customer name
+ * @return The value.
+ */
+ public CharSequence getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the 'name' field. Customer name
+ * @param value The value of 'name'.
+ * @return This builder.
+ */
+ public CustomerEvent.Builder setName(CharSequence value) {
+ validate(fields()[0], value);
+ this.name = value;
+ fieldSetFlags()[0] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'name' field has been set. Customer name
+ * @return True if the 'name' field has been set, false otherwise.
+ */
+ public boolean hasName() {
+ return fieldSetFlags()[0];
+ }
+
+ /**
+ * Clears the value of the 'name' field. Customer name
+ * @return This builder.
+ */
+ public CustomerEvent.Builder clearName() {
+ name = null;
+ fieldSetFlags()[0] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'email' field.
+ * @return The value.
+ */
+ public CharSequence getEmail() {
+ return email;
+ }
+
+ /**
+ * Sets the value of the 'email' field.
+ * @param value The value of 'email'.
+ * @return This builder.
+ */
+ public CustomerEvent.Builder setEmail(CharSequence value) {
+ validate(fields()[1], value);
+ this.email = value;
+ fieldSetFlags()[1] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'email' field has been set.
+ * @return True if the 'email' field has been set, false otherwise.
+ */
+ public boolean hasEmail() {
+ return fieldSetFlags()[1];
+ }
+
+ /**
+ * Clears the value of the 'email' field.
+ * @return This builder.
+ */
+ public CustomerEvent.Builder clearEmail() {
+ email = null;
+ fieldSetFlags()[1] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'addresses' field.
+ * @return The value.
+ */
+ public java.util.List getAddresses() {
+ return addresses;
+ }
+
+ /**
+ * Sets the value of the 'addresses' field.
+ * @param value The value of 'addresses'.
+ * @return This builder.
+ */
+ public CustomerEvent.Builder setAddresses(
+ java.util.List value) {
+ validate(fields()[2], value);
+ this.addresses = value;
+ fieldSetFlags()[2] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'addresses' field has been set.
+ * @return True if the 'addresses' field has been set, false otherwise.
+ */
+ public boolean hasAddresses() {
+ return fieldSetFlags()[2];
+ }
+
+ /**
+ * Clears the value of the 'addresses' field.
+ * @return This builder.
+ */
+ public CustomerEvent.Builder clearAddresses() {
+ addresses = null;
+ fieldSetFlags()[2] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'id' field.
+ * @return The value.
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the 'id' field.
+ * @param value The value of 'id'.
+ * @return This builder.
+ */
+ public CustomerEvent.Builder setId(Long value) {
+ validate(fields()[3], value);
+ this.id = value;
+ fieldSetFlags()[3] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'id' field has been set.
+ * @return True if the 'id' field has been set, false otherwise.
+ */
+ public boolean hasId() {
+ return fieldSetFlags()[3];
+ }
+
+ /**
+ * Clears the value of the 'id' field.
+ * @return This builder.
+ */
+ public CustomerEvent.Builder clearId() {
+ id = null;
+ fieldSetFlags()[3] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'version' field.
+ * @return The value.
+ */
+ public Integer getVersion() {
+ return version;
+ }
+
+ /**
+ * Sets the value of the 'version' field.
+ * @param value The value of 'version'.
+ * @return This builder.
+ */
+ public CustomerEvent.Builder setVersion(Integer value) {
+ validate(fields()[4], value);
+ this.version = value;
+ fieldSetFlags()[4] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'version' field has been set.
+ * @return True if the 'version' field has been set, false otherwise.
+ */
+ public boolean hasVersion() {
+ return fieldSetFlags()[4];
+ }
+
+ /**
+ * Clears the value of the 'version' field.
+ * @return This builder.
+ */
+ public CustomerEvent.Builder clearVersion() {
+ version = null;
+ fieldSetFlags()[4] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'paymentMethods' field.
+ * @return The value.
+ */
+ public java.util.List getPaymentMethods() {
+ return paymentMethods;
+ }
+
+ /**
+ * Sets the value of the 'paymentMethods' field.
+ * @param value The value of 'paymentMethods'.
+ * @return This builder.
+ */
+ public CustomerEvent.Builder setPaymentMethods(
+ java.util.List value) {
+ validate(fields()[5], value);
+ this.paymentMethods = value;
+ fieldSetFlags()[5] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'paymentMethods' field has been set.
+ * @return True if the 'paymentMethods' field has been set, false otherwise.
+ */
+ public boolean hasPaymentMethods() {
+ return fieldSetFlags()[5];
+ }
+
+ /**
+ * Clears the value of the 'paymentMethods' field.
+ * @return This builder.
+ */
+ public CustomerEvent.Builder clearPaymentMethods() {
+ paymentMethods = null;
+ fieldSetFlags()[5] = false;
+ return this;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public CustomerEvent build() {
+ try {
+ CustomerEvent record = new CustomerEvent();
+ record.name = fieldSetFlags()[0] ? this.name : (CharSequence) defaultValue(fields()[0]);
+ record.email = fieldSetFlags()[1] ? this.email : (CharSequence) defaultValue(fields()[1]);
+ record.addresses = fieldSetFlags()[2] ? this.addresses
+ : (java.util.List) defaultValue(
+ fields()[2]);
+ record.id = fieldSetFlags()[3] ? this.id : (Long) defaultValue(fields()[3]);
+ record.version = fieldSetFlags()[4] ? this.version : (Integer) defaultValue(fields()[4]);
+ record.paymentMethods = fieldSetFlags()[5] ? this.paymentMethods
+ : (java.util.List) defaultValue(
+ fields()[5]);
+ return record;
+ }
+ catch (org.apache.avro.AvroMissingFieldException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new org.apache.avro.AvroRuntimeException(e);
+ }
+ }
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private static final org.apache.avro.io.DatumWriter WRITER$ = (org.apache.avro.io.DatumWriter) MODEL$
+ .createDatumWriter(SCHEMA$);
+
+ @Override
+ public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException {
+ WRITER$.write(this, SpecificData.getEncoder(out));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static final org.apache.avro.io.DatumReader READER$ = (org.apache.avro.io.DatumReader) MODEL$
+ .createDatumReader(SCHEMA$);
+
+ @Override
+ public void readExternal(java.io.ObjectInput in) throws java.io.IOException {
+ READER$.read(this, SpecificData.getDecoder(in));
+ }
+
+ @Override
+ protected boolean hasCustomCoders() {
+ return true;
+ }
+
+ @Override
+ public void customEncode(org.apache.avro.io.Encoder out) throws java.io.IOException {
+ out.writeString(this.name);
+
+ out.writeString(this.email);
+
+ long size0 = this.addresses.size();
+ out.writeArrayStart();
+ out.setItemCount(size0);
+ long actualSize0 = 0;
+ for (Address e0 : this.addresses) {
+ actualSize0++;
+ out.startItem();
+ e0.customEncode(out);
+ }
+ out.writeArrayEnd();
+ if (actualSize0 != size0)
+ throw new java.util.ConcurrentModificationException(
+ "Array-size written was " + size0 + ", but element count was " + actualSize0 + ".");
+
+ if (this.id == null) {
+ out.writeIndex(0);
+ out.writeNull();
+ }
+ else {
+ out.writeIndex(1);
+ out.writeLong(this.id);
+ }
+
+ if (this.version == null) {
+ out.writeIndex(0);
+ out.writeNull();
+ }
+ else {
+ out.writeIndex(1);
+ out.writeInt(this.version);
+ }
+
+ long size1 = this.paymentMethods.size();
+ out.writeArrayStart();
+ out.setItemCount(size1);
+ long actualSize1 = 0;
+ for (PaymentMethod e1 : this.paymentMethods) {
+ actualSize1++;
+ out.startItem();
+ e1.customEncode(out);
+ }
+ out.writeArrayEnd();
+ if (actualSize1 != size1)
+ throw new java.util.ConcurrentModificationException(
+ "Array-size written was " + size1 + ", but element count was " + actualSize1 + ".");
+
+ }
+
+ @Override
+ public void customDecode(org.apache.avro.io.ResolvingDecoder in) throws java.io.IOException {
+ org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff();
+ if (fieldOrder == null) {
+ this.name = in.readString(this.name instanceof Utf8 ? (Utf8) this.name : null);
+
+ this.email = in.readString(this.email instanceof Utf8 ? (Utf8) this.email : null);
+
+ long size0 = in.readArrayStart();
+ java.util.List a0 = this.addresses;
+ if (a0 == null) {
+ a0 = new SpecificData.Array((int) size0,
+ SCHEMA$.getField("addresses").schema());
+ this.addresses = a0;
+ }
+ else
+ a0.clear();
+ SpecificData.Array ga0 = (a0 instanceof SpecificData.Array
+ ? (SpecificData.Array) a0 : null);
+ for (; 0 < size0; size0 = in.arrayNext()) {
+ for (; size0 != 0; size0--) {
+ Address e0 = (ga0 != null ? ga0.peek() : null);
+ if (e0 == null) {
+ e0 = new Address();
+ }
+ e0.customDecode(in);
+ a0.add(e0);
+ }
+ }
+
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.id = null;
+ }
+ else {
+ this.id = in.readLong();
+ }
+
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.version = null;
+ }
+ else {
+ this.version = in.readInt();
+ }
+
+ long size1 = in.readArrayStart();
+ java.util.List a1 = this.paymentMethods;
+ if (a1 == null) {
+ a1 = new SpecificData.Array((int) size1,
+ SCHEMA$.getField("paymentMethods").schema());
+ this.paymentMethods = a1;
+ }
+ else
+ a1.clear();
+ SpecificData.Array ga1 = (a1 instanceof SpecificData.Array
+ ? (SpecificData.Array) a1 : null);
+ for (; 0 < size1; size1 = in.arrayNext()) {
+ for (; size1 != 0; size1--) {
+ PaymentMethod e1 = (ga1 != null ? ga1.peek() : null);
+ if (e1 == null) {
+ e1 = new PaymentMethod();
+ }
+ e1.customDecode(in);
+ a1.add(e1);
+ }
+ }
+
+ }
+ else {
+ for (int i = 0; i < 6; i++) {
+ switch (fieldOrder[i].pos()) {
+ case 0:
+ this.name = in.readString(this.name instanceof Utf8 ? (Utf8) this.name : null);
+ break;
+
+ case 1:
+ this.email = in.readString(this.email instanceof Utf8 ? (Utf8) this.email : null);
+ break;
+
+ case 2:
+ long size0 = in.readArrayStart();
+ java.util.List a0 = this.addresses;
+ if (a0 == null) {
+ a0 = new SpecificData.Array(
+ (int) size0, SCHEMA$.getField("addresses").schema());
+ this.addresses = a0;
+ }
+ else
+ a0.clear();
+ SpecificData.Array ga0 = (a0 instanceof SpecificData.Array
+ ? (SpecificData.Array) a0 : null);
+ for (; 0 < size0; size0 = in.arrayNext()) {
+ for (; size0 != 0; size0--) {
+ Address e0 = (ga0 != null ? ga0.peek()
+ : null);
+ if (e0 == null) {
+ e0 = new Address();
+ }
+ e0.customDecode(in);
+ a0.add(e0);
+ }
+ }
+ break;
+
+ case 3:
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.id = null;
+ }
+ else {
+ this.id = in.readLong();
+ }
+ break;
+
+ case 4:
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.version = null;
+ }
+ else {
+ this.version = in.readInt();
+ }
+ break;
+
+ case 5:
+ long size1 = in.readArrayStart();
+ java.util.List a1 = this.paymentMethods;
+ if (a1 == null) {
+ a1 = new SpecificData.Array(
+ (int) size1, SCHEMA$.getField("paymentMethods").schema());
+ this.paymentMethods = a1;
+ }
+ else
+ a1.clear();
+ SpecificData.Array ga1 = (a1 instanceof SpecificData.Array
+ ? (SpecificData.Array) a1
+ : null);
+ for (; 0 < size1; size1 = in.arrayNext()) {
+ for (; size1 != 0; size1--) {
+ PaymentMethod e1 = (ga1 != null ? ga1.peek()
+ : null);
+ if (e1 == null) {
+ e1 = new PaymentMethod();
+ }
+ e1.customDecode(in);
+ a1.add(e1);
+ }
+ }
+ break;
+
+ default:
+ throw new java.io.IOException("Corrupt ResolvingDecoder.");
+ }
+ }
+ }
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/ExternalizedCustomerEvent.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/ExternalizedCustomerEvent.java
new file mode 100644
index 00000000..4c4e5d74
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/ExternalizedCustomerEvent.java
@@ -0,0 +1,898 @@
+/**
+ * Autogenerated by Avro
+ *
+ * DO NOT EDIT DIRECTLY
+ */
+package org.springframework.modulith.events.scs.dtos.avro;
+
+import org.apache.avro.message.BinaryMessageDecoder;
+import org.apache.avro.message.BinaryMessageEncoder;
+import org.apache.avro.message.SchemaStore;
+import org.apache.avro.specific.SpecificData;
+import org.apache.avro.util.Utf8;
+import org.springframework.modulith.events.Externalized;
+
+@Externalized("customers-avro-externalized-out-0::#{#this.getId()}")
+@org.apache.avro.specific.AvroGenerated
+public class ExternalizedCustomerEvent extends org.apache.avro.specific.SpecificRecordBase
+ implements org.apache.avro.specific.SpecificRecord {
+
+ private static final long serialVersionUID = -8546638608361810155L;
+
+ public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse(
+ "{\"type\":\"record\",\"name\":\"ExternalizedCustomerEvent\",\"namespace\":\"io.zenwave360.modulith.events.scs.dtos.avro\",\"fields\":[{\"name\":\"name\",\"type\":\"string\",\"doc\":\"Customer name\"},{\"name\":\"email\",\"type\":\"string\",\"doc\":\"\"},{\"name\":\"addresses\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"Address\",\"fields\":[{\"name\":\"street\",\"type\":\"string\"},{\"name\":\"city\",\"type\":\"string\"}]},\"java-class\":\"java.util.List\"}},{\"name\":\"id\",\"type\":[\"null\",\"long\"]},{\"name\":\"version\",\"type\":[\"null\",\"int\"]},{\"name\":\"paymentMethods\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"PaymentMethod\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"type\",\"type\":{\"type\":\"enum\",\"name\":\"PaymentMethodType\",\"symbols\":[\"VISA\",\"MASTERCARD\"]}},{\"name\":\"cardNumber\",\"type\":\"string\"}]},\"java-class\":\"java.util.List\"}}]}");
+
+ public static org.apache.avro.Schema getClassSchema() {
+ return SCHEMA$;
+ }
+
+ private static final SpecificData MODEL$ = new SpecificData();
+
+ private static final BinaryMessageEncoder ENCODER = new BinaryMessageEncoder<>(MODEL$, SCHEMA$);
+
+ private static final BinaryMessageDecoder DECODER = new BinaryMessageDecoder<>(MODEL$, SCHEMA$);
+
+ /**
+ * Return the BinaryMessageEncoder instance used by this class.
+ * @return the message encoder used by this class
+ */
+ public static BinaryMessageEncoder getEncoder() {
+ return ENCODER;
+ }
+
+ /**
+ * Return the BinaryMessageDecoder instance used by this class.
+ * @return the message decoder used by this class
+ */
+ public static BinaryMessageDecoder getDecoder() {
+ return DECODER;
+ }
+
+ /**
+ * Create a new BinaryMessageDecoder instance for this class that uses the specified
+ * {@link SchemaStore}.
+ * @param resolver a {@link SchemaStore} used to find schemas by fingerprint
+ * @return a BinaryMessageDecoder instance for this class backed by the given
+ * SchemaStore
+ */
+ public static BinaryMessageDecoder createDecoder(SchemaStore resolver) {
+ return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver);
+ }
+
+ /**
+ * Serializes this CustomerEvent to a ByteBuffer.
+ * @return a buffer holding the serialized data for this instance
+ * @throws java.io.IOException if this instance could not be serialized
+ */
+ public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException {
+ return ENCODER.encode(this);
+ }
+
+ /**
+ * Deserializes a CustomerEvent from a ByteBuffer.
+ * @param b a byte buffer holding serialized data for an instance of this class
+ * @return a CustomerEvent instance decoded from the given buffer
+ * @throws java.io.IOException if the given bytes could not be deserialized into an
+ * instance of this class
+ */
+ public static ExternalizedCustomerEvent fromByteBuffer(java.nio.ByteBuffer b) throws java.io.IOException {
+ return DECODER.decode(b);
+ }
+
+ /** Customer name */
+ private CharSequence name;
+
+ private CharSequence email;
+
+ private java.util.List addresses;
+
+ private Long id;
+
+ private Integer version;
+
+ private java.util.List paymentMethods;
+
+ /**
+ * Default constructor. Note that this does not initialize fields to their default
+ * values from the schema. If that is desired then one should use
+ * newBuilder()
.
+ */
+ public ExternalizedCustomerEvent() {
+ }
+
+ /**
+ * All-args constructor.
+ * @param name Customer name
+ * @param email The new value for email
+ * @param addresses The new value for addresses
+ * @param id The new value for id
+ * @param version The new value for version
+ * @param paymentMethods The new value for paymentMethods
+ */
+ public ExternalizedCustomerEvent(CharSequence name, CharSequence email,
+ java.util.List addresses, Long id, Integer version,
+ java.util.List paymentMethods) {
+ this.name = name;
+ this.email = email;
+ this.addresses = addresses;
+ this.id = id;
+ this.version = version;
+ this.paymentMethods = paymentMethods;
+ }
+
+ @Override
+ public SpecificData getSpecificData() {
+ return MODEL$;
+ }
+
+ @Override
+ public org.apache.avro.Schema getSchema() {
+ return SCHEMA$;
+ }
+
+ // Used by DatumWriter. Applications should not call.
+ @Override
+ public Object get(int field$) {
+ switch (field$) {
+ case 0:
+ return name;
+ case 1:
+ return email;
+ case 2:
+ return addresses;
+ case 3:
+ return id;
+ case 4:
+ return version;
+ case 5:
+ return paymentMethods;
+ default:
+ throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ // Used by DatumReader. Applications should not call.
+ @Override
+ @SuppressWarnings(value = "unchecked")
+ public void put(int field$, Object value$) {
+ switch (field$) {
+ case 0:
+ name = (CharSequence) value$;
+ break;
+ case 1:
+ email = (CharSequence) value$;
+ break;
+ case 2:
+ addresses = (java.util.List) value$;
+ break;
+ case 3:
+ id = (Long) value$;
+ break;
+ case 4:
+ version = (Integer) value$;
+ break;
+ case 5:
+ paymentMethods = (java.util.List) value$;
+ break;
+ default:
+ throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ /**
+ * Gets the value of the 'name' field.
+ * @return Customer name
+ */
+ public CharSequence getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the 'name' field. Customer name
+ * @param value the value to set.
+ */
+ public void setName(CharSequence value) {
+ this.name = value;
+ }
+
+ /**
+ * Gets the value of the 'email' field.
+ * @return The value of the 'email' field.
+ */
+ public CharSequence getEmail() {
+ return email;
+ }
+
+ /**
+ * Sets the value of the 'email' field.
+ * @param value the value to set.
+ */
+ public void setEmail(CharSequence value) {
+ this.email = value;
+ }
+
+ /**
+ * Gets the value of the 'addresses' field.
+ * @return The value of the 'addresses' field.
+ */
+ public java.util.List getAddresses() {
+ return addresses;
+ }
+
+ /**
+ * Sets the value of the 'addresses' field.
+ * @param value the value to set.
+ */
+ public void setAddresses(java.util.List value) {
+ this.addresses = value;
+ }
+
+ /**
+ * Gets the value of the 'id' field.
+ * @return The value of the 'id' field.
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the 'id' field.
+ * @param value the value to set.
+ */
+ public void setId(Long value) {
+ this.id = value;
+ }
+
+ /**
+ * Gets the value of the 'version' field.
+ * @return The value of the 'version' field.
+ */
+ public Integer getVersion() {
+ return version;
+ }
+
+ /**
+ * Sets the value of the 'version' field.
+ * @param value the value to set.
+ */
+ public void setVersion(Integer value) {
+ this.version = value;
+ }
+
+ /**
+ * Gets the value of the 'paymentMethods' field.
+ * @return The value of the 'paymentMethods' field.
+ */
+ public java.util.List getPaymentMethods() {
+ return paymentMethods;
+ }
+
+ /**
+ * Sets the value of the 'paymentMethods' field.
+ * @param value the value to set.
+ */
+ public void setPaymentMethods(java.util.List value) {
+ this.paymentMethods = value;
+ }
+
+ /**
+ * Creates a new CustomerEvent RecordBuilder.
+ * @return A new CustomerEvent RecordBuilder
+ */
+ public static ExternalizedCustomerEvent.Builder newBuilder() {
+ return new ExternalizedCustomerEvent.Builder();
+ }
+
+ /**
+ * Creates a new CustomerEvent RecordBuilder by copying an existing Builder.
+ * @param other The existing builder to copy.
+ * @return A new CustomerEvent RecordBuilder
+ */
+ public static ExternalizedCustomerEvent.Builder newBuilder(
+ ExternalizedCustomerEvent.Builder other) {
+ if (other == null) {
+ return new ExternalizedCustomerEvent.Builder();
+ }
+ else {
+ return new ExternalizedCustomerEvent.Builder(other);
+ }
+ }
+
+ /**
+ * Creates a new CustomerEvent RecordBuilder by copying an existing CustomerEvent
+ * instance.
+ * @param other The existing instance to copy.
+ * @return A new CustomerEvent RecordBuilder
+ */
+ public static ExternalizedCustomerEvent.Builder newBuilder(
+ ExternalizedCustomerEvent other) {
+ if (other == null) {
+ return new ExternalizedCustomerEvent.Builder();
+ }
+ else {
+ return new ExternalizedCustomerEvent.Builder(other);
+ }
+ }
+
+ /**
+ * RecordBuilder for CustomerEvent instances.
+ */
+ @org.apache.avro.specific.AvroGenerated
+ public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase
+ implements org.apache.avro.data.RecordBuilder {
+
+ /** Customer name */
+ private CharSequence name;
+
+ private CharSequence email;
+
+ private java.util.List addresses;
+
+ private Long id;
+
+ private Integer version;
+
+ private java.util.List paymentMethods;
+
+ /** Creates a new Builder */
+ private Builder() {
+ super(SCHEMA$, MODEL$);
+ }
+
+ /**
+ * Creates a Builder by copying an existing Builder.
+ * @param other The existing Builder to copy.
+ */
+ private Builder(ExternalizedCustomerEvent.Builder other) {
+ super(other);
+ if (isValidValue(fields()[0], other.name)) {
+ this.name = data().deepCopy(fields()[0].schema(), other.name);
+ fieldSetFlags()[0] = other.fieldSetFlags()[0];
+ }
+ if (isValidValue(fields()[1], other.email)) {
+ this.email = data().deepCopy(fields()[1].schema(), other.email);
+ fieldSetFlags()[1] = other.fieldSetFlags()[1];
+ }
+ if (isValidValue(fields()[2], other.addresses)) {
+ this.addresses = data().deepCopy(fields()[2].schema(), other.addresses);
+ fieldSetFlags()[2] = other.fieldSetFlags()[2];
+ }
+ if (isValidValue(fields()[3], other.id)) {
+ this.id = data().deepCopy(fields()[3].schema(), other.id);
+ fieldSetFlags()[3] = other.fieldSetFlags()[3];
+ }
+ if (isValidValue(fields()[4], other.version)) {
+ this.version = data().deepCopy(fields()[4].schema(), other.version);
+ fieldSetFlags()[4] = other.fieldSetFlags()[4];
+ }
+ if (isValidValue(fields()[5], other.paymentMethods)) {
+ this.paymentMethods = data().deepCopy(fields()[5].schema(), other.paymentMethods);
+ fieldSetFlags()[5] = other.fieldSetFlags()[5];
+ }
+ }
+
+ /**
+ * Creates a Builder by copying an existing CustomerEvent instance
+ * @param other The existing instance to copy.
+ */
+ private Builder(ExternalizedCustomerEvent other) {
+ super(SCHEMA$, MODEL$);
+ if (isValidValue(fields()[0], other.name)) {
+ this.name = data().deepCopy(fields()[0].schema(), other.name);
+ fieldSetFlags()[0] = true;
+ }
+ if (isValidValue(fields()[1], other.email)) {
+ this.email = data().deepCopy(fields()[1].schema(), other.email);
+ fieldSetFlags()[1] = true;
+ }
+ if (isValidValue(fields()[2], other.addresses)) {
+ this.addresses = data().deepCopy(fields()[2].schema(), other.addresses);
+ fieldSetFlags()[2] = true;
+ }
+ if (isValidValue(fields()[3], other.id)) {
+ this.id = data().deepCopy(fields()[3].schema(), other.id);
+ fieldSetFlags()[3] = true;
+ }
+ if (isValidValue(fields()[4], other.version)) {
+ this.version = data().deepCopy(fields()[4].schema(), other.version);
+ fieldSetFlags()[4] = true;
+ }
+ if (isValidValue(fields()[5], other.paymentMethods)) {
+ this.paymentMethods = data().deepCopy(fields()[5].schema(), other.paymentMethods);
+ fieldSetFlags()[5] = true;
+ }
+ }
+
+ /**
+ * Gets the value of the 'name' field. Customer name
+ * @return The value.
+ */
+ public CharSequence getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the 'name' field. Customer name
+ * @param value The value of 'name'.
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder setName(CharSequence value) {
+ validate(fields()[0], value);
+ this.name = value;
+ fieldSetFlags()[0] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'name' field has been set. Customer name
+ * @return True if the 'name' field has been set, false otherwise.
+ */
+ public boolean hasName() {
+ return fieldSetFlags()[0];
+ }
+
+ /**
+ * Clears the value of the 'name' field. Customer name
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder clearName() {
+ name = null;
+ fieldSetFlags()[0] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'email' field.
+ * @return The value.
+ */
+ public CharSequence getEmail() {
+ return email;
+ }
+
+ /**
+ * Sets the value of the 'email' field.
+ * @param value The value of 'email'.
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder setEmail(CharSequence value) {
+ validate(fields()[1], value);
+ this.email = value;
+ fieldSetFlags()[1] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'email' field has been set.
+ * @return True if the 'email' field has been set, false otherwise.
+ */
+ public boolean hasEmail() {
+ return fieldSetFlags()[1];
+ }
+
+ /**
+ * Clears the value of the 'email' field.
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder clearEmail() {
+ email = null;
+ fieldSetFlags()[1] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'addresses' field.
+ * @return The value.
+ */
+ public java.util.List getAddresses() {
+ return addresses;
+ }
+
+ /**
+ * Sets the value of the 'addresses' field.
+ * @param value The value of 'addresses'.
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder setAddresses(
+ java.util.List value) {
+ validate(fields()[2], value);
+ this.addresses = value;
+ fieldSetFlags()[2] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'addresses' field has been set.
+ * @return True if the 'addresses' field has been set, false otherwise.
+ */
+ public boolean hasAddresses() {
+ return fieldSetFlags()[2];
+ }
+
+ /**
+ * Clears the value of the 'addresses' field.
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder clearAddresses() {
+ addresses = null;
+ fieldSetFlags()[2] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'id' field.
+ * @return The value.
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the 'id' field.
+ * @param value The value of 'id'.
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder setId(Long value) {
+ validate(fields()[3], value);
+ this.id = value;
+ fieldSetFlags()[3] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'id' field has been set.
+ * @return True if the 'id' field has been set, false otherwise.
+ */
+ public boolean hasId() {
+ return fieldSetFlags()[3];
+ }
+
+ /**
+ * Clears the value of the 'id' field.
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder clearId() {
+ id = null;
+ fieldSetFlags()[3] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'version' field.
+ * @return The value.
+ */
+ public Integer getVersion() {
+ return version;
+ }
+
+ /**
+ * Sets the value of the 'version' field.
+ * @param value The value of 'version'.
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder setVersion(Integer value) {
+ validate(fields()[4], value);
+ this.version = value;
+ fieldSetFlags()[4] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'version' field has been set.
+ * @return True if the 'version' field has been set, false otherwise.
+ */
+ public boolean hasVersion() {
+ return fieldSetFlags()[4];
+ }
+
+ /**
+ * Clears the value of the 'version' field.
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder clearVersion() {
+ version = null;
+ fieldSetFlags()[4] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'paymentMethods' field.
+ * @return The value.
+ */
+ public java.util.List getPaymentMethods() {
+ return paymentMethods;
+ }
+
+ /**
+ * Sets the value of the 'paymentMethods' field.
+ * @param value The value of 'paymentMethods'.
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder setPaymentMethods(
+ java.util.List value) {
+ validate(fields()[5], value);
+ this.paymentMethods = value;
+ fieldSetFlags()[5] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'paymentMethods' field has been set.
+ * @return True if the 'paymentMethods' field has been set, false otherwise.
+ */
+ public boolean hasPaymentMethods() {
+ return fieldSetFlags()[5];
+ }
+
+ /**
+ * Clears the value of the 'paymentMethods' field.
+ * @return This builder.
+ */
+ public ExternalizedCustomerEvent.Builder clearPaymentMethods() {
+ paymentMethods = null;
+ fieldSetFlags()[5] = false;
+ return this;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public ExternalizedCustomerEvent build() {
+ try {
+ ExternalizedCustomerEvent record = new ExternalizedCustomerEvent();
+ record.name = fieldSetFlags()[0] ? this.name : (CharSequence) defaultValue(fields()[0]);
+ record.email = fieldSetFlags()[1] ? this.email : (CharSequence) defaultValue(fields()[1]);
+ record.addresses = fieldSetFlags()[2] ? this.addresses
+ : (java.util.List) defaultValue(
+ fields()[2]);
+ record.id = fieldSetFlags()[3] ? this.id : (Long) defaultValue(fields()[3]);
+ record.version = fieldSetFlags()[4] ? this.version : (Integer) defaultValue(fields()[4]);
+ record.paymentMethods = fieldSetFlags()[5] ? this.paymentMethods
+ : (java.util.List) defaultValue(
+ fields()[5]);
+ return record;
+ }
+ catch (org.apache.avro.AvroMissingFieldException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new org.apache.avro.AvroRuntimeException(e);
+ }
+ }
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private static final org.apache.avro.io.DatumWriter WRITER$ = (org.apache.avro.io.DatumWriter) MODEL$
+ .createDatumWriter(SCHEMA$);
+
+ @Override
+ public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException {
+ WRITER$.write(this, SpecificData.getEncoder(out));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static final org.apache.avro.io.DatumReader READER$ = (org.apache.avro.io.DatumReader) MODEL$
+ .createDatumReader(SCHEMA$);
+
+ @Override
+ public void readExternal(java.io.ObjectInput in) throws java.io.IOException {
+ READER$.read(this, SpecificData.getDecoder(in));
+ }
+
+ @Override
+ protected boolean hasCustomCoders() {
+ return true;
+ }
+
+ @Override
+ public void customEncode(org.apache.avro.io.Encoder out) throws java.io.IOException {
+ out.writeString(this.name);
+
+ out.writeString(this.email);
+
+ long size0 = this.addresses.size();
+ out.writeArrayStart();
+ out.setItemCount(size0);
+ long actualSize0 = 0;
+ for (Address e0 : this.addresses) {
+ actualSize0++;
+ out.startItem();
+ e0.customEncode(out);
+ }
+ out.writeArrayEnd();
+ if (actualSize0 != size0)
+ throw new java.util.ConcurrentModificationException(
+ "Array-size written was " + size0 + ", but element count was " + actualSize0 + ".");
+
+ if (this.id == null) {
+ out.writeIndex(0);
+ out.writeNull();
+ }
+ else {
+ out.writeIndex(1);
+ out.writeLong(this.id);
+ }
+
+ if (this.version == null) {
+ out.writeIndex(0);
+ out.writeNull();
+ }
+ else {
+ out.writeIndex(1);
+ out.writeInt(this.version);
+ }
+
+ long size1 = this.paymentMethods.size();
+ out.writeArrayStart();
+ out.setItemCount(size1);
+ long actualSize1 = 0;
+ for (PaymentMethod e1 : this.paymentMethods) {
+ actualSize1++;
+ out.startItem();
+ e1.customEncode(out);
+ }
+ out.writeArrayEnd();
+ if (actualSize1 != size1)
+ throw new java.util.ConcurrentModificationException(
+ "Array-size written was " + size1 + ", but element count was " + actualSize1 + ".");
+
+ }
+
+ @Override
+ public void customDecode(org.apache.avro.io.ResolvingDecoder in) throws java.io.IOException {
+ org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff();
+ if (fieldOrder == null) {
+ this.name = in.readString(this.name instanceof Utf8 ? (Utf8) this.name : null);
+
+ this.email = in.readString(this.email instanceof Utf8 ? (Utf8) this.email : null);
+
+ long size0 = in.readArrayStart();
+ java.util.List a0 = this.addresses;
+ if (a0 == null) {
+ a0 = new SpecificData.Array((int) size0,
+ SCHEMA$.getField("addresses").schema());
+ this.addresses = a0;
+ }
+ else
+ a0.clear();
+ SpecificData.Array ga0 = (a0 instanceof SpecificData.Array
+ ? (SpecificData.Array) a0 : null);
+ for (; 0 < size0; size0 = in.arrayNext()) {
+ for (; size0 != 0; size0--) {
+ Address e0 = (ga0 != null ? ga0.peek() : null);
+ if (e0 == null) {
+ e0 = new Address();
+ }
+ e0.customDecode(in);
+ a0.add(e0);
+ }
+ }
+
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.id = null;
+ }
+ else {
+ this.id = in.readLong();
+ }
+
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.version = null;
+ }
+ else {
+ this.version = in.readInt();
+ }
+
+ long size1 = in.readArrayStart();
+ java.util.List a1 = this.paymentMethods;
+ if (a1 == null) {
+ a1 = new SpecificData.Array((int) size1,
+ SCHEMA$.getField("paymentMethods").schema());
+ this.paymentMethods = a1;
+ }
+ else
+ a1.clear();
+ SpecificData.Array ga1 = (a1 instanceof SpecificData.Array
+ ? (SpecificData.Array) a1 : null);
+ for (; 0 < size1; size1 = in.arrayNext()) {
+ for (; size1 != 0; size1--) {
+ PaymentMethod e1 = (ga1 != null ? ga1.peek() : null);
+ if (e1 == null) {
+ e1 = new PaymentMethod();
+ }
+ e1.customDecode(in);
+ a1.add(e1);
+ }
+ }
+
+ }
+ else {
+ for (int i = 0; i < 6; i++) {
+ switch (fieldOrder[i].pos()) {
+ case 0:
+ this.name = in.readString(this.name instanceof Utf8 ? (Utf8) this.name : null);
+ break;
+
+ case 1:
+ this.email = in.readString(this.email instanceof Utf8 ? (Utf8) this.email : null);
+ break;
+
+ case 2:
+ long size0 = in.readArrayStart();
+ java.util.List a0 = this.addresses;
+ if (a0 == null) {
+ a0 = new SpecificData.Array(
+ (int) size0, SCHEMA$.getField("addresses").schema());
+ this.addresses = a0;
+ }
+ else
+ a0.clear();
+ SpecificData.Array ga0 = (a0 instanceof SpecificData.Array
+ ? (SpecificData.Array) a0 : null);
+ for (; 0 < size0; size0 = in.arrayNext()) {
+ for (; size0 != 0; size0--) {
+ Address e0 = (ga0 != null ? ga0.peek()
+ : null);
+ if (e0 == null) {
+ e0 = new Address();
+ }
+ e0.customDecode(in);
+ a0.add(e0);
+ }
+ }
+ break;
+
+ case 3:
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.id = null;
+ }
+ else {
+ this.id = in.readLong();
+ }
+ break;
+
+ case 4:
+ if (in.readIndex() != 1) {
+ in.readNull();
+ this.version = null;
+ }
+ else {
+ this.version = in.readInt();
+ }
+ break;
+
+ case 5:
+ long size1 = in.readArrayStart();
+ java.util.List a1 = this.paymentMethods;
+ if (a1 == null) {
+ a1 = new SpecificData.Array(
+ (int) size1, SCHEMA$.getField("paymentMethods").schema());
+ this.paymentMethods = a1;
+ }
+ else
+ a1.clear();
+ SpecificData.Array ga1 = (a1 instanceof SpecificData.Array
+ ? (SpecificData.Array) a1
+ : null);
+ for (; 0 < size1; size1 = in.arrayNext()) {
+ for (; size1 != 0; size1--) {
+ PaymentMethod e1 = (ga1 != null ? ga1.peek()
+ : null);
+ if (e1 == null) {
+ e1 = new PaymentMethod();
+ }
+ e1.customDecode(in);
+ a1.add(e1);
+ }
+ }
+ break;
+
+ default:
+ throw new java.io.IOException("Corrupt ResolvingDecoder.");
+ }
+ }
+ }
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethod.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethod.java
new file mode 100644
index 00000000..09d9873a
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethod.java
@@ -0,0 +1,499 @@
+/**
+ * Autogenerated by Avro
+ *
+ * DO NOT EDIT DIRECTLY
+ */
+package org.springframework.modulith.events.scs.dtos.avro;
+
+import org.apache.avro.message.BinaryMessageDecoder;
+import org.apache.avro.message.BinaryMessageEncoder;
+import org.apache.avro.message.SchemaStore;
+import org.apache.avro.specific.SpecificData;
+import org.apache.avro.util.Utf8;
+
+@org.apache.avro.specific.AvroGenerated
+public class PaymentMethod extends org.apache.avro.specific.SpecificRecordBase
+ implements org.apache.avro.specific.SpecificRecord {
+
+ private static final long serialVersionUID = -3545422545868948468L;
+
+ public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse(
+ "{\"type\":\"record\",\"name\":\"PaymentMethod\",\"namespace\":\"org.springframework.modulith.events.scs.dtos.avro\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"type\",\"type\":{\"type\":\"enum\",\"name\":\"PaymentMethodType\",\"symbols\":[\"VISA\",\"MASTERCARD\"]}},{\"name\":\"cardNumber\",\"type\":\"string\"}]}");
+
+ public static org.apache.avro.Schema getClassSchema() {
+ return SCHEMA$;
+ }
+
+ private static final SpecificData MODEL$ = new SpecificData();
+
+ private static final BinaryMessageEncoder ENCODER = new BinaryMessageEncoder<>(MODEL$, SCHEMA$);
+
+ private static final BinaryMessageDecoder DECODER = new BinaryMessageDecoder<>(MODEL$, SCHEMA$);
+
+ /**
+ * Return the BinaryMessageEncoder instance used by this class.
+ * @return the message encoder used by this class
+ */
+ public static BinaryMessageEncoder getEncoder() {
+ return ENCODER;
+ }
+
+ /**
+ * Return the BinaryMessageDecoder instance used by this class.
+ * @return the message decoder used by this class
+ */
+ public static BinaryMessageDecoder getDecoder() {
+ return DECODER;
+ }
+
+ /**
+ * Create a new BinaryMessageDecoder instance for this class that uses the specified
+ * {@link SchemaStore}.
+ * @param resolver a {@link SchemaStore} used to find schemas by fingerprint
+ * @return a BinaryMessageDecoder instance for this class backed by the given
+ * SchemaStore
+ */
+ public static BinaryMessageDecoder createDecoder(SchemaStore resolver) {
+ return new BinaryMessageDecoder<>(MODEL$, SCHEMA$, resolver);
+ }
+
+ /**
+ * Serializes this PaymentMethod to a ByteBuffer.
+ * @return a buffer holding the serialized data for this instance
+ * @throws java.io.IOException if this instance could not be serialized
+ */
+ public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException {
+ return ENCODER.encode(this);
+ }
+
+ /**
+ * Deserializes a PaymentMethod from a ByteBuffer.
+ * @param b a byte buffer holding serialized data for an instance of this class
+ * @return a PaymentMethod instance decoded from the given buffer
+ * @throws java.io.IOException if the given bytes could not be deserialized into an
+ * instance of this class
+ */
+ public static PaymentMethod fromByteBuffer(java.nio.ByteBuffer b) throws java.io.IOException {
+ return DECODER.decode(b);
+ }
+
+ private int id;
+
+ private PaymentMethodType type;
+
+ private CharSequence cardNumber;
+
+ /**
+ * Default constructor. Note that this does not initialize fields to their default
+ * values from the schema. If that is desired then one should use
+ * newBuilder()
.
+ */
+ public PaymentMethod() {
+ }
+
+ /**
+ * All-args constructor.
+ * @param id The new value for id
+ * @param type The new value for type
+ * @param cardNumber The new value for cardNumber
+ */
+ public PaymentMethod(Integer id, PaymentMethodType type,
+ CharSequence cardNumber) {
+ this.id = id;
+ this.type = type;
+ this.cardNumber = cardNumber;
+ }
+
+ @Override
+ public SpecificData getSpecificData() {
+ return MODEL$;
+ }
+
+ @Override
+ public org.apache.avro.Schema getSchema() {
+ return SCHEMA$;
+ }
+
+ // Used by DatumWriter. Applications should not call.
+ @Override
+ public Object get(int field$) {
+ switch (field$) {
+ case 0:
+ return id;
+ case 1:
+ return type;
+ case 2:
+ return cardNumber;
+ default:
+ throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ // Used by DatumReader. Applications should not call.
+ @Override
+ @SuppressWarnings(value = "unchecked")
+ public void put(int field$, Object value$) {
+ switch (field$) {
+ case 0:
+ id = (Integer) value$;
+ break;
+ case 1:
+ type = (PaymentMethodType) value$;
+ break;
+ case 2:
+ cardNumber = (CharSequence) value$;
+ break;
+ default:
+ throw new IndexOutOfBoundsException("Invalid index: " + field$);
+ }
+ }
+
+ /**
+ * Gets the value of the 'id' field.
+ * @return The value of the 'id' field.
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the 'id' field.
+ * @param value the value to set.
+ */
+ public void setId(int value) {
+ this.id = value;
+ }
+
+ /**
+ * Gets the value of the 'type' field.
+ * @return The value of the 'type' field.
+ */
+ public PaymentMethodType getType() {
+ return type;
+ }
+
+ /**
+ * Sets the value of the 'type' field.
+ * @param value the value to set.
+ */
+ public void setType(PaymentMethodType value) {
+ this.type = value;
+ }
+
+ /**
+ * Gets the value of the 'cardNumber' field.
+ * @return The value of the 'cardNumber' field.
+ */
+ public CharSequence getCardNumber() {
+ return cardNumber;
+ }
+
+ /**
+ * Sets the value of the 'cardNumber' field.
+ * @param value the value to set.
+ */
+ public void setCardNumber(CharSequence value) {
+ this.cardNumber = value;
+ }
+
+ /**
+ * Creates a new PaymentMethod RecordBuilder.
+ * @return A new PaymentMethod RecordBuilder
+ */
+ public static PaymentMethod.Builder newBuilder() {
+ return new PaymentMethod.Builder();
+ }
+
+ /**
+ * Creates a new PaymentMethod RecordBuilder by copying an existing Builder.
+ * @param other The existing builder to copy.
+ * @return A new PaymentMethod RecordBuilder
+ */
+ public static PaymentMethod.Builder newBuilder(
+ PaymentMethod.Builder other) {
+ if (other == null) {
+ return new PaymentMethod.Builder();
+ }
+ else {
+ return new PaymentMethod.Builder(other);
+ }
+ }
+
+ /**
+ * Creates a new PaymentMethod RecordBuilder by copying an existing PaymentMethod
+ * instance.
+ * @param other The existing instance to copy.
+ * @return A new PaymentMethod RecordBuilder
+ */
+ public static PaymentMethod.Builder newBuilder(
+ PaymentMethod other) {
+ if (other == null) {
+ return new PaymentMethod.Builder();
+ }
+ else {
+ return new PaymentMethod.Builder(other);
+ }
+ }
+
+ /**
+ * RecordBuilder for PaymentMethod instances.
+ */
+ @org.apache.avro.specific.AvroGenerated
+ public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase
+ implements org.apache.avro.data.RecordBuilder {
+
+ private int id;
+
+ private PaymentMethodType type;
+
+ private CharSequence cardNumber;
+
+ /** Creates a new Builder */
+ private Builder() {
+ super(SCHEMA$, MODEL$);
+ }
+
+ /**
+ * Creates a Builder by copying an existing Builder.
+ * @param other The existing Builder to copy.
+ */
+ private Builder(PaymentMethod.Builder other) {
+ super(other);
+ if (isValidValue(fields()[0], other.id)) {
+ this.id = data().deepCopy(fields()[0].schema(), other.id);
+ fieldSetFlags()[0] = other.fieldSetFlags()[0];
+ }
+ if (isValidValue(fields()[1], other.type)) {
+ this.type = data().deepCopy(fields()[1].schema(), other.type);
+ fieldSetFlags()[1] = other.fieldSetFlags()[1];
+ }
+ if (isValidValue(fields()[2], other.cardNumber)) {
+ this.cardNumber = data().deepCopy(fields()[2].schema(), other.cardNumber);
+ fieldSetFlags()[2] = other.fieldSetFlags()[2];
+ }
+ }
+
+ /**
+ * Creates a Builder by copying an existing PaymentMethod instance
+ * @param other The existing instance to copy.
+ */
+ private Builder(PaymentMethod other) {
+ super(SCHEMA$, MODEL$);
+ if (isValidValue(fields()[0], other.id)) {
+ this.id = data().deepCopy(fields()[0].schema(), other.id);
+ fieldSetFlags()[0] = true;
+ }
+ if (isValidValue(fields()[1], other.type)) {
+ this.type = data().deepCopy(fields()[1].schema(), other.type);
+ fieldSetFlags()[1] = true;
+ }
+ if (isValidValue(fields()[2], other.cardNumber)) {
+ this.cardNumber = data().deepCopy(fields()[2].schema(), other.cardNumber);
+ fieldSetFlags()[2] = true;
+ }
+ }
+
+ /**
+ * Gets the value of the 'id' field.
+ * @return The value.
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the 'id' field.
+ * @param value The value of 'id'.
+ * @return This builder.
+ */
+ public PaymentMethod.Builder setId(int value) {
+ validate(fields()[0], value);
+ this.id = value;
+ fieldSetFlags()[0] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'id' field has been set.
+ * @return True if the 'id' field has been set, false otherwise.
+ */
+ public boolean hasId() {
+ return fieldSetFlags()[0];
+ }
+
+ /**
+ * Clears the value of the 'id' field.
+ * @return This builder.
+ */
+ public PaymentMethod.Builder clearId() {
+ fieldSetFlags()[0] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'type' field.
+ * @return The value.
+ */
+ public PaymentMethodType getType() {
+ return type;
+ }
+
+ /**
+ * Sets the value of the 'type' field.
+ * @param value The value of 'type'.
+ * @return This builder.
+ */
+ public PaymentMethod.Builder setType(
+ PaymentMethodType value) {
+ validate(fields()[1], value);
+ this.type = value;
+ fieldSetFlags()[1] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'type' field has been set.
+ * @return True if the 'type' field has been set, false otherwise.
+ */
+ public boolean hasType() {
+ return fieldSetFlags()[1];
+ }
+
+ /**
+ * Clears the value of the 'type' field.
+ * @return This builder.
+ */
+ public PaymentMethod.Builder clearType() {
+ type = null;
+ fieldSetFlags()[1] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'cardNumber' field.
+ * @return The value.
+ */
+ public CharSequence getCardNumber() {
+ return cardNumber;
+ }
+
+ /**
+ * Sets the value of the 'cardNumber' field.
+ * @param value The value of 'cardNumber'.
+ * @return This builder.
+ */
+ public PaymentMethod.Builder setCardNumber(CharSequence value) {
+ validate(fields()[2], value);
+ this.cardNumber = value;
+ fieldSetFlags()[2] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'cardNumber' field has been set.
+ * @return True if the 'cardNumber' field has been set, false otherwise.
+ */
+ public boolean hasCardNumber() {
+ return fieldSetFlags()[2];
+ }
+
+ /**
+ * Clears the value of the 'cardNumber' field.
+ * @return This builder.
+ */
+ public PaymentMethod.Builder clearCardNumber() {
+ cardNumber = null;
+ fieldSetFlags()[2] = false;
+ return this;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public PaymentMethod build() {
+ try {
+ PaymentMethod record = new PaymentMethod();
+ record.id = fieldSetFlags()[0] ? this.id : (Integer) defaultValue(fields()[0]);
+ record.type = fieldSetFlags()[1] ? this.type
+ : (PaymentMethodType) defaultValue(fields()[1]);
+ record.cardNumber = fieldSetFlags()[2] ? this.cardNumber : (CharSequence) defaultValue(fields()[2]);
+ return record;
+ }
+ catch (org.apache.avro.AvroMissingFieldException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new org.apache.avro.AvroRuntimeException(e);
+ }
+ }
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private static final org.apache.avro.io.DatumWriter WRITER$ = (org.apache.avro.io.DatumWriter) MODEL$
+ .createDatumWriter(SCHEMA$);
+
+ @Override
+ public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException {
+ WRITER$.write(this, SpecificData.getEncoder(out));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static final org.apache.avro.io.DatumReader READER$ = (org.apache.avro.io.DatumReader) MODEL$
+ .createDatumReader(SCHEMA$);
+
+ @Override
+ public void readExternal(java.io.ObjectInput in) throws java.io.IOException {
+ READER$.read(this, SpecificData.getDecoder(in));
+ }
+
+ @Override
+ protected boolean hasCustomCoders() {
+ return true;
+ }
+
+ @Override
+ public void customEncode(org.apache.avro.io.Encoder out) throws java.io.IOException {
+ out.writeInt(this.id);
+
+ out.writeEnum(this.type.ordinal());
+
+ out.writeString(this.cardNumber);
+
+ }
+
+ @Override
+ public void customDecode(org.apache.avro.io.ResolvingDecoder in) throws java.io.IOException {
+ org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff();
+ if (fieldOrder == null) {
+ this.id = in.readInt();
+
+ this.type = PaymentMethodType.values()[in.readEnum()];
+
+ this.cardNumber = in.readString(this.cardNumber instanceof Utf8 ? (Utf8) this.cardNumber : null);
+
+ }
+ else {
+ for (int i = 0; i < 3; i++) {
+ switch (fieldOrder[i].pos()) {
+ case 0:
+ this.id = in.readInt();
+ break;
+
+ case 1:
+ this.type = PaymentMethodType.values()[in
+ .readEnum()];
+ break;
+
+ case 2:
+ this.cardNumber = in
+ .readString(this.cardNumber instanceof Utf8 ? (Utf8) this.cardNumber : null);
+ break;
+
+ default:
+ throw new java.io.IOException("Corrupt ResolvingDecoder.");
+ }
+ }
+ }
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethodType.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethodType.java
new file mode 100644
index 00000000..5294ee94
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/avro/PaymentMethodType.java
@@ -0,0 +1,25 @@
+/**
+ * Autogenerated by Avro
+ *
+ * DO NOT EDIT DIRECTLY
+ */
+package org.springframework.modulith.events.scs.dtos.avro;
+
+@org.apache.avro.specific.AvroGenerated
+public enum PaymentMethodType implements org.apache.avro.generic.GenericEnumSymbol {
+
+ VISA, MASTERCARD;
+
+ public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse(
+ "{\"type\":\"enum\",\"name\":\"PaymentMethodType\",\"namespace\":\"org.springframework.modulith.events.scs.dtos.avro\",\"symbols\":[\"VISA\",\"MASTERCARD\"]}");
+
+ public static org.apache.avro.Schema getClassSchema() {
+ return SCHEMA$;
+ }
+
+ @Override
+ public org.apache.avro.Schema getSchema() {
+ return SCHEMA$;
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/Address.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/Address.java
new file mode 100644
index 00000000..e94c99db
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/Address.java
@@ -0,0 +1,228 @@
+
+package org.springframework.modulith.events.scs.dtos.json;
+
+import com.fasterxml.jackson.annotation.*;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonPropertyOrder({ "street", "city" })
+public class Address implements Serializable {
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("street")
+ @Size(max = 254)
+ @NotNull
+ private String street;
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("city")
+ @Size(max = 254)
+ @NotNull
+ private String city;
+
+ @JsonIgnore
+ @Valid
+ private Map additionalProperties = new LinkedHashMap();
+
+ protected final static Object NOT_FOUND_VALUE = new Object();
+
+ private final static long serialVersionUID = -2054487487628445756L;
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("street")
+ public String getStreet() {
+ return street;
+ }
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("street")
+ public void setStreet(String street) {
+ this.street = street;
+ }
+
+ public Address withStreet(String street) {
+ this.street = street;
+ return this;
+ }
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("city")
+ public String getCity() {
+ return city;
+ }
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("city")
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public Address withCity(String city) {
+ this.city = city;
+ return this;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ public Address withAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ return this;
+ }
+
+ protected boolean declaredProperty(String name, Object value) {
+ if ("street".equals(name)) {
+ if (value instanceof String) {
+ setStreet(((String) value));
+ }
+ else {
+ throw new IllegalArgumentException(("property \"street\" is of type \"java.lang.String\", but got "
+ + value.getClass().toString()));
+ }
+ return true;
+ }
+ else {
+ if ("city".equals(name)) {
+ if (value instanceof String) {
+ setCity(((String) value));
+ }
+ else {
+ throw new IllegalArgumentException(("property \"city\" is of type \"java.lang.String\", but got "
+ + value.getClass().toString()));
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ }
+
+ protected Object declaredPropertyOrNotFound(String name, Object notFoundValue) {
+ if ("street".equals(name)) {
+ return getStreet();
+ }
+ else {
+ if ("city".equals(name)) {
+ return getCity();
+ }
+ else {
+ return notFoundValue;
+ }
+ }
+ }
+
+ @SuppressWarnings({ "unchecked" })
+ public T get(String name) {
+ Object value = declaredPropertyOrNotFound(name, Address.NOT_FOUND_VALUE);
+ if (Address.NOT_FOUND_VALUE != value) {
+ return ((T) value);
+ }
+ else {
+ return ((T) getAdditionalProperties().get(name));
+ }
+ }
+
+ public void set(String name, Object value) {
+ if (!declaredProperty(name, value)) {
+ getAdditionalProperties().put(name, ((Object) value));
+ }
+ }
+
+ public Address with(String name, Object value) {
+ if (!declaredProperty(name, value)) {
+ getAdditionalProperties().put(name, ((Object) value));
+ }
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Address.class.getName())
+ .append('@')
+ .append(Integer.toHexString(System.identityHashCode(this)))
+ .append('[');
+ sb.append("street");
+ sb.append('=');
+ sb.append(((this.street == null) ? "" : this.street));
+ sb.append(',');
+ sb.append("city");
+ sb.append('=');
+ sb.append(((this.city == null) ? "" : this.city));
+ sb.append(',');
+ sb.append("additionalProperties");
+ sb.append('=');
+ sb.append(((this.additionalProperties == null) ? "" : this.additionalProperties));
+ sb.append(',');
+ if (sb.charAt((sb.length() - 1)) == ',') {
+ sb.setCharAt((sb.length() - 1), ']');
+ }
+ else {
+ sb.append(']');
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = ((result * 31) + ((this.additionalProperties == null) ? 0 : this.additionalProperties.hashCode()));
+ result = ((result * 31) + ((this.city == null) ? 0 : this.city.hashCode()));
+ result = ((result * 31) + ((this.street == null) ? 0 : this.street.hashCode()));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof Address) == false) {
+ return false;
+ }
+ Address rhs = ((Address) other);
+ return ((((this.additionalProperties == rhs.additionalProperties)
+ || ((this.additionalProperties != null) && this.additionalProperties.equals(rhs.additionalProperties)))
+ && ((this.city == rhs.city) || ((this.city != null) && this.city.equals(rhs.city))))
+ && ((this.street == rhs.street) || ((this.street != null) && this.street.equals(rhs.street))));
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/CustomerEvent.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/CustomerEvent.java
new file mode 100644
index 00000000..b220dcef
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/CustomerEvent.java
@@ -0,0 +1,402 @@
+
+package org.springframework.modulith.events.scs.dtos.json;
+
+import com.fasterxml.jackson.annotation.*;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonPropertyOrder({ "name", "email", "addresses", "id", "version", "paymentMethods" })
+public class CustomerEvent implements Serializable {
+
+ /**
+ * Customer name (Required)
+ *
+ */
+ @JsonProperty("name")
+ @JsonPropertyDescription("Customer name")
+ @Size(max = 254)
+ @NotNull
+ private String name;
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("email")
+ @Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}")
+ @Size(max = 254)
+ @NotNull
+ private String email;
+
+ @JsonProperty("addresses")
+ @Valid
+ private List addresses = new ArrayList();
+
+ @JsonProperty("id")
+ private Long id;
+
+ @JsonProperty("version")
+ private Long version;
+
+ @JsonProperty("paymentMethods")
+ @Valid
+ private List paymentMethods = new ArrayList();
+
+ @JsonIgnore
+ @Valid
+ private Map additionalProperties = new LinkedHashMap();
+
+ protected final static Object NOT_FOUND_VALUE = new Object();
+
+ private final static long serialVersionUID = 2415813790372213850L;
+
+ /**
+ * Customer name (Required)
+ *
+ */
+ @JsonProperty("name")
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Customer name (Required)
+ *
+ */
+ @JsonProperty("name")
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public CustomerEvent withName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("email")
+ public String getEmail() {
+ return email;
+ }
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("email")
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public CustomerEvent withEmail(String email) {
+ this.email = email;
+ return this;
+ }
+
+ @JsonProperty("addresses")
+ public List getAddresses() {
+ return addresses;
+ }
+
+ @JsonProperty("addresses")
+ public void setAddresses(List addresses) {
+ this.addresses = addresses;
+ }
+
+ public CustomerEvent withAddresses(List addresses) {
+ this.addresses = addresses;
+ return this;
+ }
+
+ @JsonProperty("id")
+ public Long getId() {
+ return id;
+ }
+
+ @JsonProperty("id")
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public CustomerEvent withId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ @JsonProperty("version")
+ public Long getVersion() {
+ return version;
+ }
+
+ @JsonProperty("version")
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+
+ public CustomerEvent withVersion(Long version) {
+ this.version = version;
+ return this;
+ }
+
+ @JsonProperty("paymentMethods")
+ public List getPaymentMethods() {
+ return paymentMethods;
+ }
+
+ @JsonProperty("paymentMethods")
+ public void setPaymentMethods(List paymentMethods) {
+ this.paymentMethods = paymentMethods;
+ }
+
+ public CustomerEvent withPaymentMethods(List paymentMethods) {
+ this.paymentMethods = paymentMethods;
+ return this;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ public CustomerEvent withAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ return this;
+ }
+
+ protected boolean declaredProperty(String name, Object value) {
+ if ("name".equals(name)) {
+ if (value instanceof String) {
+ setName(((String) value));
+ }
+ else {
+ throw new IllegalArgumentException(
+ ("property \"name\" is of type \"java.lang.String\", but got " + value.getClass().toString()));
+ }
+ return true;
+ }
+ else {
+ if ("email".equals(name)) {
+ if (value instanceof String) {
+ setEmail(((String) value));
+ }
+ else {
+ throw new IllegalArgumentException(("property \"email\" is of type \"java.lang.String\", but got "
+ + value.getClass().toString()));
+ }
+ return true;
+ }
+ else {
+ if ("addresses".equals(name)) {
+ if (value instanceof List) {
+ setAddresses(((List) value));
+ }
+ else {
+ throw new IllegalArgumentException(
+ ("property \"addresses\" is of type \"java.util.List\", but got "
+ + value.getClass().toString()));
+ }
+ return true;
+ }
+ else {
+ if ("id".equals(name)) {
+ if (value instanceof Long) {
+ setId(((Long) value));
+ }
+ else {
+ throw new IllegalArgumentException(
+ ("property \"id\" is of type \"java.lang.Long\", but got "
+ + value.getClass().toString()));
+ }
+ return true;
+ }
+ else {
+ if ("version".equals(name)) {
+ if (value instanceof Long) {
+ setVersion(((Long) value));
+ }
+ else {
+ throw new IllegalArgumentException(
+ ("property \"version\" is of type \"java.lang.Long\", but got "
+ + value.getClass().toString()));
+ }
+ return true;
+ }
+ else {
+ if ("paymentMethods".equals(name)) {
+ if (value instanceof List) {
+ setPaymentMethods(((List) value));
+ }
+ else {
+ throw new IllegalArgumentException(
+ ("property \"paymentMethods\" is of type \"java.util.List\", but got "
+ + value.getClass().toString()));
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected Object declaredPropertyOrNotFound(String name, Object notFoundValue) {
+ if ("name".equals(name)) {
+ return getName();
+ }
+ else {
+ if ("email".equals(name)) {
+ return getEmail();
+ }
+ else {
+ if ("addresses".equals(name)) {
+ return getAddresses();
+ }
+ else {
+ if ("id".equals(name)) {
+ return getId();
+ }
+ else {
+ if ("version".equals(name)) {
+ return getVersion();
+ }
+ else {
+ if ("paymentMethods".equals(name)) {
+ return getPaymentMethods();
+ }
+ else {
+ return notFoundValue;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings({ "unchecked" })
+ public T get(String name) {
+ Object value = declaredPropertyOrNotFound(name, CustomerEvent.NOT_FOUND_VALUE);
+ if (CustomerEvent.NOT_FOUND_VALUE != value) {
+ return ((T) value);
+ }
+ else {
+ return ((T) getAdditionalProperties().get(name));
+ }
+ }
+
+ public void set(String name, Object value) {
+ if (!declaredProperty(name, value)) {
+ getAdditionalProperties().put(name, ((Object) value));
+ }
+ }
+
+ public CustomerEvent with(String name, Object value) {
+ if (!declaredProperty(name, value)) {
+ getAdditionalProperties().put(name, ((Object) value));
+ }
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(CustomerEvent.class.getName())
+ .append('@')
+ .append(Integer.toHexString(System.identityHashCode(this)))
+ .append('[');
+ sb.append("name");
+ sb.append('=');
+ sb.append(((this.name == null) ? "" : this.name));
+ sb.append(',');
+ sb.append("email");
+ sb.append('=');
+ sb.append(((this.email == null) ? "" : this.email));
+ sb.append(',');
+ sb.append("addresses");
+ sb.append('=');
+ sb.append(((this.addresses == null) ? "" : this.addresses));
+ sb.append(',');
+ sb.append("id");
+ sb.append('=');
+ sb.append(((this.id == null) ? "" : this.id));
+ sb.append(',');
+ sb.append("version");
+ sb.append('=');
+ sb.append(((this.version == null) ? "" : this.version));
+ sb.append(',');
+ sb.append("paymentMethods");
+ sb.append('=');
+ sb.append(((this.paymentMethods == null) ? "" : this.paymentMethods));
+ sb.append(',');
+ sb.append("additionalProperties");
+ sb.append('=');
+ sb.append(((this.additionalProperties == null) ? "" : this.additionalProperties));
+ sb.append(',');
+ if (sb.charAt((sb.length() - 1)) == ',') {
+ sb.setCharAt((sb.length() - 1), ']');
+ }
+ else {
+ sb.append(']');
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = ((result * 31) + ((this.addresses == null) ? 0 : this.addresses.hashCode()));
+ result = ((result * 31) + ((this.paymentMethods == null) ? 0 : this.paymentMethods.hashCode()));
+ result = ((result * 31) + ((this.name == null) ? 0 : this.name.hashCode()));
+ result = ((result * 31) + ((this.id == null) ? 0 : this.id.hashCode()));
+ result = ((result * 31) + ((this.additionalProperties == null) ? 0 : this.additionalProperties.hashCode()));
+ result = ((result * 31) + ((this.version == null) ? 0 : this.version.hashCode()));
+ result = ((result * 31) + ((this.email == null) ? 0 : this.email.hashCode()));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof CustomerEvent) == false) {
+ return false;
+ }
+ CustomerEvent rhs = ((CustomerEvent) other);
+ return ((((((((this.addresses == rhs.addresses)
+ || ((this.addresses != null) && this.addresses.equals(rhs.addresses)))
+ && ((this.paymentMethods == rhs.paymentMethods)
+ || ((this.paymentMethods != null) && this.paymentMethods.equals(rhs.paymentMethods))))
+ && ((this.name == rhs.name) || ((this.name != null) && this.name.equals(rhs.name))))
+ && ((this.id == rhs.id) || ((this.id != null) && this.id.equals(rhs.id))))
+ && ((this.additionalProperties == rhs.additionalProperties) || ((this.additionalProperties != null)
+ && this.additionalProperties.equals(rhs.additionalProperties))))
+ && ((this.version == rhs.version) || ((this.version != null) && this.version.equals(rhs.version))))
+ && ((this.email == rhs.email) || ((this.email != null) && this.email.equals(rhs.email))));
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/ExternalizedCustomerEvent.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/ExternalizedCustomerEvent.java
new file mode 100644
index 00000000..6742d198
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/ExternalizedCustomerEvent.java
@@ -0,0 +1,8 @@
+package org.springframework.modulith.events.scs.dtos.json;
+
+import org.springframework.modulith.events.Externalized;
+
+@Externalized("customers-json-externalized-out-0::#{#this.getName()}")
+public class ExternalizedCustomerEvent extends CustomerEvent {
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethod.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethod.java
new file mode 100644
index 00000000..f7d1dd51
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethod.java
@@ -0,0 +1,269 @@
+
+package org.springframework.modulith.events.scs.dtos.json;
+
+import com.fasterxml.jackson.annotation.*;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonPropertyOrder({ "id", "type", "cardNumber" })
+public class PaymentMethod implements Serializable {
+
+ @JsonProperty("id")
+ private Long id;
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("type")
+ @NotNull
+ private PaymentMethodType type;
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("cardNumber")
+ @NotNull
+ private String cardNumber;
+
+ @JsonIgnore
+ @Valid
+ private Map additionalProperties = new LinkedHashMap();
+
+ protected final static Object NOT_FOUND_VALUE = new Object();
+
+ private final static long serialVersionUID = -8629217294484723648L;
+
+ @JsonProperty("id")
+ public Long getId() {
+ return id;
+ }
+
+ @JsonProperty("id")
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public PaymentMethod withId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("type")
+ public PaymentMethodType getType() {
+ return type;
+ }
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("type")
+ public void setType(PaymentMethodType type) {
+ this.type = type;
+ }
+
+ public PaymentMethod withType(PaymentMethodType type) {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("cardNumber")
+ public String getCardNumber() {
+ return cardNumber;
+ }
+
+ /**
+ *
+ * (Required)
+ *
+ */
+ @JsonProperty("cardNumber")
+ public void setCardNumber(String cardNumber) {
+ this.cardNumber = cardNumber;
+ }
+
+ public PaymentMethod withCardNumber(String cardNumber) {
+ this.cardNumber = cardNumber;
+ return this;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ public PaymentMethod withAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ return this;
+ }
+
+ protected boolean declaredProperty(String name, Object value) {
+ if ("id".equals(name)) {
+ if (value instanceof Long) {
+ setId(((Long) value));
+ }
+ else {
+ throw new IllegalArgumentException(
+ ("property \"id\" is of type \"java.lang.Long\", but got " + value.getClass().toString()));
+ }
+ return true;
+ }
+ else {
+ if ("type".equals(name)) {
+ if (value instanceof PaymentMethodType) {
+ setType(((PaymentMethodType) value));
+ }
+ else {
+ throw new IllegalArgumentException(
+ ("property \"type\" is of type \"io.zenwave360.example.core.outbound.events.dtos.PaymentMethodType\", but got "
+ + value.getClass().toString()));
+ }
+ return true;
+ }
+ else {
+ if ("cardNumber".equals(name)) {
+ if (value instanceof String) {
+ setCardNumber(((String) value));
+ }
+ else {
+ throw new IllegalArgumentException(
+ ("property \"cardNumber\" is of type \"java.lang.String\", but got "
+ + value.getClass().toString()));
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ }
+ }
+
+ protected Object declaredPropertyOrNotFound(String name, Object notFoundValue) {
+ if ("id".equals(name)) {
+ return getId();
+ }
+ else {
+ if ("type".equals(name)) {
+ return getType();
+ }
+ else {
+ if ("cardNumber".equals(name)) {
+ return getCardNumber();
+ }
+ else {
+ return notFoundValue;
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings({ "unchecked" })
+ public T get(String name) {
+ Object value = declaredPropertyOrNotFound(name, PaymentMethod.NOT_FOUND_VALUE);
+ if (PaymentMethod.NOT_FOUND_VALUE != value) {
+ return ((T) value);
+ }
+ else {
+ return ((T) getAdditionalProperties().get(name));
+ }
+ }
+
+ public void set(String name, Object value) {
+ if (!declaredProperty(name, value)) {
+ getAdditionalProperties().put(name, ((Object) value));
+ }
+ }
+
+ public PaymentMethod with(String name, Object value) {
+ if (!declaredProperty(name, value)) {
+ getAdditionalProperties().put(name, ((Object) value));
+ }
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(PaymentMethod.class.getName())
+ .append('@')
+ .append(Integer.toHexString(System.identityHashCode(this)))
+ .append('[');
+ sb.append("id");
+ sb.append('=');
+ sb.append(((this.id == null) ? "" : this.id));
+ sb.append(',');
+ sb.append("type");
+ sb.append('=');
+ sb.append(((this.type == null) ? "" : this.type));
+ sb.append(',');
+ sb.append("cardNumber");
+ sb.append('=');
+ sb.append(((this.cardNumber == null) ? "" : this.cardNumber));
+ sb.append(',');
+ sb.append("additionalProperties");
+ sb.append('=');
+ sb.append(((this.additionalProperties == null) ? "" : this.additionalProperties));
+ sb.append(',');
+ if (sb.charAt((sb.length() - 1)) == ',') {
+ sb.setCharAt((sb.length() - 1), ']');
+ }
+ else {
+ sb.append(']');
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = ((result * 31) + ((this.id == null) ? 0 : this.id.hashCode()));
+ result = ((result * 31) + ((this.additionalProperties == null) ? 0 : this.additionalProperties.hashCode()));
+ result = ((result * 31) + ((this.type == null) ? 0 : this.type.hashCode()));
+ result = ((result * 31) + ((this.cardNumber == null) ? 0 : this.cardNumber.hashCode()));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if ((other instanceof PaymentMethod) == false) {
+ return false;
+ }
+ PaymentMethod rhs = ((PaymentMethod) other);
+ return (((((this.id == rhs.id) || ((this.id != null) && this.id.equals(rhs.id)))
+ && ((this.additionalProperties == rhs.additionalProperties) || ((this.additionalProperties != null)
+ && this.additionalProperties.equals(rhs.additionalProperties))))
+ && ((this.type == rhs.type) || ((this.type != null) && this.type.equals(rhs.type))))
+ && ((this.cardNumber == rhs.cardNumber)
+ || ((this.cardNumber != null) && this.cardNumber.equals(rhs.cardNumber))));
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethodType.java b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethodType.java
new file mode 100644
index 00000000..18cf7d60
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/java/org/springframework/modulith/events/scs/dtos/json/PaymentMethodType.java
@@ -0,0 +1,49 @@
+
+package org.springframework.modulith.events.scs.dtos.json;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum PaymentMethodType {
+
+ VISA("VISA"), MASTERCARD("MASTERCARD");
+
+ private final String value;
+
+ private final static Map CONSTANTS = new HashMap();
+
+ static {
+ for (PaymentMethodType c : values()) {
+ CONSTANTS.put(c.value, c);
+ }
+ }
+
+ PaymentMethodType(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return this.value;
+ }
+
+ @JsonValue
+ public String value() {
+ return this.value;
+ }
+
+ @JsonCreator
+ public static PaymentMethodType fromValue(String value) {
+ PaymentMethodType constant = CONSTANTS.get(value);
+ if (constant == null) {
+ throw new IllegalArgumentException(value);
+ }
+ else {
+ return constant;
+ }
+ }
+
+}
diff --git a/spring-modulith-events/spring-modulith-events-scs/src/test/resources/application.yml b/spring-modulith-events/spring-modulith-events-scs/src/test/resources/application.yml
new file mode 100644
index 00000000..898bb877
--- /dev/null
+++ b/spring-modulith-events/spring-modulith-events-scs/src/test/resources/application.yml
@@ -0,0 +1,33 @@
+spring.main.banner-mode: OFF
+spring.modulith.events.jdbc.schema-initialization.enabled: true
+spring.modulith.events.externalization.enabled: true
+
+logging:
+ level:
+ root: WARN
+ io.zenwave360: DEBUG
+ org.springframework.jdbc: DEBUG
+ org.springframework.modulith: TRACE
+
+spring.datasource.url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
+spring.datasource.driverClassName: org.h2.Driver
+spring.datasource.username: sa
+spring.datasource.password: password
+spring.h2.console.enabled: true
+spring.sql.init.platform: h2
+
+spring:
+ kafka:
+ producer:
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
+ cloud:
+ stream:
+ bindings:
+ customers-json-out-0:
+ destination: customers-json-topic
+ customers-avro-out-0:
+ destination: customers-avro-topic
+ content-type: application/*+avro
+ customers-avro-externalized-out-0:
+ destination: customers-avro-topic
+ content-type: application/*+avro