BAEL-6529 springwolf (#14204)

* [BAEL-6529] Add spring boot springwolf article

Co-authored-by: David Müller <david.mueller@codecentric.de>

* [BAEL-6529] Remove README.md

Co-authored-by: David Müller <david.mueller@codecentric.de>

* Bael 6529 springwolf 0.12.0 (#2)

* [BAEL-6529] Bump springwolf-kafka to 0.12.0

Co-authored-by: David Müller <david.mueller@codecentric.de>

* [BAEL-6529] Bump springwolf-kafka to 0.12.1

Co-authored-by: David Müller <david.mueller@codecentric.de>

---------

Co-authored-by: David Müller <david.mueller@codecentric.de>
This commit is contained in:
Timon Back 2023-07-10 17:27:56 +02:00 committed by GitHub
parent 612a42286a
commit bf1edf8d51
13 changed files with 581 additions and 0 deletions

View File

@ -703,6 +703,7 @@
<module>osgi</module>
<module>spring-katharsis</module>
<module>logging-modules</module>
<module>spring-boot-documentation</module>
<module>spring-boot-modules</module>
<module>apache-httpclient</module>
<module>apache-httpclient4</module>
@ -974,6 +975,7 @@
<module>osgi</module>
<module>spring-katharsis</module>
<module>logging-modules</module>
<module>spring-boot-documentation</module>
<module>spring-boot-modules</module>
<module>apache-httpclient</module>
<module>apache-httpclient4</module>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baeldung.spring-boot-documentation</groupId>
<artifactId>spring-boot-documentation</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>spring-boot-documentation</name>
<packaging>pom</packaging>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../parent-boot-3</relativePath>
</parent>
<modules>
<module>springwolf</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit-jupiter.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<maven-war-plugin.version>3.3.2</maven-war-plugin.version>
</properties>
</project>

View File

@ -0,0 +1,37 @@
version: '3'
services:
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME:
kafka:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,PLAINTEXT_HOST://kafka:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
akhq:
image: tchiotludo/akhq
restart: unless-stopped
environment:
AKHQ_CONFIGURATION: |
akhq:
connections:
docker-kafka-server:
properties:
bootstrap.servers: "kafka:29092"
ports:
- "9090:8080"
links:
- kafka

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>springwolf</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springwolf</name>
<description>Documentation Spring Event Driven API Using AsyncAPI and Springwolf</description>
<parent>
<groupId>com.baeldung.spring-boot-documentation</groupId>
<artifactId>spring-boot-documentation</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-core-jakarta</artifactId>
<version>${swagger-core.version}</version>
</dependency>
<dependency>
<groupId>io.github.springwolf</groupId>
<artifactId>springwolf-kafka</artifactId>
<version>${springwolf-kafka.version}</version>
</dependency>
<dependency>
<groupId>io.github.springwolf</groupId>
<artifactId>springwolf-ui</artifactId>
<version>${springwolf-ui.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${testcontainers-kafka.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.baeldung.boot.documentation.springwolf.SpringwolfApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<swagger-core.version>2.2.11</swagger-core.version>
<springwolf-kafka.version>0.12.1</springwolf-kafka.version>
<springwolf-ui.version>0.8.0</springwolf-ui.version>
<testcontainers-kafka.version>1.18.3</testcontainers-kafka.version>
</properties>
</project>

View File

@ -0,0 +1,12 @@
package com.baeldung.boot.documentation.springwolf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringwolfApplication {
public static void main(String[] args) {
SpringApplication.run(SpringwolfApplication.class, args);
}
}

View File

@ -0,0 +1,48 @@
package com.baeldung.boot.documentation.springwolf.adapter.incoming;
import com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto;
import com.baeldung.boot.documentation.springwolf.service.ProcessorService;
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.KafkaAsyncOperationBinding;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import static org.springframework.kafka.support.mapping.AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME;
@AllArgsConstructor
@Component
@Slf4j
public class IncomingConsumer {
private static final String TOPIC_NAME = "incoming-topic";
private final ProcessorService processorService;
@KafkaListener(topics = TOPIC_NAME)
@AsyncListener(operation = @AsyncOperation(
channelName = TOPIC_NAME,
description = "More details for the incoming topic",
headers = @AsyncOperation.Headers(
schemaName = "SpringKafkaDefaultHeadersIncomingPayloadDto",
values = {
// this header is generated by Spring by default
@AsyncOperation.Headers.Header(
name = DEFAULT_CLASSID_FIELD_NAME,
description = "Spring Type Id Header",
value = "com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto"
),
}
)
)
)
@KafkaAsyncOperationBinding
public void consume(IncomingPayloadDto payload) {
log.info("Received new message: {}", payload.toString());
processorService.doHandle(payload);
}
}

View File

@ -0,0 +1,47 @@
package com.baeldung.boot.documentation.springwolf.adapter.outgoing;
import com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto;
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.KafkaAsyncOperationBinding;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
import static org.springframework.kafka.support.mapping.AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME;
@AllArgsConstructor
@Component
@Slf4j
public class OutgoingProducer {
private static final String TOPIC_NAME = "outgoing-topic";
private final KafkaTemplate<String, OutgoingPayloadDto> kafkaTemplate;
@AsyncPublisher(
operation = @AsyncOperation(
channelName = TOPIC_NAME,
description = "More details for the outgoing topic",
headers = @AsyncOperation.Headers(
schemaName = "SpringKafkaDefaultHeadersOutgoingPayloadDto",
values = {
// this header is generated by Spring by default
@AsyncOperation.Headers.Header(
name = DEFAULT_CLASSID_FIELD_NAME,
description = "Spring Type Id Header",
value = "com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto"
),
}
)
)
)
@KafkaAsyncOperationBinding
public void publish(OutgoingPayloadDto payload) {
log.info("Publishing new message: {}", payload.toString());
kafkaTemplate.send(TOPIC_NAME, payload);
}
}

View File

@ -0,0 +1,25 @@
package com.baeldung.boot.documentation.springwolf.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
@Data
@Schema(description = "Incoming payload model")
public class IncomingPayloadDto {
@Schema(description = "Some string field", example = "some string value", requiredMode = REQUIRED)
private String someString;
@Schema(description = "Some long field", example = "5", requiredMode = NOT_REQUIRED)
private long someLong;
@Schema(description = "Some enum field", example = "FOO2", requiredMode = REQUIRED)
private IncomingPayloadEnum someEnum;
public enum IncomingPayloadEnum {
FOO1, FOO2, FOO3
}
}

View File

@ -0,0 +1,20 @@
package com.baeldung.boot.documentation.springwolf.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
@Data
@Builder
@Schema(description = "Outgoing payload model")
public class OutgoingPayloadDto {
@Schema(description = "Foo field", example = "bar", requiredMode = NOT_REQUIRED)
private String foo;
@Schema(description = "IncomingPayload field", requiredMode = REQUIRED)
private IncomingPayloadDto incomingWrapped;
}

View File

@ -0,0 +1,23 @@
package com.baeldung.boot.documentation.springwolf.service;
import com.baeldung.boot.documentation.springwolf.adapter.outgoing.OutgoingProducer;
import com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto;
import com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class ProcessorService {
private final OutgoingProducer outgoingProducer;
public void doHandle(IncomingPayloadDto payload) {
OutgoingPayloadDto message = OutgoingPayloadDto.builder()
.foo("Foo message")
.incomingWrapped(payload)
.build();
outgoingProducer.publish(message);
}
}

View File

@ -0,0 +1,30 @@
#########
# Spring Configuration
spring.application.name=Baeldung Tutorial Springwolf Application
#########
# Spring Kafka Configuration
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=baeldung-kafka-group-id
spring.kafka.consumer.properties.spring.json.trusted.packages=com.baeldung.boot.documentation.springwolf.*
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
#########
# Springwolf Configuration
springwolf.docket.base-package=com.baeldung.boot.documentation.springwolf.adapter
springwolf.docket.info.title=${spring.application.name}
springwolf.docket.info.version=1.0.0
springwolf.docket.info.description=Baeldung Tutorial Application to Demonstrate AsyncAPI Documentation using Springwolf
# Springwolf Kafka Configuration
springwolf.docket.servers.kafka.protocol=kafka
springwolf.docket.servers.kafka.url=localhost:9092
springwolf.plugin.kafka.publishing.enabled=true
springwolf.plugin.kafka.publishing.producer.bootstrap-servers=localhost:9092
springwolf.plugin.kafka.publishing.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
springwolf.plugin.kafka.publishing.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
springwolf.plugin.kafka.publishing.producer.properties.spring.json.add.type.headers=false

View File

@ -0,0 +1,41 @@
package com.baeldung.boot.documentation.springwolf;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.kafka.test.context.EmbeddedKafka;
import org.springframework.test.annotation.DirtiesContext;
import org.testcontainers.shaded.org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@SpringBootTest(classes = {SpringwolfApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EmbeddedKafka(
partitions = 1, brokerProperties = {
"listeners=PLAINTEXT://localhost:9092",
"port=9092",
})
@DirtiesContext
public class ApiIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void asyncApiResourceArtifactTest() throws JSONException, IOException {
// given
InputStream s = this.getClass().getResourceAsStream("/asyncapi.json");
String expected = IOUtils.toString(s, StandardCharsets.UTF_8);
String url = "/springwolf/docs";
String actual = restTemplate.getForObject(url, String.class);
JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT);
}
}

View File

@ -0,0 +1,168 @@
{
"asyncapi": "2.6.0",
"info": {
"title": "Baeldung Tutorial Springwolf Application",
"version": "1.0.0",
"description": "Baeldung Tutorial Application to Demonstrate AsyncAPI Documentation using Springwolf"
},
"defaultContentType": "application/json",
"servers": {
"kafka": {
"url": "localhost:9092",
"protocol": "kafka"
}
},
"channels": {
"incoming-topic": {
"publish": {
"operationId": "incoming-topic_publish",
"description": "More details for the incoming topic",
"bindings": {
"kafka": { }
},
"message": {
"schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0",
"name": "com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto",
"title": "IncomingPayloadDto",
"description": "Incoming payload model",
"payload": {
"$ref": "#/components/schemas/IncomingPayloadDto"
},
"headers": {
"$ref": "#/components/schemas/SpringKafkaDefaultHeadersIncomingPayloadDto"
},
"bindings": {
"kafka": { }
}
}
}
},
"outgoing-topic": {
"subscribe": {
"operationId": "outgoing-topic_subscribe",
"description": "More details for the outgoing topic",
"bindings": {
"kafka": { }
},
"message": {
"schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0",
"name": "com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto",
"title": "OutgoingPayloadDto",
"description": "Outgoing payload model",
"payload": {
"$ref": "#/components/schemas/OutgoingPayloadDto"
},
"headers": {
"$ref": "#/components/schemas/SpringKafkaDefaultHeadersOutgoingPayloadDto"
},
"bindings": {
"kafka": { }
}
}
}
}
},
"components": {
"schemas": {
"HeadersNotDocumented": {
"type": "object",
"properties": { },
"example": { }
},
"IncomingPayloadDto": {
"required": [
"someEnum",
"someString"
],
"type": "object",
"properties": {
"someEnum": {
"type": "string",
"description": "Some enum field",
"example": "FOO2",
"enum": [
"FOO1",
"FOO2",
"FOO3"
]
},
"someLong": {
"type": "integer",
"description": "Some long field",
"format": "int64",
"example": 5
},
"someString": {
"type": "string",
"description": "Some string field",
"example": "some string value"
}
},
"description": "Incoming payload model",
"example": {
"someEnum": "FOO2",
"someLong": 5,
"someString": "some string value"
}
},
"OutgoingPayloadDto": {
"required": [
"incomingWrapped"
],
"type": "object",
"properties": {
"foo": {
"type": "string",
"description": "Foo field",
"example": "bar"
},
"incomingWrapped": {
"$ref": "#/components/schemas/IncomingPayloadDto"
}
},
"description": "Outgoing payload model",
"example": {
"foo": "bar",
"incomingWrapped": {
"someEnum": "FOO2",
"someLong": 5,
"someString": "some string value"
}
}
},
"SpringKafkaDefaultHeadersIncomingPayloadDto": {
"type": "object",
"properties": {
"__TypeId__": {
"type": "string",
"description": "Spring Type Id Header",
"example": "com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto",
"enum": [
"com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto"
]
}
},
"example": {
"__TypeId__": "com.baeldung.boot.documentation.springwolf.dto.IncomingPayloadDto"
}
},
"SpringKafkaDefaultHeadersOutgoingPayloadDto": {
"type": "object",
"properties": {
"__TypeId__": {
"type": "string",
"description": "Spring Type Id Header",
"example": "com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto",
"enum": [
"com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto"
]
}
},
"example": {
"__TypeId__": "com.baeldung.boot.documentation.springwolf.dto.OutgoingPayloadDto"
}
}
}
},
"tags": [ ]
}