Documenting event-driven APIs with AsyncAPI

In this post Mauro tells us about AsyncApi: a tool for documenting Event-Driven architectures. Follow this tutorial to set it up!

Documenting event-driven APIs with AsyncAPI
Hey, do you remember what this field looks like in GET /operations?

Ever since I started working in a team, the answer to that first question has been pretty straightforward:

Sure, let’s check the OpenAPI documentation file.

After two or three times, people who didn’t work with our codebase started remembering where the information was, and stopped asking altogether. At least when it came to synchronous HTTP APIs. But as the previous question disappeared, another, slightly different one arose:

Which are the fields we need to send in this asynchronous message?

And the answer was not as easy. Finding it involved diving into the code, looking at DTO and data mapping objects, or even ancient Slack messages. While the HTTP API documentation was stored in a well-known file, everything event-driven API related was scrambled around. Wouldn’t it be nice if we could deal with both the synchronous and the asynchronous APIs in the same way? Turns out, we can!

AsyncAPI

Asking around, I stumbled upon AsyncAPI, a specification just like OpenAPI, but made for Event-Driven architectures. Just like with OpenAPI, it is used to describe how to communicate with an application, defining channels and message formats. Let’s see how to use it with an example:

One of the requirements was a way to notify of an event to our provider, only once. We decided to use SQS, an AWS message queueing service, into which our backend publishes messages with a specific format, and the provider listens to them.

A simple diagram showing an application (our backend) that publishes messages in an SQS named OperationCreationQueue, and an external application that consumes those messages.
A simple diagram showing an application (our backend) that publishes messages in an SQS named OperationCreationQueue, and an external application that consumes those messages.

To begin, we need to define the basics: the specification version, and our API name and version, in a new .yaml file.

asyncapi: '2.4.0'
info:
  title: Best API in the world

Once that’s out of the way, we’ll add a new channel, through which the messages are delivered. In our case, that channel is our SQS:

asyncapi: '2.4.0'
info:
  title: Best API in the world
  version: 0.1.0
channels:
  OperationCreationQueue:

The next field we need is one I struggled a bit to wrap my head around. If this was an HTTP API, it would be obvious that the endpoints exposed are to be called by the client. But since our API works either by receiving or sending messages to client, we need a way to express how the clients will interact with the application. Will the clients receive messages through the SQS, or will they send them? Or, in other words, will the clients interact with the channel by publishing messages, or by subscribing to listen for new messages?

In our example, operations are created in our backend and then forwarded to the provider, who will be listening for new messages. Since we are documenting how others can interact with our API, the correct operation for this queue is subscribe

asyncapi: '2.4.0'
info:
  title: Best API in the world
    version: 0.1.0
channels:
  OperationCreationQueue:
    subscribe:

Once we have the basics, we can start documenting the operations as we see fit. For, instance, we can add a description, expected message schema, even protocol information if necessary. For instance, following our example:

asyncapi: '2.4.0'
info:
  title: Best API in the world
  version: 0.1.0
channels:
  OperationCreationQueue:
    subscribe:
      description: The application will send new operation notification messages through this channel.
	message:
	  payload:
	    type: object
	      properties:
		  operationId:
		    type: string
		    format: uuid
		    description: id of the operation
		  operationAmount:
		    type: number
		    description: Operation amount in Argentinian pesos.

The message properties are defined using JSON Schema.

Visualization and other tools

Just like we have Swagger UI for RESTful API docs visualization, we can paste our AsyncAPI file into the AsyncAPI Studio for pretty visualization, like this:

AsyncAPI Studio previsualization tool
AsyncAPI Studio previsualization tool

This tool will even generate basic examples for our messages, if we don’t provide our own.

Additional tools, such as code generation tools, parsers, other UI components and CI integrations can be found in this site.

There are many other features I have not explored in this article — I haven’t even tried most of them yet! — but this is enough to write a simple document. The question introduced in the beginning has, from now on, an answer which is easy to find!