Skip to content

Commit 2ceaa63

Browse files
committed
feat: new plugin asyncapi-generator that wraps spring-cloud-stream or kafka + json + avro (#75)
1 parent d5096af commit 2ceaa63

File tree

107 files changed

+4174
-204
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+4174
-204
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ jobs:
4040
jacoco-csv-file: >
4141
./plugins/asyncapi-spring-cloud-streams3/target/site/jacoco/jacoco.csv
4242
./plugins/avro-schema-compiler/target/site/jacoco/jacoco.csv
43+
./plugins/asyncapi-spring-cloud-streams-with-avro-json/target/site/jacoco/jacoco.csv
4344
./plugins/java-to-jdl/target/site/jacoco/jacoco.csv
4445
./plugins/java-to-asyncapi/target/site/jacoco/jacoco.csv
4546
./plugins/backend-application-default/target/site/jacoco/jacoco.csv

.github/workflows/publish-maven-central.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ jobs:
5252
jacoco-csv-file: >
5353
./plugins/asyncapi-spring-cloud-streams3/target/site/jacoco/jacoco.csv
5454
./plugins/avro-schema-compiler/target/site/jacoco/jacoco.csv
55+
./plugins/asyncapi-generator/target/site/jacoco/jacoco.csv
5556
./plugins/java-to-jdl/target/site/jacoco/jacoco.csv
5657
./plugins/java-to-asyncapi/target/site/jacoco/jacoco.csv
5758
./plugins/backend-application-default/target/site/jacoco/jacoco.csv

.github/workflows/publish-maven-snapshots.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ jobs:
4545
jacoco-csv-file: >
4646
./plugins/asyncapi-spring-cloud-streams3/target/site/jacoco/jacoco.csv
4747
./plugins/avro-schema-compiler/target/site/jacoco/jacoco.csv
48+
./plugins/asyncapi-generator/target/site/jacoco/jacoco.csv
4849
./plugins/java-to-jdl/target/site/jacoco/jacoco.csv
4950
./plugins/java-to-asyncapi/target/site/jacoco/jacoco.csv
5051
./plugins/backend-application-default/target/site/jacoco/jacoco.csv

e2e/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>io.zenwave360.sdk</groupId>
66
<artifactId>zenwave-sdk</artifactId>
7-
<version>2.1.0-SNAPSHOT</version>
7+
<version>2.2.0-SNAPSHOT</version>
88
</parent>
99
<name>${project.groupId}:${project.artifactId}</name>
1010
<artifactId>e2e</artifactId>
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# AsyncAPI and Spring Cloud Stream with Avro and JSON
2+
> 👉 ZenWave360 Helps You Create Software Easy to Understand
3+
4+
[![Maven Central](https://img.shields.io/maven-central/v/io.zenwave360.sdk/zenwave-sdk.svg?label=Maven%20Central&logo=apachemaven)](https://search.maven.org/artifact/io.zenwave360.sdk/zenwave-sdk)
5+
[![GitHub](https://img.shields.io/github/license/ZenWave360/zenwave-sdk)](https://github.com/ZenWave360/zenwave-sdk/blob/main/LICENSE)
6+
7+
Generates strongly typed SpringCloudStreams3 producer/consumer classes for AsyncAPI with Avro and JSON DTOs
8+
9+
## Maven Usage
10+
11+
- Configure `zenwave-sdk-maven-plugin` in your `pom.xml`
12+
- Add `asyncapi-spring-cloud-streams-with-avro-json` as dependency to `zenwave-sdk-maven-plugin`
13+
- Bring your own version of `avro-compiler` as dependency to `zenwave-sdk-maven-plugin`, supporting versions 1.8.0 - 1.12.0+
14+
- Include maven dependencies holding your API files in case you are using `classpath:` scheme in `inputSpec`.
15+
- Set `<generatorName>SpringCloudStreamsWithDtosPlugin</generatorName>`
16+
- Point to your AsyncAPI file in `inputSpec`: supported schemes are `classpath:`, `https://` and regular local files.
17+
- Configure basic options:
18+
- `producerApiPackage`,`consumerApiPackage`: producer/consumer packages (for events/commands respectively)
19+
- `modelPackage`: package for generated DTOs in case of JSON payloads
20+
- `avroCompilerProperties.*`: options like `imports`, `includes`, `excludes`, `customLogicalTypeFactories`, `customConversions`, etc... See [AvroCompilerProperties](https://github.com/ZenWave360/zenwave-sdk/blob/main/plugins/avro-schema-compiler/src/main/java/io/zenwave360/sdk/plugins/AvroCompilerProperties.java)
21+
- `jsonschema2pojo.*`: JsonSchema2Pojo settings for downstream library. See [JsonSchema2PojoConfiguration.java](https://github.com/ZenWave360/zenwave-sdk/blob/main/plugins/asyncapi-jsonschema2pojo/src/main/java/io/zenwave360/sdk/plugins/JsonSchema2PojoConfiguration.java)
22+
23+
```xml
24+
<plugin>
25+
<groupId>io.zenwave360.sdk</groupId>
26+
<artifactId>zenwave-sdk-maven-plugin</artifactId>
27+
<version>${zenwave.version}</version>
28+
<configuration>
29+
<addCompileSourceRoot>true</addCompileSourceRoot><!-- default is true -->
30+
<addTestCompileSourceRoot>true</addTestCompileSourceRoot><!-- default is true -->
31+
</configuration>
32+
<executions>
33+
<execution>
34+
<id>generate-asyncapi-with-dtos</id>
35+
<phase>generate-sources</phase>
36+
<goals>
37+
<goal>generate</goal>
38+
</goals>
39+
<configuration>
40+
<generatorName>SpringCloudStreamsWithDtosPlugin</generatorName>
41+
<inputSpec>classpath:model/asyncapi.yml</inputSpec><!-- supported schemes are `classpath:`, `https://` and regular local files -->
42+
<configOptions>
43+
<producerApiPackage>io.example.api.producer</producerApiPackage>
44+
<consumerApiPackage>io.example.api.consumer</consumerApiPackage>
45+
46+
<avroCompilerProperties.imports>classpath:avro</avroCompilerProperties.imports><!-- supports local files/folders, `classpath:` files/folders or `https://` file resources -->
47+
48+
<modelPackage>io.example.api.model</modelPackage>
49+
<jsonschema2pojo.includeTypeInfo>true</jsonschema2pojo.includeTypeInfo>
50+
51+
<transactionalOutbox>modulith</transactionalOutbox>
52+
</configOptions>
53+
</configuration>
54+
</execution>
55+
</executions>
56+
<dependencies>
57+
<dependency>
58+
<groupId>io.zenwave360.sdk.plugins</groupId>
59+
<artifactId>asyncapi-spring-cloud-streams-with-avro-json</artifactId>
60+
<version>${zenwave.version}</version><!-- 2.2.0+ -->
61+
</dependency>
62+
<dependency>
63+
<groupId>org.apache.avro</groupId>
64+
<artifactId>avro-compiler</artifactId>
65+
<version>${avro-compiler.version}</version><!-- 1.8.0 - 1.12.0+ -->
66+
</dependency>
67+
</dependencies>
68+
</plugin>
69+
```
70+
71+
72+
73+
## Options
74+
75+
| **Option** | **Description** | **Type** | **Default** | **Values** |
76+
|-----------------------------------------------------|-----------------|----------|-------------|------------|
77+
| `apiFile` | API Specification File | URI | | |
78+
| `role` | Project role: provider/client | AsyncapiRoleType | provider | provider, client |
79+
| `style` | Programming style | ProgrammingStyle | imperative | imperative, reactive |
80+
| `modelPackage` | Java Models package name | String | | |
81+
| `producerApiPackage` | Java API package name for outbound (producer) services. It can override apiPackage for producers. | String | {{apiPackage}} | |
82+
| `consumerApiPackage` | Java API package name for inbound (consumer) services. It can override apiPackage for consumer. | String | {{apiPackage}} | |
83+
| `apiPackage` | Java API package, if `producerApiPackage` and `consumerApiPackage` are not set. | String | | |
84+
| `jsonschema2pojo` | JsonSchema2Pojo settings for downstream library [(docs)](https://github.com/ZenWave360/zenwave-sdk/blob/main/plugins/asyncapi-jsonschema2pojo/src/main/java/io/zenwave360/sdk/plugins/JsonSchema2PojoConfiguration.java) | Map | {} | |
85+
| `avroCompilerProperties` | Avro Compiler Properties | AvroCompilerProperties | See [AvroCompilerProperties](https://github.com/ZenWave360/zenwave-sdk/blob/main/plugins/avro-schema-compiler/src/main/java/io/zenwave360/sdk/plugins/AvroCompilerProperties.java) | |
86+
| `avroCompilerProperties.sourceDirectory` | Avro schema file or folder containing avro schemas | File | | |
87+
| `avroCompilerProperties.imports` | Avro schema files or folders containing avro schemas. It supports local files/folders, `classpath:` files/folders or `https://` file resources. | List | | |
88+
| `avroCompilerProperties.includes` | A set of Ant-like inclusion patterns used to select files from the source tree that are to be processed. By default, the pattern **\/*.avsc is used to include all avro schema files. | List | [**/*.avsc] | |
89+
| `avroCompilerProperties.excludes` | A set of Ant-like exclusion patterns used to prevent certain files from being processed. By default, this set is empty such that no files are excluded. | List | | |
90+
| `avroCompilerProperties.customLogicalTypeFactories` | Custom Logical Type Factories | List | | |
91+
| `avroCompilerProperties.customConversions` | Custom Conversions | List | | |
92+
| `operationIds` | Operation ids to include in code generation. Generates code for ALL if left empty | List | [] | |
93+
| `messageNames` | Message names to include in code generation (combined with operationIds). Generates code for ALL if left empty | List | [] | |
94+
| `excludeOperationIds` | Operation ids to exclude in code generation. Skips code generation if is not included or is excluded. | List | [] | |
95+
| `bindingTypes` | Binding names to include in code generation. Generates code for ALL bindings if left empty | List | | |
96+
| **JsonSchema2Pojo Options** | | | | |
97+
| `modelNamePrefix` | Sets the prefix for model classes and enums | String | | |
98+
| `modelNameSuffix` | Sets the suffix for model classes and enums | String | | |
99+
| `generatedAnnotationClass` | Annotation class to mark generated code (e.g. `org.springframework.aot.generate.Generated`). When retained at runtime, this prevents code coverage tools like Jacoco from including generated classes in coverage reports. | String | | |
100+
| **SpringCloudStreams3 Options** | | | | |
101+
| `transactionalOutbox` | Transactional outbox type for message producers. | TransactionalOutboxType | none | none, modulith, mongodb, jdbc |
102+
| `bindingPrefix` | SC Streams Binding Name Prefix (used in @Component name) | String | | |
103+
| `bindingSuffix` | Spring-Boot binding suffix. It will be appended to the operation name kebab-cased. E.g. <operation-id>-in-0 | String | -0 | |
104+
| `runtimeHeadersProperty` | AsyncAPI extension property name for runtime auto-configuration of headers. | String | x-runtime-expression | |
105+
| `includeApplicationEventListener` | Include ApplicationEvent listener for consuming messages within the modulith. | boolean | false | |
106+
| `skipProducerImplementation` | Generate only the producer interface and skip the implementation. | boolean | false | |
107+
| `exposeMessage` | Whether to expose underlying spring Message to consumers or not. | boolean | false | |
108+
| `useEnterpriseEnvelope` | Include support for enterprise envelop wrapping/unwrapping. | boolean | false | |
109+
| `envelopeJavaTypeExtensionName` | AsyncAPI Message extension name for the envelop java type for wrapping/unwrapping. | String | x-envelope-java-type | |
110+
| `methodAndMessageSeparator` | To avoid method erasure conflicts, when exposeMessage or reactive style this character will be used as separator to append message payload type to method names in consumer interfaces. | String | $ | |
111+
| `consumerPrefix` | SC Streams Binder class prefix | String | | |
112+
| `consumerSuffix` | SC Streams Binder class suffix | String | Consumer | |
113+
| `consumerServicePrefix` | Business/Service interface prefix | String | I | |
114+
| `consumerServiceSuffix` | Business/Service interface suffix | String | ConsumerService | |
115+
| **General Options** | | | | |
116+
| `sourceFolder` | Source folder inside folder to generate code to. | String | src/main/java | |
117+
| `targetFolder` | Target folder to generate code to. If left empty, it will print to stdout. | File | | |
118+
| `formatter` | Code formatter implementation | Formatters | palantir | palantir, spring, google |
119+
| `skipFormatting` | Skip java sources output formatting | boolean | false | |
120+
| `haltOnFailFormatting` | Halt on formatting errors | boolean | true | |
121+
122+
## Getting Help
123+
124+
```shell
125+
jbang zw -p io.zenwave360.sdk.plugins.AsyncAPIGeneratorPlugin``

plugins/asyncapi-generator/pom.xml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<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">
3+
<modelVersion>4.0.0</modelVersion>
4+
<parent>
5+
<groupId>io.zenwave360.sdk</groupId>
6+
<artifactId>plugins-parent</artifactId>
7+
<version>2.2.0-SNAPSHOT</version>
8+
</parent>
9+
<name>${project.groupId}:${project.artifactId}</name>
10+
<groupId>io.zenwave360.sdk.plugins</groupId>
11+
<artifactId>asyncapi-generator</artifactId>
12+
<packaging>jar</packaging>
13+
14+
<properties>
15+
<avro-compiler.version>1.12.0</avro-compiler.version>
16+
</properties>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>io.zenwave360.sdk.plugins</groupId>
21+
<artifactId>asyncapi-jsonschema2pojo</artifactId>
22+
<version>${project.version}</version>
23+
</dependency>
24+
<dependency>
25+
<groupId>io.zenwave360.sdk.plugins</groupId>
26+
<artifactId>avro-schema-compiler</artifactId>
27+
<version>${project.version}</version>
28+
</dependency>
29+
30+
<dependency>
31+
<groupId>org.apache.avro</groupId>
32+
<artifactId>avro-compiler</artifactId>
33+
<version>${avro-compiler.version}</version>
34+
<scope>test</scope>
35+
</dependency>
36+
</dependencies>
37+
</project>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package io.zenwave360.sdk.plugins;
2+
3+
import io.zenwave360.sdk.doc.DocumentedOption;
4+
import io.zenwave360.sdk.generators.AbstractAsyncapiGenerator;
5+
import io.zenwave360.sdk.options.ProgrammingStyle;
6+
import io.zenwave360.sdk.plugins.templates.SpringCloudStreamTemplates;
7+
import io.zenwave360.sdk.plugins.templates.AsyncAPIHandlebarsHelpers;
8+
import io.zenwave360.sdk.plugins.templates.SpringKafkaTemplates;
9+
import io.zenwave360.sdk.templating.HandlebarsEngine;
10+
import io.zenwave360.sdk.utils.JSONPath;
11+
import org.apache.commons.lang3.StringEscapeUtils;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
15+
import java.util.ArrayList;
16+
import java.util.Collections;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.stream.Collectors;
20+
21+
import static io.zenwave360.sdk.templating.OutputFormatType.JAVA;
22+
23+
public class AsyncAPIGenerator extends AbstractAsyncapiGenerator {
24+
25+
private Logger log = LoggerFactory.getLogger(getClass());
26+
27+
public enum TransactionalOutboxType {
28+
none, modulith
29+
}
30+
31+
// @DocumentedOption(description = "Programming style")
32+
public final ProgrammingStyle style = ProgrammingStyle.imperative;
33+
34+
@DocumentedOption(description = "Transactional outbox type for message producers.")
35+
public TransactionalOutboxType transactionalOutbox = TransactionalOutboxType.none;
36+
37+
// @DocumentedOption(description = "Include ApplicationEvent listener for consuming messages within the modulith.")
38+
// public boolean includeApplicationEventListener = false;
39+
40+
@DocumentedOption(description = "Generate only the producer interface and skip the implementation.")
41+
public boolean skipProducerImplementation = false;
42+
43+
@DocumentedOption(description = "Whether to expose underlying spring Message to consumers or not.")
44+
public boolean exposeMessage = false;
45+
46+
@DocumentedOption(description = "Include support for enterprise envelop wrapping/unwrapping.")
47+
public boolean useEnterpriseEnvelope = false;
48+
49+
@DocumentedOption(description = "AsyncAPI Message extension name for the envelop java type for wrapping/unwrapping.")
50+
public String envelopeJavaTypeExtensionName = "x-envelope-java-type";
51+
52+
@DocumentedOption(description = "To avoid method erasure conflicts, when exposeMessage or reactive style this character will be used as separator to append message payload type to method names in consumer interfaces.")
53+
public String methodAndMessageSeparator = "$";
54+
55+
@DocumentedOption(description = "SC Streams Binding Name Prefix (used in @Component name)" )
56+
public String bindingPrefix = "";
57+
58+
@DocumentedOption(description = "SC Streams Binder class prefix")
59+
public String consumerPrefix = "";
60+
61+
@DocumentedOption(description = "SC Streams Binder class suffix")
62+
public String consumerSuffix = "Consumer";
63+
64+
@DocumentedOption(description = "Business/Service interface prefix")
65+
public String consumerServicePrefix = "I";
66+
67+
@DocumentedOption(description = "Business/Service interface suffix")
68+
public String consumerServiceSuffix = "ConsumerService";
69+
70+
@DocumentedOption(description = "Spring-Boot binding suffix. It will be appended to the operation name kebab-cased. E.g. <operation-id>-in-0")
71+
public String bindingSuffix = "-0";
72+
73+
@DocumentedOption(description = "AsyncAPI extension property name for runtime auto-configuration of headers.")
74+
public String runtimeHeadersProperty = "x-runtime-expression";
75+
76+
@DocumentedOption(description = "Annotation class to mark generated code (e.g. `org.springframework.aot.generate.Generated`). When retained at runtime, this prevents code coverage tools like Jacoco from including generated classes in coverage reports.")
77+
public String generatedAnnotationClass;
78+
79+
@DocumentedOption(description = "Templates to use for code generation.", values = {"SpringCloudStream", "SpringKafka", "FQ Class Name"})
80+
public String templates = "SpringCloudStream";
81+
82+
protected Templates configureTemplates() {
83+
if("SpringCloudStream".equals(templates)) {
84+
return new SpringCloudStreamTemplates(this);
85+
}
86+
if("SpringKafka".equals(templates)) {
87+
return new SpringKafkaTemplates(this);
88+
}
89+
// Instantiate FQ class name
90+
try {
91+
return (Templates) Class.forName(templates).getConstructor(AsyncAPIGenerator.class).newInstance(this);
92+
} catch (Exception e) {
93+
try {
94+
return (Templates) Class.forName(templates).getConstructor().newInstance();
95+
} catch (Exception ex) {
96+
throw new RuntimeException(e);
97+
}
98+
}
99+
}
100+
101+
public static String getApiClassName(String serviceName, OperationRoleType operationRoleType) {
102+
return operationRoleType != null? serviceName + operationRoleType.getServiceSuffix() : serviceName;
103+
}
104+
105+
}

0 commit comments

Comments
 (0)