An introduction to the AsyncAPI specification
Tags: asyncapi, openapi, eventdriven, microservices
Published: 2022-01-31 12:45:54

---
title: An introduction to the AsyncAPI specification
published: true
description: An introduction to the AsyncAPI specification
tags: asyncapi, openapi, eda, microservices
cover_image: https://eltonminetto.dev/images/posts/asyncapi.png
---
If you develop or consume REST APIs, you have probably come across some documentation written according to the [OpenAPI](https://www.openapis.org) specification. It is the industry standard, although I prefer to document using [API Blueprint](https://apiblueprint.org) :)

But the subject of this post is another specification, the [AsyncAPI](https://www.asyncapi.com). Inspired by the OpenAPI, the AsyncAPI goal is to document applications that use the *Event-Driven Architectures* or *EDA*. 

In the following image, we can see a comparison between the two patterns:

[![apidoc1](https://eltonminetto.dev/images/posts/openapi_asyncapi_comparision.png)](/images/posts/apidoc1.png)

Like its big sister, the AsyncAPI allows us to generate documentation in different formats and generate code, thanks to a series of tools created by the community.

In this post, I will demonstrate the use of the AsyncAPI with a straightforward example. Let's model a system based on microservices and EDA for a company that sells Software as a Service (*SaaS*). The system is composed of three microservices:

- `accounts`: service responsible for registering new users. It generates the `user-registered` event.

- `subscription`: service responsible for controlling the subscription of the company's plans by users. It "listens" for the `user-registered` event and can generate the `user-subscribed` and `user-unsubscribed` events.

- `finance`: service responsible for the financial control of the plans. It "listens" for the `user-subscribed` and `user-unsubscribed` events, and based on this information, it can generate the `payment-succeded` and `payment-failed` events.

In this context, when I use the term `listen,` it means that the service *subscribes* to the event, or it is a *consumer* of this event. And when I use the term `generate,` it means that the service *publishes* an event, or it is a *producer* of this type of occurrence. 

These concepts are pretty standard in event-based applications, like our example. The [Reactive Manifesto](https://www.reactivemanifesto.org/glossary#Message-Driven) contains a concise description of the difference between message-based and event-based architectures:

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

Let's now start documenting our project. First, I created a `docs` directory and the file `saas-service.yaml` inside it. The first lines of the file contain:


```yml
asyncapi: 2.2.0
info:
  title: Awesome SaaS
  version: 1.0.0
  description: The Awesome Saas Company
  contact:
    name: Elton Minetto
    email: elton@minetto.dev
```

These lines define the version of [specification](https://www.asyncapi.com/docs/specifications/v2.2.0) we are using and some background information. 

Let's now include information related to how we will process project events. The [specification](https://www.asyncapi.com/docs/specifications/v2.2.0#serversObject) does not limit the user to a specific solution. In this example, we will use [RabbitMQ](https://www.rabbitmq.com), but we could use Kafka, [Mosquito](https://mosquito.org), among others. So let's include in our document the configuration of our RabbitMQ servers:

```yml
servers:
  rabbitmq-dev:
    url: localhost:5672
    description: Local RabbitMQ
    protocol: amqp
    protocolVersion: "0.9.1"
  rabbitmq-staging:
    url: staging-rabbitmq.server.saas.com:5672
    description: Staging RabbitMQ
    protocol: amqp
    protocolVersion: "0.9.1"
  rabbitmq-prod:
    url: rabbitmq.server.saas.com:5672
    description: Production RabbitMQ
    protocol: amqp
    protocolVersion: "0.9.1"
```

The next step is to include information related to the events that our system will process. Let's start with publishing and subscribing to the `user-registered` event:


```yml
channels:
  user-registered:
    publish:
      operationId: userRegisteredPub
      description: The payload of user registration
      message:
        $ref: "#/components/messages/user"
      bindings:
        amqp:
          timestamp: true
          ack: false
          bindingVersion: 0.2.0
    subscribe:
      operationId: userRegisteredSub
      description: The payload of user registration
      message:
        $ref: "#/components/messages/user"
    bindings:
      amqp:
        is: routingKey
        exchange:
          name: userExchange
          type: direct
          durable: true
          vhost: /
        bindingVersion: 0.2.0
```

Inside `channels,` we will specify the way services can publish and consume this event. We are using RabbitMQ as our `message broker`, so the information inside `bindings` has specific configurations of this solution. Each solution has unique settings, as stated in [documentation](https://www.asinapi.com/docs/specifications/v2.2.0#channelBindingsObject). Another important point is the `message` key that references (`$ref`) a message that we are going to define now:

```yml
components:
  messages:
    user:
      payload:
        type: object
        properties:
          id:
            type: integer
            format: int64
            description: ID of user
          name:
            type: string
            description: Name of user
          email:
            type: string
            description: E-mail of user
          password:
            type: string
            format: password
            description: Password of user
          registered_at:
            type: string
            format: date-time
            description: Timestamp of registration
```

The use of these references is helpful to reuse the information in various events, if necessary. 

In the [documentation](https://www.asyncapi.com/docs/specifications/v2.2.0#dataTypeFormat), you can see all the data types that the specification supports. 

After this introduction, we continued including the other events, and the final file looked like this:

```yml
asyncapi: 2.2.0
info:
  title: Awesome SaaS
  version: 1.0.0
  description: The Awesome Saas Company
  contact:
    name: Elton Minetto
    email: elton@minetto.dev
servers:
  rabbitmq-dev:
    url: localhost:5672
    description: Local RabbitMQ
    protocol: amqp
    protocolVersion: "0.9.1"
  rabbitmq-staging:
    url: staging-rabbitmq.server.saas.com:5672
    description: Staging RabbitMQ
    protocol: amqp
    protocolVersion: "0.9.1"
  rabbitmq-prod:
    url: rabbitmq.server.saas.com:5672
    description: Production RabbitMQ
    protocol: amqp
    protocolVersion: "0.9.1"
channels:
  user-registered:
    publish:
      operationId: userRegisteredPub
      description: The payload of user registration
      message:
        $ref: "#/components/messages/user"
      bindings:
        amqp:
          timestamp: true
          ack: false
          bindingVersion: 0.2.0
    subscribe:
      operationId: userRegisteredSub
      description: The payload of user registration
      message:
        $ref: "#/components/messages/user"
    bindings:
      amqp:
        is: routingKey
        exchange:
          name: userExchange
          type: direct
          durable: true
          vhost: /
        bindingVersion: 0.2.0
  user-subscribed:
    publish:
      operationId: userSubscribedPub
      description: The payload of user subscription
      message:
        $ref: "#/components/messages/subscription"
      bindings:
        amqp:
          timestamp: true
          ack: false
          bindingVersion: 0.2.0
    subscribe:
      operationId: userSubscribedSub
      description: The payload of user subscription
      message:
        $ref: "#/components/messages/subscription"
    bindings:
      amqp:
        is: routingKey
        exchange:
          name: subscriptionExchange
          type: direct
          durable: true
          vhost: /
        bindingVersion: 0.2.0
  user-unsubscribed:
    publish:
      operationId: userUnsubscribedPub
      description: The payload of user unsubscription
      message:
        $ref: "#/components/messages/unsubscription"
      bindings:
        amqp:
          timestamp: true
          ack: false
          bindingVersion: 0.2.0
    subscribe:
      operationId: userUnsubscribedSub
      description: The payload of user unsubscription
      message:
        $ref: "#/components/messages/unsubscription"
    bindings:
      amqp:
        is: routingKey
        exchange:
          name: unsubscriptionExchange
          type: direct
          durable: true
          vhost: /
        bindingVersion: 0.2.0
  payment-succeeded:
    publish:
      operationId: paymentSucceededPub
      description: The payload of successful payment
      message:
        $ref: "#/components/messages/payment"
      bindings:
        amqp:
          timestamp: true
          ack: false
          bindingVersion: 0.2.0
    subscribe:
      operationId: paymentSucceededSub
      description: The payload of successful payment
      message:
        $ref: "#/components/messages/payment"
    bindings:
      amqp:
        is: routingKey
        exchange:
          name: paymentSucceededExchange
          type: direct
          durable: true
          vhost: /
        bindingVersion: 0.2.0
  payment-failed:
    publish:
      operationId: paymentFailedPub
      description: The payload of failed payment
      message:
        $ref: "#/components/messages/payment"
      bindings:
        amqp:
          timestamp: true
          ack: false
          bindingVersion: 0.2.0
    subscribe:
      operationId: paymentFailedSub
      description: The payload of failed payment
      message:
        $ref: "#/components/messages/payment"
    bindings:
      amqp:
        is: routingKey
        exchange:
          name: paymentFailedExchange
          type: direct
          durable: true
          vhost: /
        bindingVersion: 0.2.0
components:
  messages:
    user:
      payload:
        type: object
        properties:
          id:
            type: integer
            format: int64
            description: ID of user
          name:
            type: string
            description: Name of user
          email:
            type: string
            description: E-mail of user
          password:
            type: string
            format: password
            description: Password of user
          registered_at:
            type: string
            format: date-time
            description: Timestamp of registration
    subscription:
      payload:
        type: object
        properties:
          id:
            type: integer
            format: int64
            description: ID of subscription
          user_id:
            type: integer
            format: int64
            description: ID of user
          plan_id:
            type: integer
            format: int64
            description: ID of plan
          plan_name:
            type: string
            description: Name of plan
          plan_value:
            type: number
            format: float
            description: Value of plan
          subscribed_at:
            type: string
            format: date-time
            description: Timestamp of subscription
    unsubscription:
      payload:
        type: object
        properties:
          id:
            type: integer
            format: int64
            description: ID of subscription
          unsubscribed_at:
            type: string
            format: date-time
            description: Timestamp of unsubscription
    payment:
      payload:
        type: object
        properties:
          id:
            type: integer
            format: int64
            description: ID of payment
          user_id:
            type: integer
            format: int64
            description: ID of user
          plan_id:
            type: integer
            format: int64
            description: ID of plan
          value:
            type: number
            format: float
            description: Value of payment
          created_at:
            type: string
            format: date-time
            description: Timestamp of payment
```

## Generating the documentation

With the file created, we can now generate more friendly documentation. For this, we need to install [generator](https://github.com/asyncapi/generator) or use it in its Docker version. 

To install locally, you need `npm` and run the command:

```bash
npm install -g @asyncapi/generator
```

With the generator installed, just run:

```bash
mkdir docs/html
ag docs/saas-service.yaml -o docs/html/ @asyncapi/html-template --force-write
```

Or, using the command via Docker:

```bash
docker run --rm -it \
-v ${PWD}/docs/saas-service.yaml:/app/saas-service.yaml \
-v ${PWD}/docs/html:/app/output \
asyncapi/generator -o /app/output /app/saas-service.yaml @asyncapi/html-template --force-write
```

The generated documentation is handy and easy to understand:

[![apidoc1](https://eltonminetto.dev/images/posts/asyncapi.png)](/images/posts/asyncapi.png)

It is also possible to create the documentation in Markdown format.

### Generating code

The generator can also generate source code. According to [documentation](https://www.asyncapi.com/tools/generator), it is possible to generate code in Node.js, Java, and Python. For example, I ran the command below:

```bas
 ag docs/saas-service.yaml @asyncapi/python-paho-template -o src
```

And the directory was created, with codes in the language:

```bash
asyncapi_post ⟩ ls  src/
README.md           entity.py           messaging.py
config-template.ini main.py             payload.py
```

As I am not fluent in any of the three supported languages, I will not comment on the quality of the generated code. Instead, I leave it to the reader to test and give their opinions in this post's comments.

## Tools

In addition to the generator, there is an [online tool](https://studio.asynkapi.com) to test the specification and [Github Actions](https://www.asynkapi.com/tools/github-actions), parsers, and plugins for [VSCode](https://github.com/asynkapi/vs-asynkapi-preview) and IDEs based on [IntelliJ](https://plugins.jetbrains.com/plugin/15673-asyncapi).


### Conclusions

It is undeniable that good documentation is crucial for any project, even more so in asynchronous and event-based communication cases. And having a standard adopted by big companies like Slack, SAP, and Salesforce dramatically increases the importance of AsyncAPI. 

My only complaints are:

- the fact that I need to write YAML files (I'm a grumpy old man who doesn't like the format, but that's personal taste)
- the generator could be faster (maybe there are other implementations in Go or Rust, but I didn't do extensive research about this for this post). 

Anyway, it seems to me that learning this spec is a good investment.