apigenie-generatorAsyncAPI serversserver spring (AsyncAPI)

server spring (AsyncAPI)

Generate a Spring Boot application of the Kafka channels of an AsyncAPI document.

Usage

apigenie-generator server spring [...OPTIONS] INPUT_FILE

Description

apigenie-generator server spring generates from OpenAPI documents (see apigenie-generator server spring) and from AsyncAPI 3.x documents, telling them apart by their openapi or asyncapi field. Documents referencing other files are bundled first.

From an AsyncAPI document it generates a Maven project of a Spring Boot 3 application using Spring for Apache Kafka. By default the application is the one the document describes: it sends the messages of send operations and receives the messages of receive operations. Set the perspective option to counterpart to swap them.

The generator has one part per protocol. Today it generates the Kafka channels: the channels of servers with the kafka or kafka-secure protocol, and channels with a Kafka binding in documents without servers. Channels of other protocols are skipped and reported as info.

@Component
class LabelFarm implements RequestLabelRenderHandler {
    @Override
    public RequestLabelRenderReply onRenderLabel(RenderLabel message) {
        LabelRenderResult result = render(message.payload());
        return new LabelRendered(message.key(), result, new LabelRenderedHeaders(null));
    }
}

@Service
class Shipments {
    private final PublishShipmentEventsSender events;

    void delivered(String shipmentId, ShipmentDeliveredData data, ShipmentDeliveredHeaders headers) {
        events.sendShipmentDelivered(new ShipmentDelivered(shipmentId, data, headers));
    }
}

The generated code has:

  • models (package model): a record per object schema the messages use, with Jackson and Jakarta Bean Validation annotations (@NotNull for required properties, @Valid, @Pattern, @Size, @Min/@Max, @DecimalMin/@DecimalMax, @Email); an enum per enumeration; a sealed interface per oneOf of objects (@JsonTypeInfo by the discriminator property, or by deduction). allOf members are merged into one record. Schemas under #/components/schemas keep their name; inline schemas are named after their message (PlaceOrderPayload) or their owner and property (LabelRenderRequestFormat).
  • messages (package messages): a record per message with its Kafka key, its payload and its headers, and a headers record with the message’s headers, the headers of its traits merged as AsyncAPI does (JSON Merge Patch: a later required list replaces an earlier one). Headers with a constant value, e.g. the ce_specversion and ce_type of CloudEvents in binary mode, are constants of the headers record (ShipmentStatusChangedHeaders.CE_TYPE), which senders write and listeners compare. The key is a String validated by the key schema of the Kafka message binding.
  • Kafka (package kafka):
    • for every received operation, a handler interface (ConsumeOrdersHandler) with an on<Message> method per message, which you implement as a Spring bean, and a listener (ConsumeOrdersListener) with a @KafkaListener method. Several messages of a channel are told apart by a payload property or a header with a distinct constant value (e.g. type or ce_type); otherwise the first message whose payload is valid is chosen. Records matching no message go to onUnknown, which ignores them by default.
    • for every sent operation, a sender (PublishShipmentEventsSender) with a send<Message>(message) method per message, over a KafkaTemplate using the operation’s client id.
    • request/reply operations with a reply channel: the requester’s sender uses a ReplyingKafkaTemplate listening on the reply channel’s topic and returns CompletableFuture<<Operation>Reply>, a sealed interface of the reply messages. Replies are matched by the header of the request message’s correlationId, generated when the request has none. A replier’s handler returns the reply, which the listener sends to the reply topic with the request’s correlation id copied (from a header or a payload property, as the correlationId locations say).
    • KafkaJson, converting payloads (JSON) and headers (UTF-8 text) and validating messages, and KafkaTopicsConfiguration, creating the topics of the channels from their Kafka bindings (partitions, replicas, topicConfiguration) when asyncapi.kafka.create-topics is true.
  • project files: pom.xml (parent spring-boot-starter-parent, spring-boot-starter, spring-boot-starter-json, spring-boot-starter-validation (with bean_validation), spring-kafka, and the test starters), the @SpringBootApplication class and src/main/resources/application.yaml.

application.yaml points spring.kafka.bootstrap-servers to the first Kafka server (server variables replaced by their default) and configures String keys and byte array values. The asyncapi.kafka section holds every Kafka server, the topic of every channel and the group and client ids of every operation, which the generated code reads, so that they can be changed without regenerating:

asyncapi:
  kafka:
    create-topics: false
    servers:
      production-kafka:
        bootstrap-servers: kafka-eu.events.carpetexpress.example:9093
        schema-registry-url: https://schemas.events.carpetexpress.example
    channels:
      shipment-events:
        topic: carpetexpress.shipment-events.v2
    operations:
      consume-carrier-scans:
        group-id: tracking-ingest
        client-id: carpet-express-consume-carrier-scans

Group and client ids come from the groupId and clientId schemas of the Kafka operation bindings, of the operation or its traits: their const, single enum value, default or first example. Without a value, <artifact id>-<operation> is used, with a comment naming the binding’s pattern.

Constraints Bean Validation can’t express are line comments of the record or component: draft-07 dependencies, if/then/else, not, tuples (items arrays, mapped to List with @Size), patternProperties, const values, and anyOf/oneOf branches constraining the properties of an object.

Not supported, and reported as warnings: Avro payloads and payloads of other non-JSON schema formats (the messages are skipped, and operations left without a message too), reply addresses given by runtime expressions, and Kafka topics with parameters. Security (SASL/SCRAM, mTLS) and the schema registry are not configured. With a correlation id in the payload, the replier’s reply must carry the property (the listener overwrites it with the request’s), and a requester matches replies by the kafka_correlationId header, which the replier must copy.

Extensions

Extension Effect
x-apigenie.codegen.name name of the class of a schema
x-apigenie.codegen.disabled true on a channel or an operation leaves it out (with the operations of a channel left out)

The extensions can live in an overlay for this target instead of in the document, e.g. spring.overlay.yaml:

overlay: 1.0.0
info:
  title: Java names
  version: 1.0.0
actions:
  - target: $.components.schemas.Order
    update:
      x-apigenie:
        codegen:
          name: PurchaseOrder
apigenie-generator server spring asyncapi.yaml --overlay spring.overlay.yaml -o out

Options

-c, –config

Path to the configuration file to use. See the “Configuration” section for more information.

–overlay OVERLAY

Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the “Extensions” section. Can be repeated: the overlays apply in order, after those listed under commands.generate.server_spring.overlay in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays apply to the AsyncAPI document bundled with the files it references.

–strict

Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.).

-o, –output

Directory where the project is generated.

–package-name

Maven artifact id of the project. Defaults to the title of the API in kebab case. The base package defaults to com.example. followed by the artifact id in lower case without other characters than letters and digits (prefixed with app when it starts with a digit or is a Java keyword).

–package-version

Version of the project. Defaults to the version of the API.

-p, –property

Applies to OpenAPI documents only; using it with an AsyncAPI document is an error.

Configuration

The options are read from the asyncapi section of the configuration of apigenie-generator server spring; its other keys configure the generation from OpenAPI documents.

version: '1.0'
commands:
  generate:
    server_spring:
      asyncapi:
        base_package: com.acme.events
        group_id: com.acme
        java_version: 21
        spring_boot_version: 3.3.13
        bean_validation: true
        perspective: application
Option Description
base_package Package of the generated code (default: com.example.<artifact id>); models are in .model, messages in .messages, the Kafka code in .kafka
group_id Maven group id (default: the base package)
java_version Java release, 17 (default) or later
spring_boot_version Version of the Spring Boot 3 parent (default 3.3.13)
bean_validation true (default): Bean Validation annotations, and validation of sent and received messages; false: none
perspective application (default): the described application; counterpart: its counterpart, receiving what it sends