Skip to content

Commit

Permalink
Add Spring Modulith integration with SQS and SNS
Browse files Browse the repository at this point in the history
  • Loading branch information
Maciej Walkowiak committed Mar 3, 2024
1 parent e189ea5 commit 2d6f487
Show file tree
Hide file tree
Showing 29 changed files with 870 additions and 31 deletions.
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<module>spring-cloud-aws-starters/spring-cloud-aws-starter-sqs</module>
<module>spring-cloud-aws-samples</module>
<module>spring-cloud-aws-test</module>
<module>spring-cloud-aws-modulith</module>
<module>docs</module>
</modules>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ public SesClient sesClient(SesProperties properties, AwsClientBuilderConfigurer
@Bean
@ConditionalOnMissingClass("jakarta.mail.Session")
public MailSender simpleMailSender(SesClient sesClient, SesProperties properties) {
return new SimpleEmailServiceMailSender(sesClient, properties.getSourceArn(), properties.getConfigurationSetName());
return new SimpleEmailServiceMailSender(sesClient, properties.getSourceArn(),
properties.getConfigurationSetName());
}

@Bean
@ConditionalOnClass(name = "jakarta.mail.Session")
public JavaMailSender javaMailSender(SesClient sesClient, SesProperties properties) {
return new SimpleEmailServiceJavaMailSender(sesClient, properties.getSourceArn(), properties.getConfigurationSetName());
return new SimpleEmailServiceJavaMailSender(sesClient, properties.getSourceArn(),
properties.getConfigurationSetName());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -278,11 +278,11 @@ void propertyIsNotResolvedWhenIntegrationIsDisabled() {
application.setWebApplicationType(WebApplicationType.NONE);

try (ConfigurableApplicationContext context = application.run(
"--spring.config.import=aws-secretsmanager:/config/spring;/config/second",
"--spring.cloud.aws.secretsmanager.enabled=false", "--spring.cloud.aws.credentials.secret-key=noop",
"--spring.cloud.aws.endpoint=" + localstack.getEndpoint(),
"--spring.cloud.aws.credentials.access-key=noop", "--spring.cloud.aws.credentials.secret-key=noop",
"--spring.cloud.aws.region.static=eu-west-1")) {
"--spring.config.import=aws-secretsmanager:/config/spring;/config/second",
"--spring.cloud.aws.secretsmanager.enabled=false", "--spring.cloud.aws.credentials.secret-key=noop",
"--spring.cloud.aws.endpoint=" + localstack.getEndpoint(),
"--spring.cloud.aws.credentials.access-key=noop", "--spring.cloud.aws.credentials.secret-key=noop",
"--spring.cloud.aws.region.static=eu-west-1")) {
assertThat(context.getEnvironment().getProperty("message")).isNull();
assertThat(context.getBeanProvider(SecretsManagerClient.class).getIfAvailable()).isNull();
}
Expand Down
9 changes: 9 additions & 0 deletions spring-cloud-aws-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<aws-crt.version>0.28.3</aws-crt.version>
<mockito.version>5.3.1</mockito.version>
<bytebuddy.version>1.14.9</bytebuddy.version>
<spring-modulith.version>1.1.2</spring-modulith.version>
</properties>

<dependencyManagement>
Expand All @@ -54,6 +55,14 @@
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-bom</artifactId>
<version>${spring-modulith.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>software.amazon.dax</groupId>
<artifactId>amazon-dax-client</artifactId>
Expand Down
21 changes: 21 additions & 0 deletions spring-cloud-aws-modulith/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws</artifactId>
<version>3.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-cloud-aws-modulith</artifactId>
<name>Spring Cloud AWS - Spring Modulith Integrations</name>
<description>Spring Cloud AWS - Spring Modulith Integrations</description>
<packaging>pom</packaging>

<modules>
<module>spring-cloud-aws-modulith-events-sns</module>
<module>spring-cloud-aws-modulith-events-sqs</module>
</modules>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-modulith</artifactId>
<version>3.1.1-SNAPSHOT</version>
</parent>

<name>Spring Cloud AWS Modulith - Events - SNS support</name>
<artifactId>spring-cloud-aws-modulith-events-sns</artifactId>

<dependencies>

<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-api</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-events-core</artifactId>
</dependency>

<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-sns</artifactId>
</dependency>

<!-- Test dependencies -->

<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-starter-jdbc</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-sns</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-sqs</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>localstack</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2013-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 io.awspring.cloud.modulith.events.sns;

import io.awspring.cloud.sns.core.SnsNotification;
import io.awspring.cloud.sns.core.SnsOperations;
import io.awspring.cloud.sns.core.SnsTemplate;
import java.util.concurrent.CompletableFuture;
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.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.BrokerRouting;
import org.springframework.modulith.events.support.DelegatingEventExternalizer;

/**
* Auto-configuration to set up a {@link DelegatingEventExternalizer} to externalize events to SNS.
*
* @author Maciej Walkowiak
* @author Oliver Drotbohm
* @since 1.1
*/
@AutoConfiguration
@AutoConfigureAfter(EventExternalizationAutoConfiguration.class)
@ConditionalOnClass(SnsTemplate.class)
@ConditionalOnProperty(name = "spring.modulith.events.externalization.enabled", havingValue = "true", matchIfMissing = true)
class SnsEventExternalizerConfiguration {

private static final Logger logger = LoggerFactory.getLogger(SnsEventExternalizerConfiguration.class);

@Bean
DelegatingEventExternalizer snsEventExternalizer(EventExternalizationConfiguration configuration,
SnsOperations operations, BeanFactory factory) {

logger.debug("Registering domain event externalization to SNS…");

var context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(factory));

return new DelegatingEventExternalizer(configuration, (target, payload) -> {

var routing = BrokerRouting.of(target, context);
var builder = SnsNotification.builder(payload);
var key = routing.getKey(payload);

// when routing key is set, SNS topic must be a FIFO topic
if (key != null) {
builder.groupId(key);
}

operations.sendNotification(routing.getTarget(), builder.build());

return CompletableFuture.completedFuture(null);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* AWS SNS event externalization support.
*/
@org.springframework.lang.NonNullApi
package io.awspring.cloud.modulith.events.sns;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.awspring.cloud.modulith.events.sns.SnsEventExternalizerConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2013-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 io.awspring.cloud.modulith.events.sns;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

import io.awspring.cloud.sns.core.SnsOperations;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.modulith.events.EventExternalizationConfiguration;
import org.springframework.modulith.events.support.DelegatingEventExternalizer;

/**
* Integration tests for {@link SnsEventExternalizerConfiguration}.
*
* @author Maciej Walkowiak
* @since 1.1
*/
class SnsEventExternalizerConfigurationIntegrationTests {

@Test // GH-344
void registersExternalizerByDefault() {

basicSetup().run(ctxt -> {
assertThat(ctxt).hasSingleBean(DelegatingEventExternalizer.class);
});
}

@Test // GH-344
void disablesExternalizationIfConfigured() {

basicSetup().withPropertyValues("spring.modulith.events.externalization.enabled=false").run(ctxt -> {
assertThat(ctxt).doesNotHaveBean(DelegatingEventExternalizer.class);
});
}

private ApplicationContextRunner basicSetup() {

return new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SnsEventExternalizerConfiguration.class))
.withBean(EventExternalizationConfiguration.class, () -> EventExternalizationConfiguration.disabled())
.withBean(SnsOperations.class, () -> mock(SnsOperations.class));
}
}
Loading

0 comments on commit 2d6f487

Please sign in to comment.