AsyncAPI – Documentation of event- and message-driven architectures
source link: https://blog.codecentric.de/en/2021/09/asyncapi-documentation-event-message-driven-architectures/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
AsyncAPI – Documentation of event- and message-driven architectures
09/27/21 by Daniel Kocot
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/specification/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 withasyncapi: '2.1.0'.
This is followed by the description of the info object.
info:
title: Order Service
version: 1.0.0
description: The service is in charge of processing orders
contact:
name: Daniel Kocot
email: [email protected]
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
servers:
rabbitmq:
url: localhost:5672
description: RabbitMQ
protocol: amqp
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.
channels:
order-received:
publish:
operationId: orderReceivedPub
description: Payload of received order
message:
$ref: '#/components/messages/order'
bindings:
amqp:
timestamp: true
ack: false
bindingVersion: 0.2.0
subscribe:
operationId: orderReceivedSub
description: Payload of received order
message:
$ref: '#/components/messages/order'
bindings:
amqp:
is: routingKey
exchange:
name: orderExchange
type: direct
durable: true
vhost: /
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.
components:
messages:
order:
payload:
type: object
properties:
id:
type: integer
format: int64
description: ID of received order
customerReference:
type: string
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
.
api-showcases/async on main [?] on
➜ ag order-service.yaml @asyncapi/html-template
Done!
Check 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.
# Order Service 1.0.0 documentation
The service is in charge of processing orders
## Table of Contents
* [Servers](#servers)
* [Channels](#channels)
## Servers
### **rabbitmq** Server
| URL | Protocol | Description |
|---|---|---|
| localhost:5672 | amqp 0.9.1 | RabbitMQ |
## Channels
### **order-processed** Channel
#### `publish` 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)_
```json
{
"id": 0,
"customerReference": "string"
}
```
#### `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)_
```json
{
"id": 0,
"customerReference": "string"
}
```
Extending the documentation process with docToolchain
We can add the generated document to the existing architecture documentation in an extended docs-as-code toolchain with docToolchain. To do this, we run the generation command ag
with other parameters.
❯ 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.
➜ ./dtcw exportMarkdown
dtcw - docToolchain wrapper V0.22
docToolchain V2.0.0
Java Version 11
docker available
home folder exists
use local homefolder install /Users/danielkocot/.doctoolchain/
> Configure project :
arc42/arc42.adoc
BUILD SUCCESSFUL in 1s
1 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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK