Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

<Note. use green for closed issues, yellow for important ones if needed>

AsyncAPI


Parser

The parser is used to validate AsynAPI documents. AsyncAPI documents can be written in either YAML or JSON. There are parsers available for various programming languages including Go, C# and Javascript. For CPS we would leverage the AsyncAPI Parser Java Wrapper.

...

The AsyncAPI generator is a tool that generates anything you want using the AsyncAPI Document and Template that are supplied as inputs to the AsyncAPI CLI. The template can define code, documentation and diagrams among other things to be generated with the generator.

Example AsyncAPI Document

View file
namekafka.yml
height250


Servers

Code Block
languageyml
titleservers
servers:
  production:
    url: kafka.bootstrap:{port}
    protocol: kafka
    variables:
      port:
        default: '9092'
        enum:
          - '9092'
          - '9093'


Channels

A channel is a mechanism created by the server for the organization and transmission of messages. Users can define channels as a topic, queue, routing key, path, or subject depending on the protocol used.

Code Block
languageyml
titlechannels
channels:
  event.lighting.measured:
    publish:
      bindings:
        kafka:
          groupId: my-group
      operationId: readLightMeasurement
      message:
        $ref: '#/components/messages/lightMeasured'
    subscribe:
      operationId: updateLightMeasurement
      message:
        $ref: '#/components/messages/lightMeasured'


Example AsyncAPI Document

View file
namekafka.yml
height250

Code Generators

1. ZenWave SDK

ZenWave 360º is a set of tools built on the foundations of Domain Driven Design and API-First principles for Event-Driven Architectures, to help you create software easy to understand.

With ZenWave's spring-cloud-streams3 and jsonschema2pojo sdk plugins you can generate:

  • Strongly typed business interfaces
  • Payload DTOs
  • Header objects from AsyncAPI definitions.
  • It uses Spring Cloud Streams, so it can connect to different brokers via provided binders.


Using the kafka.yml the pom.xml can be configured to create DTO's:

Code Block
 <plugin>
                <groupId>io.github.zenwave360.zenwave-sdk</groupId>
                <artifactId>zenwave-sdk-maven-plugin</artifactId>
                <version>1.3.5</version>
                <configuration>
                    <skip>false</skip>
                    <inputSpec>${project.basedir}/src/main/resources/asyncapi.yml</inputSpec>
                    <addCompileSourceRoot>true</addCompileSourceRoot>
                    <addTestCompileSourceRoot>true</addTestCompileSourceRoot>
                </configuration>
                <executions>
                    <execution>
                        <id>generate-asyncapi-dtos</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <generatorName>jsonschema2pojo</generatorName>
                            <configOptions>
                                <jsonschema2pojo.removeOldOutput>true</jsonschema2pojo.removeOldOutput>
                                <jsonschema2pojo.useJakartaValidation>true</jsonschema2pojo.useJakartaValidation>
                                <modelPackage>com.example.asyncmethod.models</modelPackage>
                                <apiPackage>com.example.asyncmethod.api</apiPackage>
                                <jsonschema2pojo.useLongIntegers>true</jsonschema2pojo.useLongIntegers>
                            </configOptions>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>io.github.zenwave360.zenwave-sdk.plugins</groupId>
                        <artifactId>asyncapi-spring-cloud-streams3</artifactId>
                        <version>1.3.5</version>
                    </dependency>
                    <dependency>
                        <groupId>io.github.zenwave360.zenwave-sdk.plugins</groupId>
                        <artifactId>asyncapi-jsonschema2pojo</artifactId>
                        <version>1.3.5</version>
                    </dependency>
                    <dependency>
                        <groupId>jakarta.validation</groupId>
                        <artifactId>jakarta.validation-api</artifactId>
                        <version>3.0.2</version>
                    </dependency>
                </dependencies>
            </plugin>


Problem

The useJakartaValidation configuration option does not appear to work. This results in generated code using javax.validation that does not always work with Java 17.


Resulting generated class example:

Code Block
languagejava
package com.example.asyncmethod.models;

import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.validation.Valid;
import javax.validation.constraints.DecimalMin;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
    "lumens",
    "sentAt"
})
public class lightMeasuredPayload implements Serializable
{

    /**
     * Light intensity measured in lumens.
     * 
     */
    @JsonProperty("lumens")
    @JsonPropertyDescription("Light intensity measured in lumens.")
    @DecimalMin("0")
    private Long lumens;
    /**
     * Date and time when the message was sent.
     * 
     */
    @JsonProperty("sentAt")
    @JsonPropertyDescription("Date and time when the message was sent.")
    private String sentAt;
    @JsonIgnore
    @Valid
    private Map<String, Object> additionalProperties = new LinkedHashMap<String, Object>();
    protected final static Object NOT_FOUND_VALUE = new Object();
    private final static long serialVersionUID = 6984519541679788517L;

    /**
     * Light intensity measured in lumens.
     * 
     */
    @JsonProperty("lumens")
    public Long getLumens() {
        return lumens;
    }

    /**
     * Light intensity measured in lumens.
     * 
     */
    @JsonProperty("lumens")
    public void setLumens(Long lumens) {
        this.lumens = lumens;
    }

    public lightMeasuredPayload withLumens(Long lumens) {
        this.lumens = lumens;
        return this;
    }

    /**
     * Date and time when the message was sent.
     * 
     */
    @JsonProperty("sentAt")
    public String getSentAt() {
        return sentAt;
    }

    /**
     * Date and time when the message was sent.
     * 
     */
    @JsonProperty("sentAt")
    public void setSentAt(String sentAt) {
        this.sentAt = sentAt;
    }

    public lightMeasuredPayload withSentAt(String sentAt) {
        this.sentAt = sentAt;
        return this;
    }

    @JsonAnyGetter
    public Map<String, Object> getAdditionalProperties() {
        return this.additionalProperties;
    }

    @JsonAnySetter
    public void setAdditionalProperty(String name, Object value) {
        this.additionalProperties.put(name, value);
    }

    public lightMeasuredPayload withAdditionalProperty(String name, Object value) {
        this.additionalProperties.put(name, value);
        return this;
    }

    protected boolean declaredProperty(String name, Object value) {
        if ("lumens".equals(name)) {
            if (value instanceof Long) {
                setLumens(((Long) value));
            } else {
                throw new IllegalArgumentException(("property \"lumens\" is of type \"java.lang.Long\", but got "+ value.getClass().toString()));
            }
            return true;
        } else {
            if ("sentAt".equals(name)) {
                if (value instanceof String) {
                    setSentAt(((String) value));
                } else {
                    throw new IllegalArgumentException(("property \"sentAt\" is of type \"java.lang.String\", but got "+ value.getClass().toString()));
                }
                return true;
            } else {
                return false;
            }
        }
    }

    protected Object declaredPropertyOrNotFound(String name, Object notFoundValue) {
        if ("lumens".equals(name)) {
            return getLumens();
        } else {
            if ("sentAt".equals(name)) {
                return getSentAt();
            } else {
                return notFoundValue;
            }
        }
    }

    @SuppressWarnings({
        "unchecked"
    })
    public<T >T get(String name) {
        Object value = declaredPropertyOrNotFound(name, lightMeasuredPayload.NOT_FOUND_VALUE);
        if (lightMeasuredPayload.NOT_FOUND_VALUE!= value) {
            return ((T) value);
        } else {
            return ((T) getAdditionalProperties().get(name));
        }
    }

    public void set(String name, Object value) {
        if (!declaredProperty(name, value)) {
            getAdditionalProperties().put(name, ((Object) value));
        }
    }

    public lightMeasuredPayload with(String name, Object value) {
        if (!declaredProperty(name, value)) {
            getAdditionalProperties().put(name, ((Object) value));
        }
        return this;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(lightMeasuredPayload.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('[');
        sb.append("lumens");
        sb.append('=');
        sb.append(((this.lumens == null)?"<null>":this.lumens));
        sb.append(',');
        sb.append("sentAt");
        sb.append('=');
        sb.append(((this.sentAt == null)?"<null>":this.sentAt));
        sb.append(',');
        sb.append("additionalProperties");
        sb.append('=');
        sb.append(((this.additionalProperties == null)?"<null>":this.additionalProperties));
        sb.append(',');
        if (sb.charAt((sb.length()- 1)) == ',') {
            sb.setCharAt((sb.length()- 1), ']');
        } else {
            sb.append(']');
        }
        return sb.toString();
    }

    @Override
    public int hashCode() {
        int result = 1;
        result = ((result* 31)+((this.lumens == null)? 0 :this.lumens.hashCode()));
        result = ((result* 31)+((this.sentAt == null)? 0 :this.sentAt.hashCode()));
        result = ((result* 31)+((this.additionalProperties == null)? 0 :this.additionalProperties.hashCode()));
        return result;
    }

    @Override
    public boolean equals(Object other) {
        if (other == this) {
            return true;
        }
        if ((other instanceof lightMeasuredPayload) == false) {
            return false;
        }
        lightMeasuredPayload rhs = ((lightMeasuredPayload) other);
        return ((((this.lumens == rhs.lumens)||((this.lumens!= null)&&this.lumens.equals(rhs.lumens)))&&((this.sentAt == rhs.sentAt)||((this.sentAt!= null)&&this.sentAt.equals(rhs.sentAt))))&&((this.additionalProperties == rhs.additionalProperties)||((this.additionalProperties!= null)&&this.additionalProperties.equals(rhs.additionalProperties))));
    }

}


Zenwave SDK Documentation

2. MultiAPI Generator

In order to get this plugin working, you need the following things installed in your computer:

  • Java 11 Version
  • Maven

MultiAPI Generator Documentation


Documentation Generator

Springwolf

Springwolf is  a project inspired by Springfox for the purpose of documenting Asynchronous APIs. Springwolf covers many different protocols.

Image Added

Springboot listeners are used to generate documentation e.g. @KafkaListener @RabbitListener 

Dependencies used:

Image Added

Resulting documentation:

Image Added