Skip to content

Commit

Permalink
Add AWS SQS Plugin
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ctasada committed Oct 9, 2023
1 parent 3b0ecc9 commit d4244a7
Show file tree
Hide file tree
Showing 29 changed files with 1,273 additions and 0 deletions.
84 changes: 84 additions & 0 deletions .github/workflows/springwolf-sns.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: springwolf-sns

on:
push:
branches:
- master
paths:
- '.github/workflows/springwolf-sns.yml'
- 'springwolf-core/**'
- 'springwolf-plugins/springwolf-sns-plugin/**'
- 'springwolf-examples/springwolf-sns-example/**'
pull_request:
types: [ opened, synchronize, ready_for_review ]

env:
plugin: springwolf-plugins/springwolf-sns-plugin
example: springwolf-examples/springwolf-sns-example

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup spectral
run: |
echo 'extends: ["spectral:asyncapi"]' > .spectral.yaml
- name: Lint asyncapi.json with spectral
uses: addnab/docker-run-action@v3
with:
image: stoplight/spectral:latest
options: -v ${{ github.workspace }}:${{ github.workspace }} -w ${{ github.workspace }}
run: spectral lint --ruleset ./.spectral.yaml --fail-on-unmatched-globs ./${{ env.example }}/src/test/resources/asyncapi.json

- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'

- name: Setup Gradle
uses: gradle/gradle-build-action@v2

- name: Check formatting (before running tests) on plugin
run: ./gradlew -p ${{ env.plugin }} spotlessCheck

- name: Check formatting (before running tests) on example
run: ./gradlew -p ${{ env.example }} spotlessCheck

- name: Run unit tests
run: ./gradlew -p ${{ env.plugin }} test

- name: Run integration tests
run: ./gradlew -p ${{ env.example }} test

- name: Run build, check, analyzeDependencies on plugin
run: ./gradlew -p ${{ env.plugin }} build

- name: Run build, check, analyzeDependencies on example
run: ./gradlew -p ${{ env.example }} build

- name: Publish docker image
if: github.ref == 'refs/heads/master'
run: ./gradlew -p ${{ env.example }} dockerBuildImage dockerPushImage
env:
ORG_GRADLE_PROJECT_SNAPSHOT: true

ORG_GRADLE_PROJECT_DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
ORG_GRADLE_PROJECT_DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Publish package
if: github.ref == 'refs/heads/master'
run: ./gradlew -p ${{ env.plugin }} publish
env:
ORG_GRADLE_PROJECT_SNAPSHOT: true

ORG_GRADLE_PROJECT_SIGNINGKEY: ${{secrets.ORG_GRADLE_PROJECT_SIGNINGKEY}}
ORG_GRADLE_PROJECT_SIGNINGPASSWORD: ${{secrets.ORG_GRADLE_PROJECT_SIGNINGPASSWORD}}

ORG_GRADLE_PROJECT_MAVEN_URL: https://s01.oss.sonatype.org/content/repositories/snapshots/
ORG_GRADLE_PROJECT_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
ORG_GRADLE_PROJECT_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
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'

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`.
80 changes: 80 additions & 0 deletions springwolf-examples/springwolf-sns-example/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
plugins {
id 'java'

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

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

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 'io.awspring.cloud:spring-cloud-aws-messaging'
// implementation 'io.awspring.cloud:spring-cloud-aws-autoconfigure'
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.mockito:mockito-core:${mockitoCoreVersion}"

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());
}
}
Loading

0 comments on commit d4244a7

Please sign in to comment.