Skip to content

Commit

Permalink
Add AWS SNS Plugin (#392)
Browse files Browse the repository at this point in the history
* Add AWS SNS Plugin

Although AsyncAPI allows to add more details for the binding, the underlying library jAsyncApi has no support for further binding information. Therefore, only the sns binding without properties is supported.

Manual configuration is supported through the usual `@AsyncPublisher` annotation in combination with the `@SnsAsyncOperationBinding` annotation.

The code is mostly copied and adjusted based on the other plugins.

* chore: Removed duplicated plugin version definition

* refactor(sns-plugin): change sns-plugin to use autoconfiguration instead of component scanning

Co-authored-by: Timon Back <[email protected]>

---------

Co-authored-by: David Müller <[email protected]>
Co-authored-by: Timon Back <[email protected]>
  • Loading branch information
3 people authored Oct 13, 2023
1 parent c23bcf0 commit 359599f
Show file tree
Hide file tree
Showing 41 changed files with 1,258 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/springwolf-plugins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:

strategy:
matrix:
plugin: [ "amqp", "cloud-stream", "kafka", "sqs" ]
plugin: [ "amqp", "cloud-stream", "kafka", "sns", "sqs" ]

env:
plugin: springwolf-plugins/springwolf-${{ matrix.plugin }}-plugin
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
![springwolf-amqp](https://github.com/springwolf/springwolf-core/workflows/springwolf-amqp/badge.svg)
![springwolf-cloud-stream](https://github.com/springwolf/springwolf-core/workflows/springwolf-cloud-stream/badge.svg)
![springwolf-kafka](https://github.com/springwolf/springwolf-core/workflows/springwolf-kafka/badge.svg)
![springwolf-sns](https://github.com/springwolf/springwolf-core/workflows/springwolf-sns/badge.svg)
![springwolf-sqs](https://github.com/springwolf/springwolf-core/workflows/springwolf-sqs/badge.svg)
![springwolf-common-model-converters](https://github.com/springwolf/springwolf-core/actions/workflows/springwolf-common-model-converters.yml/badge.svg)

Expand Down Expand Up @@ -38,7 +39,7 @@ The documentation and quickstart is available on [springwolf.dev](https://www.sp
### Why You Should Use It

Springwolf exploits the fact that you already fully described your consumer endpoint (with listener annotations, such as
`@KafkaListner`, `@RabbitListener`, `@SqsListener`, etc.) and generates the documentation based on this information.
`@KafkaListener`, `@RabbitListener`, `@SqsListener`, etc.) and generates the documentation based on this information.

#### Share API Schema Definition

Expand All @@ -60,6 +61,7 @@ More details in the documentation.
|-------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Core](https://github.com/springwolf/springwolf-core/tree/master/springwolf-core) | | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-core?color=green&label=springwolf-core&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-core?label=springwolf-core&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
| [AMQP](https://github.com/springwolf/springwolf-core/tree/master/springwolf-plugins/springwolf-amqp-plugin) | [AMQP Example](https://github.com/springwolf/springwolf-core/tree/master/springwolf-examples/springwolf-amqp-example) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-amqp?color=green&label=springwolf-amqp&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-amqp?label=springwolf-amqp&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
| [AWS SNS](https://github.com/springwolf/springwolf-core/tree/master/springwolf-plugins/springwolf-sns-plugin) | [AWS SNS Example](https://github.com/springwolf/springwolf-core/tree/master/springwolf-examples/springwolf-sns-example) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-sns?color=green&label=springwolf-sqs&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-sns?label=springwolf-sns&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
| [AWS SQS](https://github.com/springwolf/springwolf-core/tree/master/springwolf-plugins/springwolf-sqs-plugin) | [AWS SQS Example](https://github.com/springwolf/springwolf-core/tree/master/springwolf-examples/springwolf-sqs-example) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-sqs?color=green&label=springwolf-sqs&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-sqs?label=springwolf-sqs&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
| [Cloud Stream](https://github.com/springwolf/springwolf-core/tree/master/springwolf-plugins/springwolf-cloud-stream-plugin) | [Cloud Stream Example](https://github.com/springwolf/springwolf-core/tree/master/springwolf-examples/springwolf-cloud-stream-example) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-cloud-stream?color=green&label=springwolf-cloud-stream&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-cloud-stream?label=springwolf-cloud-stream&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
| [Kafka](https://github.com/springwolf/springwolf-core/tree/master/springwolf-plugins/springwolf-kafka-plugin) | [Kafka Example](https://github.com/springwolf/springwolf-core/tree/master/springwolf-examples/springwolf-kafka-example) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-kafka?color=green&label=springwolf-kafka&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-kafka?label=springwolf-kafka&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
Expand Down
3 changes: 2 additions & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ The following list describe the steps necessary to release a new version.
1. AMQP: https://amqp.demo.springwolf.dev/
2. CloudStream https://cloud-stream.demo.springwolf.dev/
3. Kafka: https://kafka.demo.springwolf.dev/
4. SQS: https://sqs.demo.springwolf.dev/
4. SNS: https://sns.demo.springwolf.dev/
5. SQS: https://sqs.demo.springwolf.dev/
3. Remove the `-SNAPHSOT` postfix in `.env`, create a new branch `release/0.X.X` (version number), commit & push
4. Run GitHub `Publish releases` pipeline from the newly created release branch
5. Release version in nexus
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ allprojects {
project.name == 'springwolf-amqp' ||
project.name == 'springwolf-cloud-stream' ||
project.name == 'springwolf-kafka' ||
project.name == 'springwolf-sns' ||
project.name == 'springwolf-sqs' ||
project.name == 'springwolf-ui' ||
project.name == 'springwolf-common-model-converters')
Expand Down
3 changes: 3 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ include(
'springwolf-plugins:springwolf-amqp-plugin',
'springwolf-plugins:springwolf-cloud-stream-plugin',
'springwolf-plugins:springwolf-kafka-plugin',
'springwolf-plugins:springwolf-sns-plugin',
'springwolf-plugins:springwolf-sqs-plugin',
'springwolf-examples:springwolf-amqp-example',
'springwolf-examples:springwolf-cloud-stream-example',
'springwolf-examples:springwolf-kafka-example',
'springwolf-examples:springwolf-sns-example',
'springwolf-examples:springwolf-sqs-example',
'springwolf-ui',
'springwolf-add-ons:springwolf-common-model-converters'
Expand All @@ -17,5 +19,6 @@ include(
project(':springwolf-plugins:springwolf-amqp-plugin').name = 'springwolf-amqp'
project(':springwolf-plugins:springwolf-cloud-stream-plugin').name = 'springwolf-cloud-stream'
project(':springwolf-plugins:springwolf-kafka-plugin').name = 'springwolf-kafka'
project(':springwolf-plugins:springwolf-sns-plugin').name = 'springwolf-sns'
project(':springwolf-plugins:springwolf-sqs-plugin').name = 'springwolf-sqs'

2 changes: 1 addition & 1 deletion springwolf-examples/springwolf-amqp-example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
id 'io.spring.dependency-management'
id 'ca.cutterslade.analyze'

id 'com.bmuschko.docker-spring-boot-application' version '9.3.4'
id 'com.bmuschko.docker-spring-boot-application'
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
id 'org.springframework.boot'
id 'io.spring.dependency-management'
id 'ca.cutterslade.analyze'
id 'com.bmuschko.docker-spring-boot-application' version '9.3.4'
id 'com.bmuschko.docker-spring-boot-application'
}

ext {
Expand Down
2 changes: 1 addition & 1 deletion springwolf-examples/springwolf-kafka-example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
id 'io.spring.dependency-management'
id 'ca.cutterslade.analyze'

id 'com.bmuschko.docker-spring-boot-application' version '9.3.4'
id 'com.bmuschko.docker-spring-boot-application'
id 'org.springdoc.openapi-gradle-plugin' version '1.7.0'
}

Expand Down
1 change: 1 addition & 0 deletions springwolf-examples/springwolf-sns-example/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SPRINGWOLF_VERSION=0.16.0-SNAPSHOT
6 changes: 6 additions & 0 deletions springwolf-examples/springwolf-sns-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## Usage

### Run with docker compose (recommended)
1. Copy the `docker-compose.yml` file to your machine.
2. Run `$ docker-compose up`.
3. Visit `localhost:8080/springwolf/asyncapi-ui.html` or try the API: `$ curl localhost:8080/springwolf/docs`.
76 changes: 76 additions & 0 deletions springwolf-examples/springwolf-sns-example/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
plugins {
id 'java'

id 'org.springframework.boot'
id 'io.spring.dependency-management'
id 'ca.cutterslade.analyze'

id 'com.bmuschko.docker-spring-boot-application'
}

dependencyManagement {
imports {
mavenBom "io.awspring.cloud:spring-cloud-aws-dependencies:3.0.2"
}
}

dependencies {
implementation project(":springwolf-core")
implementation project(":springwolf-plugins:springwolf-sns")

annotationProcessor project(":springwolf-plugins:springwolf-sns")
runtimeOnly project(":springwolf-ui")

runtimeOnly "org.springframework.boot:spring-boot-starter-web"

implementation "org.slf4j:slf4j-api:${slf4jApiVersion}"
implementation "io.swagger.core.v3:swagger-annotations:${swaggerVersion}"

implementation 'io.awspring.cloud:spring-cloud-aws-sns'
implementation 'io.awspring.cloud:spring-cloud-aws-starter-sns'
permitUnusedDeclared 'io.awspring.cloud:spring-cloud-aws-starter-sns'
implementation "org.springframework.boot:spring-boot-autoconfigure"
implementation "org.springframework.boot:spring-boot"
implementation "org.springframework:spring-context"
implementation "org.springframework:spring-messaging"

testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}"

testImplementation "com.vaadin.external.google:android-json:${androidJsonVersion}"

testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"

testImplementation "org.springframework.boot:spring-boot-test"
testImplementation "org.springframework:spring-beans"
testImplementation "org.springframework:spring-web"
testImplementation "org.springframework:spring-test"

testImplementation "org.testcontainers:testcontainers:${testcontainersVersion}"
testImplementation "org.testcontainers:junit-jupiter:${testcontainersVersion}"
testImplementation "org.testcontainers:localstack:${testcontainersVersion}"
}

docker {
springBootApplication {
maintainer = '[email protected]'
baseImage = 'eclipse-temurin:17-jre-focal'
ports = [8080]
images = ["stavshamir/springwolf-sns-example:${project.version}"]
}

registryCredentials {
username = project.findProperty('DOCKERHUB_USERNAME') ?: ''
password = project.findProperty('DOCKERHUB_TOKEN') ?: ''
}
}

test {
dependsOn dockerBuildImage
dependsOn spotlessApply // Automatically fix code formatting if possible

useJUnitPlatform()

testLogging {
exceptionFormat = 'full'
}
}
36 changes: 36 additions & 0 deletions springwolf-examples/springwolf-sns-example/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: '3'
services:
app:
image: stavshamir/springwolf-sns-example:${SPRINGWOLF_VERSION}
links:
- localstack
environment:
- SPRING_CLOUD_AWS_ENDPOINT=http://localstack:4566
ports:
- "8080:8080"
depends_on:
- localstack

localstack:
image: localstack/localstack:2.2.0
environment:
- DEBUG=${DEBUG-}
- AWS_REGION=eu-central-1
- SERVICES=sqs,sns
ports:
- "4566:4566" # LocalStack Gateway
# - "4510-4559:4510-4559" # external services port range
volumes:
- "${TMPDIR:-/tmp/localstack}:/tmp/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
localstack_setup:
image: localstack/localstack:2.2.0
links:
- localstack
depends_on:
- localstack
restart: "no"
entrypoint: [ "bash", "-c", "
awslocal --endpoint-url=http://localstack:4566 sns create-topic --name another-topic --region eu-central-1;
awslocal --endpoint-url=http://localstack:4566 sns create-topic --name example-topic --region eu-central-1;
" ]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.stavshamir.springwolf.example.sns;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringwolfSnsExampleApplication {

public static void main(String[] args) {
SpringApplication.run(SpringwolfSnsExampleApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.stavshamir.springwolf.example.sns.consumers;

import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncListener;
import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation;
import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBinding;
import io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto;
import io.github.stavshamir.springwolf.example.sns.dtos.ExamplePayloadDto;
import io.github.stavshamir.springwolf.example.sns.producers.AnotherProducer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class ExampleConsumer {
private final AnotherProducer anotherProducer;

@AsyncListener(operation = @AsyncOperation(channelName = "example-topic"))
@SnsAsyncOperationBinding
public void receiveExamplePayload(ExamplePayloadDto payload) {
log.info("Received new message in example-topic: {}", payload.toString());

AnotherPayloadDto example = new AnotherPayloadDto();
example.setExample(payload);
example.setFoo("foo");

anotherProducer.sendMessage(example);
}

@AsyncListener(operation = @AsyncOperation(channelName = "another-topic"))
@SnsAsyncOperationBinding
public void receiveAnotherPayload(AnotherPayloadDto payload) {
log.info("Received new message in another-topic: {}", payload.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.stavshamir.springwolf.example.sns.dtos;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

@Schema(description = "Another payload model")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AnotherPayloadDto {

@Schema(description = "Foo field", example = "bar", requiredMode = NOT_REQUIRED)
private String foo;

@Schema(description = "Example field", requiredMode = REQUIRED)
private ExamplePayloadDto example;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.stavshamir.springwolf.example.sns.dtos;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

@Schema(description = "Example payload model")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExamplePayloadDto {
@Schema(description = "Some string field", example = "some string value", requiredMode = REQUIRED)
private String someString;

@Schema(description = "Some long field", example = "5")
private long someLong;

@Schema(description = "Some enum field", example = "FOO2", requiredMode = REQUIRED)
private ExampleEnum someEnum;

public enum ExampleEnum {
FOO1,
FOO2,
FOO3
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.stavshamir.springwolf.example.sns.producers;

import io.awspring.cloud.sns.core.SnsTemplate;
import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation;
import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncPublisher;
import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBinding;
import io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class AnotherProducer {
private final SnsTemplate template;

public static final String TOPIC = "another-topic";

@AsyncPublisher(
operation =
@AsyncOperation(
channelName = TOPIC,
description = "Custom, optional description defined in the AsyncPublisher annotation"))
@SnsAsyncOperationBinding
public void sendMessage(AnotherPayloadDto msg) {
template.send(TOPIC, MessageBuilder.withPayload(msg).build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#########
# Spring configuration
spring.application.name=Springwolf example project - SNS


#########
# Spring AWS configuration
spring.cloud.aws.endpoint=http://localhost:4566
spring.cloud.aws.region.static=eu-central-1
spring.cloud.aws.stack.auto=false
spring.cloud.aws.stack.enabled=false
spring.cloud.aws.credentials.secretKey=ABC
spring.cloud.aws.credentials.accessKey=XYZ


#########
# Springwolf configuration
springwolf.enabled=true
springwolf.docket.base-package=io.github.stavshamir.springwolf.example.sns
springwolf.docket.info.title=${spring.application.name}
springwolf.docket.info.version=1.0.0
springwolf.docket.info.description=Springwolf example project to demonstrate springwolfs abilities
springwolf.docket.info.terms-of-service=http://asyncapi.org/terms
springwolf.docket.info.contact.name=springwolf
springwolf.docket.info.contact.email=[email protected]
springwolf.docket.info.contact.url=https://github.com/springwolf/springwolf-core
springwolf.docket.info.license.name=Apache License 2.0
springwolf.docket.servers.sns.protocol=sns
springwolf.docket.servers.sns.url=http://localhost:4566

springwolf.plugin.sns.publishing.enabled=true


# For debugging purposes
logging.level.io.github.stavshamir.springwolf=DEBUG
Loading

0 comments on commit 359599f

Please sign in to comment.