# server spring (AsyncAPI)

Source: https://apigenie.pl/techdocs/reference/cli-generate_server_spring_asyncapi

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

## Usage

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

## Description

`apigenie-generator server spring` generates from OpenAPI documents (see
[apigenie-generator server spring](/techdocs/reference/cli-generate_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](https://spring.io/projects/spring-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.

```java
@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:

```yaml
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`:

```yaml
overlay: 1.0.0
info:
  title: Java names
  version: 1.0.0
actions:
  - target: $.components.schemas.Order
    update:
      x-apigenie:
        codegen:
          name: PurchaseOrder
```

```sh
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.

```yaml
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 |
