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 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.
1info: 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 11servers: 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.
1channels: 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
.
publish | Messages that are consumed by the application via the channel. |
subscribe | Messages 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.
1components: 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 3 4 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 2 3The service is in charge of processing orders 4## Table of Contents 5 6* [Servers](#servers) 7* [Channels](#channels) 8 9## Servers 10 11### **rabbitmq** Server 12 13| URL | Protocol | Description | 14|---|---|---| 15| localhost:5672 | amqp 0.9.1 | RabbitMQ | 16 17## Channels 18 19### **order-processed** Channel 20 21#### `publish` Operation 22 23Payload of processed order 24 25##### Message 26 27*Inform about a new processed order in the system* 28 29###### Payload 30 31| Name | Type | Description | Accepted values | 32|---|---|---|---| 33| id | integer | ID of received order | _Any_ | 34| customerReference | string | Reference for the customer according the order | _Any_ | 35 36> Examples of payload _(generated)_ 37 38```json 39{ 40 "id": 0, 41 "customerReference": "string" 42}
subscribe
Operation
Payload of processed order
Message
Inform about a new processed order in the system
Payload
Name | Type | Description | Accepted values |
---|---|---|---|
id | integer | ID of received order | Any |
customerReference | string | Reference for the customer according the order | Any |
Examples of payload (generated)
1{ 2 "id": 0, 3 "customerReference": "string" 4}
<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>
```highlight
❯ 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/ 8 9> Configure project : 10arc42/arc42.adoc 11 12BUILD SUCCESSFUL in 1s 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.
Summary
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.
More articles
fromDaniel Kocot
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
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.
Blog author
Daniel Kocot
Senior Solution Architect / Head of API Consulting
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.