Skip to content

Commit d95da87

Browse files
maciejwalkowiakodrotbohm
authored andcommitted
GH-344 - Support for event externalization into AWS SNS and SQS.
Additional event externalization implementations for AWS SNS and SQS. Original pull request: GH-350.
1 parent 0ae19fb commit d95da87

File tree

19 files changed

+860
-1
lines changed

19 files changed

+860
-1
lines changed

pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
4343
<spring.version>6.1.0-RC1</spring.version> <!-- For Javadoc links only -->
4444
<spring-boot.version>3.2.0-RC1</spring-boot.version>
45-
45+
<spring-cloud-aws-bom.version>3.0.2</spring-cloud-aws-bom.version>
4646
</properties>
4747

4848
<developers>
@@ -407,6 +407,13 @@ limitations under the License.
407407
<type>pom</type>
408408
<scope>import</scope>
409409
</dependency>
410+
<dependency>
411+
<groupId>io.awspring.cloud</groupId>
412+
<artifactId>spring-cloud-aws-dependencies</artifactId>
413+
<version>${spring-cloud-aws-bom.version}</version>
414+
<type>pom</type>
415+
<scope>import</scope>
416+
</dependency>
410417
</dependencies>
411418
</dependencyManagement>
412419

spring-modulith-events/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<modules>
1717
<module>spring-modulith-events-amqp</module>
1818
<module>spring-modulith-events-api</module>
19+
<module>spring-modulith-events-aws-sns</module>
20+
<module>spring-modulith-events-aws-sqs</module>
1921
<module>spring-modulith-events-core</module>
2022
<module>spring-modulith-events-jackson</module>
2123
<module>spring-modulith-events-jdbc</module>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>org.springframework.modulith</groupId>
7+
<artifactId>spring-modulith-events</artifactId>
8+
<version>1.1.0-SNAPSHOT</version>
9+
</parent>
10+
11+
<name>Spring Modulith - Events - AWS SNS support</name>
12+
<artifactId>spring-modulith-events-aws-sns</artifactId>
13+
14+
<properties>
15+
<module.name>org.springframework.modulith.events.aws.sns</module.name>
16+
</properties>
17+
18+
<dependencies>
19+
20+
<dependency>
21+
<groupId>org.springframework.modulith</groupId>
22+
<artifactId>spring-modulith-api</artifactId>
23+
<version>${project.version}</version>
24+
</dependency>
25+
26+
<dependency>
27+
<groupId>org.springframework.modulith</groupId>
28+
<artifactId>spring-modulith-events-core</artifactId>
29+
<version>${project.version}</version>
30+
</dependency>
31+
32+
<dependency>
33+
<groupId>io.awspring.cloud</groupId>
34+
<artifactId>spring-cloud-aws-sns</artifactId>
35+
</dependency>
36+
37+
<dependency>
38+
<groupId>com.fasterxml.jackson.core</groupId>
39+
<artifactId>jackson-databind</artifactId>
40+
<optional>true</optional>
41+
</dependency>
42+
43+
<!-- Test dependencies -->
44+
45+
<dependency>
46+
<groupId>org.springframework.modulith</groupId>
47+
<artifactId>spring-modulith-starter-jdbc</artifactId>
48+
<version>${project.version}</version>
49+
<scope>test</scope>
50+
</dependency>
51+
52+
<dependency>
53+
<groupId>com.h2database</groupId>
54+
<artifactId>h2</artifactId>
55+
<scope>test</scope>
56+
</dependency>
57+
58+
<dependency>
59+
<groupId>org.springframework.boot</groupId>
60+
<artifactId>spring-boot-starter-json</artifactId>
61+
<scope>test</scope>
62+
</dependency>
63+
64+
<dependency>
65+
<groupId>org.springframework.boot</groupId>
66+
<artifactId>spring-boot-starter-test</artifactId>
67+
<scope>test</scope>
68+
</dependency>
69+
70+
<dependency>
71+
<groupId>org.springframework.boot</groupId>
72+
<artifactId>spring-boot-testcontainers</artifactId>
73+
<scope>test</scope>
74+
</dependency>
75+
76+
<dependency>
77+
<groupId>io.awspring.cloud</groupId>
78+
<artifactId>spring-cloud-aws-starter-sns</artifactId>
79+
<scope>test</scope>
80+
</dependency>
81+
82+
<dependency>
83+
<groupId>io.awspring.cloud</groupId>
84+
<artifactId>spring-cloud-aws-starter-sqs</artifactId>
85+
<scope>test</scope>
86+
</dependency>
87+
88+
<dependency>
89+
<groupId>org.testcontainers</groupId>
90+
<artifactId>localstack</artifactId>
91+
<scope>test</scope>
92+
</dependency>
93+
94+
</dependencies>
95+
96+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.modulith.events.aws.sns;
17+
18+
import io.awspring.cloud.sns.core.SnsNotification;
19+
import io.awspring.cloud.sns.core.SnsOperations;
20+
import io.awspring.cloud.sns.core.SnsTemplate;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
import software.amazon.awssdk.services.sns.model.InvalidParameterException;
24+
25+
import org.springframework.beans.factory.BeanFactory;
26+
import org.springframework.boot.autoconfigure.AutoConfiguration;
27+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.expression.BeanFactoryResolver;
32+
import org.springframework.expression.spel.support.StandardEvaluationContext;
33+
import org.springframework.messaging.MessageDeliveryException;
34+
import org.springframework.modulith.events.EventExternalizationConfiguration;
35+
import org.springframework.modulith.events.config.EventExternalizationAutoConfiguration;
36+
import org.springframework.modulith.events.support.BrokerRouting;
37+
import org.springframework.modulith.events.support.DelegatingEventExternalizer;
38+
39+
/**
40+
* Auto-configuration to set up a {@link DelegatingEventExternalizer} to externalize events to SNS.
41+
*
42+
* @author Maciej Walkowiak
43+
* @since 1.1
44+
*/
45+
@AutoConfiguration
46+
@AutoConfigureAfter(EventExternalizationAutoConfiguration.class)
47+
@ConditionalOnClass(SnsTemplate.class)
48+
@ConditionalOnProperty(name = "spring.modulith.events.externalization.enabled",
49+
havingValue = "true",
50+
matchIfMissing = true)
51+
class SnsEventExternalizerConfiguration {
52+
53+
private static final Logger logger = LoggerFactory.getLogger(SnsEventExternalizerConfiguration.class);
54+
55+
@Bean
56+
DelegatingEventExternalizer snsEventExternalizer(EventExternalizationConfiguration configuration,
57+
SnsOperations operations, BeanFactory factory) {
58+
59+
logger.debug("Registering domain event externalization to SNS…");
60+
61+
var context = new StandardEvaluationContext();
62+
context.setBeanResolver(new BeanFactoryResolver(factory));
63+
64+
return new DelegatingEventExternalizer(configuration, (target, payload) -> {
65+
66+
var routing = BrokerRouting.of(target, context);
67+
68+
var builder = SnsNotification.builder(payload);
69+
var key = routing.getKey(payload);
70+
// when routing key is set, SNS topic must be a FIFO topic
71+
if (key != null) {
72+
builder.groupId(key);
73+
}
74+
try {
75+
operations.sendNotification(routing.getTarget(), builder.build());
76+
} catch (MessageDeliveryException e) {
77+
// message delivery may fail if groupId is set and topic is not a FIFO topic, or content based deduplication has not been set on topic attributes.
78+
if (e.getCause() instanceof InvalidParameterException) {
79+
logger.error("Failed to send notification to SNS topic {}:{}", routing.getTarget(), e.getCause().getMessage());
80+
}
81+
throw e;
82+
}
83+
});
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* SNS event externalization support.
3+
*/
4+
@org.springframework.lang.NonNullApi
5+
package org.springframework.modulith.events.aws.sns;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.springframework.modulith.events.aws.sns.SnsEventExternalizerConfiguration
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.modulith.events.aws.sns;
17+
18+
import io.awspring.cloud.sns.core.SnsOperations;
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.boot.autoconfigure.AutoConfigurations;
22+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
23+
import org.springframework.modulith.events.EventExternalizationConfiguration;
24+
import org.springframework.modulith.events.support.DelegatingEventExternalizer;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.mockito.Mockito.mock;
28+
29+
/**
30+
* Integration tests for {@link SnsEventExternalizerConfiguration}.
31+
*
32+
* @author Maciej Walkowiak
33+
* @since 1.1
34+
*/
35+
class SnsEventExternalizerConfigurationIntegrationTests {
36+
37+
@Test // GH-342
38+
void registersExternalizerByDefault() {
39+
40+
basicSetup()
41+
.run(ctxt -> {
42+
assertThat(ctxt).hasSingleBean(DelegatingEventExternalizer.class);
43+
});
44+
}
45+
46+
@Test // GH-342
47+
void disablesExternalizationIfConfigured() {
48+
49+
basicSetup()
50+
.withPropertyValues("spring.modulith.events.externalization.enabled=false")
51+
.run(ctxt -> {
52+
assertThat(ctxt).doesNotHaveBean(DelegatingEventExternalizer.class);
53+
});
54+
}
55+
56+
private ApplicationContextRunner basicSetup() {
57+
58+
return new ApplicationContextRunner()
59+
.withConfiguration(
60+
AutoConfigurations.of(SnsEventExternalizerConfiguration.class))
61+
.withBean(EventExternalizationConfiguration.class, () -> EventExternalizationConfiguration.disabled())
62+
.withBean(SnsOperations.class, () -> mock(SnsOperations.class));
63+
}
64+
}

0 commit comments

Comments
 (0)