Skip to content

[FEATURE] Kafka Operation Binding: error-topics for Retry and DLQ Topic Provisioning #299

Description

@ivangsa

Why do we need this improvement?

Motivation

Enable provisioning complete Kafka infrastructure from AsyncAPI definitions alone, making AsyncAPI the single source of truth, not just for the API contract but for the full cluster topology.

With channel bindings you can already provision main topics. But retry and DLQ topics have to be defined somewhere else. Today there are two options:

  • Define them outside AsyncAPI. This breaks the single source of truth and causes API drift.
  • Define them as channels and operations. A single receive operation with 5 retry topics and a DLQ would require 6 additional channels and 12 additional operations. And they should not be there: retry and DLQ topics are not part of a service's public API. They are internal infrastructure owned by one consumer.

This proposal closes that gap without touching the contract.

How will this change help?

This will allow teams define and provision error topics using AsyncAPI as the single source or truth in a non-verbose non-invasive manner.

ZenWave SDK supports generating Terraform from AsyncAPI files for provisioning Kafka OSS, Schema Registry or Confluent Platform and uses this as an extension to provision kafka error topics.

https://www.zenwave360.io/zenwave-sdk/plugins/asyncapi-ops/

Screenshots

No response

How could it be implemented/designed?

Proposed Design

Placement

At the operation binding level, under bindings.kafka. Retry and DLQ topics are a consumer concern, the producer has no involvement.

operations:
  onSomethingHappened:
    action: receive
    channel:
      $ref: '#/channels/some-channel'
    bindings:
      kafka:
        groupId:
          type: string
          enum: ["some.consumer.service"]
        error-topics:
          addressTemplate: "${groupId}.__.${channel.address}.${suffix}"
          retryTopics: 3
          retry:
            partitions: 1
            replicas: 2
            topicConfiguration:
              retention.ms: 259200000
          dlq:
            partitions: 1
            replicas: 2
            topicConfiguration:
              retention.ms: 2592000000
              cleanup.policy: ["delete"]

Schema

error-topics

Field Type Required Description
addressTemplate string Yes Template for computing topic addresses. See below.
retryTopics integer No Number of retry topics. Creates suffixes retry-0 through retry-{N-1}. Required when retry is present.
retry object No Config for retry topics. Omit to skip provisioning.
dlq object No Config for the DLQ topic. Omit to skip provisioning.

retry and dlq

Field Type Required Description
partitions integer No Number of partitions.
replicas integer No Replication factor.
topicConfiguration object No Same schema as channel binding topicConfiguration.

Environment-specific overrides are out of scope here. The env-server-overrides mechanism from #292 applies the same merge semantics and can be extended to cover retry and dlq if that proposal is adopted.

Address Template

Variable Resolved value
${groupId} First value of the groupId enum in the operation binding.
${channel.address} The address of the referenced channel.
${suffix} retry-0, retry-1, … retry-{N-1}, dlq.

${groupId} resolution. groupId is a schema object (type: string, enum: [...]), not a plain string. Tooling uses the first enum value. If absent or no enum is defined, resolves to empty string and tooling should emit a warning.

Example — groupId: some.consumer.service, channel address some.domain.someservice.some-event.avro.v0, retryTopics: 3, template ${groupId}.__.${channel.address}.${suffix}:

some.consumer.service.__.some.domain.someservice.some-event.avro.v0.retry-0
some.consumer.service.__.some.domain.someservice.some-event.avro.v0.retry-1
some.consumer.service.__.some.domain.someservice.some-event.avro.v0.retry-2
some.consumer.service.__.some.domain.someservice.some-event.avro.v0.dlq

The .__. separator is suggested to visually distinguish consumer group from original topic. Tooling can split on .__. to recover both parts unambiguously.

Reusable Plans via components

components:
  error-topics:
    retry:
      silver:
        partitions: 1
        replicas: 2
        topicConfiguration:
          retention.ms: 259200000       # 3 days
      gold:
        partitions: 3
        replicas: 3
        topicConfiguration:
          retention.ms: 604800000       # 7 days
    dlq:
      standard:
        partitions: 1
        replicas: 2
        topicConfiguration:
          retention.ms: 2592000000      # 30 days
          cleanup.policy: ["delete"]
      compliance:
        partitions: 1
        replicas: 3
        topicConfiguration:
          retention.ms: 31536000000     # 1 year
          cleanup.policy: ["delete"]
operations:
  onSomethingHappened:
    bindings:
      kafka:
        groupId:
          type: string
          enum: ["some.consumer.service"]
        error-topics:
          addressTemplate: "${groupId}.__.${channel.address}.${suffix}"
          retryTopics: 3
          retry:
            $ref: '#/components/error-topics/retry/silver'
          dlq:
            $ref: '#/components/error-topics/dlq/compliance'

🚧 Breaking changes

No

👀 Have you checked for similar open issues?

  • I checked and didn't find a similar issue

🏢 Have you read the Contributing Guidelines?

Are you willing to work on this issue?

Yes I am willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions