AsyncAPI – Documentation of event- and message-driven architectures

26.9.2021 | 8 minutes of reading time

Most applications today are distributed and loosely coupled – which in turn means that the architecture consists of many self-contained components that may be maintained by different teams. The information that is exchanged between components should be visibly documented and maintained. In the realm of REST APIs, there is already an existing standard: OpenAPI (see web page ). AsyncAPI (see web page ) is a new counterpart for event- and message-driven architectures which was developed on the basis of OpenAPI. From an OpenAPI perspective, this makes it easier to start using.
Compared to OpenAPI, AsyncAPI is more agnostic in its approach with regard to possible protocols. Currently, the following protocols are supported: AMPQ(S), HTTP(S), IBM MQ, JMS, (Secure) Kafka, (Secure) MQTT, STOMP(S), (Secure) WebSocket and Mercure. In order to make this article more than just an introduction, I would like to demonstrate the use of AsyncAPI in practice with a simple use case.

OpenAPI vs. AsyncAPI

Before we get into the use case in detail, I would like to take a closer look at two points: Firstly, I would like to draw a comparison between OpenAPI and AsyncAPI and secondly, I would like to shed light on the difference between events and messages. As already described in the introduction, AsyncAPI emerged from OpenAPI. One aim is to create as much compatibility as possible between the two specifications so that it is possible to reuse parts. Reusability makes a lot of sense, especially with regard to the data models within Components.

Figure 1. Comparison of OpenAPI and AsyncAPI —- © https://www.asyncapi.com/docs/getting-started/coming-from-openapi

Figure 1 shows that the two specifications are really very similar in structure. In comparison to OpenAPI, channels are listed in the AsyncAPI. The channels are then also described protocol-agnostically in contrast to the paths. A complete description of the specification can be found at https://www.asyncapi.com/docs/specifications/v2.1.0 . After looking at the comparison between the two specifications, messages and events still need to be considered. The Reactive Manifesto contains a description of the difference, which I would like to briefly reproduce here. In a message-driven architecture, the producer knows the consumer. In event-driven architectures, on the other hand, the consumer decides which sources they want to subscribe to.

The use case

For the use case, a message regarding an order is to be transmitted from a producer via a queue to a consumer. In this case, RabbitMQ takes over the part of the message broker. The exact specification is now created via AsyncAPI. Any editor is sufficient to create the document. When using Spectral as a linter, it should be noted that it does not currently support the latest specification 2.1.0.

The specification

Every AsyncAPI-Spec starts with
asyncapi: '2.1.0'.

This is followed by the description of the info object.

2  title: Order Service
3  version: 1.0.0
4  description: The service is in charge of processing orders
5  contact:
6    name: Daniel Kocot
7    email: daniel.kocot@codecentric.de
8  license:
9    name: Apache 2.0
10    url: https://www.apache.org/licenses/LICENSE-2.0.html
12  rabbitmq:
13    url: localhost:5672
14    description: RabbitMQ
15    protocol: amqp
16    protocolVersion: '0.9.1'

As with OpenAPI, all information about the service is specified under info and servers. A key difference is the explicit specification of the protocol and its version in relation to the message broker under servers. The description of the service and the general setup is followed by the channel and the description of the respective operations. The specification contains a channel item object which refers to topics, routing keys, event types and paths.

2  order-received:
3    publish:
4      operationId: orderReceivedPub
5      description: Payload of received order
6      message:
7        $ref: '#/components/messages/order'
8      bindings:
9        amqp:
10          timestamp: true
11          ack: false
12          bindingVersion: 0.2.0
13    subscribe:
14      operationId: orderReceivedSub
15      description: Payload of received order
16      message:
17        $ref: '#/components/messages/order'
18    bindings:
19      amqp:
20        is: routingKey
21        exchange:
22          name: orderExchange
23          type: direct
24          durable: true
25          vhost: /
26        bindingVersion: 0.2.0

The channel-item object just mentioned now describes all operations of the respective channel in detail. However, there are only two operation objects, publish and subscribe.

publishMessages that are consumed by the application via the channel.
subscribeMessages that are created by the application and sent to the channel.

Via bindings, protocol-specific configurations can be carried out on the server, channel, operation and message levels. The bindings each have their own versioning. This must also be explicitly specified. Now let’s take a concrete look at the configuration on the channel level. The is property defines the type of channel. This can be either queue or routingKey, which is also the default value. Depending on the value, either a queue or an exchange must be described. In our case, it means that the message is not forwarded directly to the consumer, but is first passed to the exchange, which directs the message to a specific queue. The binding briefly describes how the channel functions within the messaging component. Within the description of the channel we also find the payload. This references a Message object, which is also the last element of the specification.

2  messages:
3    order:
4      payload:
5        type: object
6        properties:
7          id:
8            type: integer
9            format: int64
10            description: ID of received order
11          customerReference:
12            type: string
13            description: Reference for the customer according the order

Like the Schema object of the OpenAPI specification, the Message object is located below the Components object, but does not replace it in any way. Within the Components object, all reusable objects of a specification are grouped together and can be integrated via references. The AsyncAPI specification can also contain extensions, the Extensions. However, these are not used in the example.

Creating documentation

Now that we have worked our way through the specification for our use case, the question is how to turn this textual file into readable documentation for further use. Similar to OpenAPI, additional tooling is needed here. First, I would like to introduce the generator . In contrast to OpenAPI, the generator is written in JavaScript and is made available via npm or Docker. It can be used to create documentation in HTML or Markdown. It is also possible to generate initial snippets for certain programming languages and frameworks. We will disregard this option for the time being in this article. After the installation with npm npm install -g @asyncapi/generator we create a documentation artefact with the command ag.

1api-showcases/async on  main [?] on ☁️
2➜ ag order-service.yaml @asyncapi/html-template
5Done! ✨
6Check out your shiny new generated files at /Users/danielkocot/api-showcases/async.

If we now open the corresponding index.html, the documentation looks like in the screenshot below.

Besides using the generator, there are also various editors and IDE plug-ins to create previews of the specification on the fly. I currently use plug-ins for Visual Studio and IntelliJ .

In addition to HTML, we also have the option of generating a corresponding document in Markdown, for this we only need to use the Markdown template and we get the following result.

1# Order Service 1.0.0 documentation
3The service is in charge of processing orders
4## Table of Contents
6* [Servers](#servers)
7* [Channels](#channels)
9## Servers
11### **rabbitmq** Server
13| URL | Protocol | Description |
15| localhost:5672 | amqp 0.9.1 | RabbitMQ |
17## Channels
19### **order-processed** Channel
21#### `publish` Operation
23Payload of processed order
25##### Message
27*Inform about a new processed order in the system*
29###### Payload
31| Name | Type | Description | Accepted values |
33| id | integer | ID of received order | _Any_ |
34| customerReference | string | Reference for the customer according the order | _Any_ |
36> Examples of payload _(generated)_
40  "id": 0,
41  "customerReference": "string"

subscribe Operation

Payload of processed order


Inform about a new processed order in the system

NameTypeDescriptionAccepted values
idintegerID of received orderAny
customerReferencestringReference for the customer according the orderAny

Examples of payload (generated)

2  "id": 0,
3  "customerReference": "string"

<h3>Extending the documentation process with docToolchain</h3><p>We can add the generated document to the existing architecture documentation in an extended docs-as-code toolchain with <a href="https://github.com/docToolchain/docToolchain" target="_blank">docToolchain</a> . To do this, we run the generation command <code>ag</code> with other parameters.</p>

❯ ag -o /Users/danielkocot/doc-showcases/src/docs order-service.yaml @asyncapi/markdown-template -p outFilename=order-service.md

Done! ✨
Check out your shiny new generated files at /Users/danielkocot/doc-showcases/src/docs.

Now we find the specification in our architecture documentation folder. Using docToolchain we can convert the Markdown file into the AsciiDoc format as a first step. I use version 2.0.0 for this, which has been released recently. Version 2.0.0 also introduces a CLI that facilitates the use of docToolchain. The call looks like this.

1➜ ./dtcw exportMarkdown
2dtcw - docToolchain wrapper V0.22
3docToolchain V2.0.0
4Java Version 11
5docker available
6home folder exists
7use local homefolder install /Users/danielkocot/.doctoolchain/
9> Configure project :
131 actionable task: 1 executed

The converted file is available in the Build folder and can be incorporated from there into other AsciiDoc(tor) documents via include. This way we ensure that the specifications can be accessed in the process of creating an overall documentation.


We have seen that AsyncAPI opens up a possibility to make an event- or message-driven system understandable in a description language that can be read by humans and machines. In contrast to OpenAPI, however, the backend is known when writing the specification so that the peculiarities of the respective system can be addressed. Despite a version number greater than 1, the AsyncAPI spec is still under development. The tooling in the area of code generation is also currently still manageable, but is growing steadily. In connection with docToolchain, there are further possibilities with regard to integration into existing documentation. All in all, this is a very exciting topic that I will be coming back to time and again in the upcoming period.

share post




More articles in this subject area\n

Discover exciting further topics and let the codecentric world inspire you.


Gemeinsam bessere Projekte umsetzen

Wir helfen Deinem Unternehmen

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.