In this article, we'll cover the tool named AsyncAPI for documenting your event-driven APIs in a detailed and creative way.
Companies widely use event-driven services in terms of performance. It's a publisher-consumer mechanism which allows services to communicate through message brokers such as RabbitMQ or Redis.
Real Case Scenario
We had to create a service that handles slug changes on specific entities such as products or categories. The standard REST API communication can't handle millions of requests and process data to update or create a new record in the database. It will also overload the server with tons of requests and the system will crash at a certain point.
As a solution, each time when a product or category changes its slug a new message (data) will be published in RabbitMQ and related consumers will get these messages from queues to process further.
We needed a tool to document technical details of internal structure to allow other teams to understand communication between services such as queue names, routing keys, message body and etc.
Documentation with AsyncAPI
We came up with a great documentation tool named AsyncAPI that allows easily creating of a detailed structure of your event-driven API.
The AsyncAPI Specification is a project used to describe and document message-driven APIs in a machine-readable format. It’s protocol-agnostic, so you can use it for APIs that work over any protocol (e.g., AMQP, MQTT, WebSockets, Kafka, STOMP, HTTP, Mercure, etc).
The usage information is very well provided in the official documentation of AsyncAPI. There are plenty of tools to generate documentation so you can also check from the official website.
The generation of the AsyncAPI document is based on the YAML file. You can play around with example documentation from AsyncAPI studio to understand the structure.
Implementation
Since we had a lot of microservices, each service needs to document its own APIs which should include the available servers based on environment and channel details.
Also, versions are changing frequently so it needs to be deployed in Gitlab pages continuously. In this case, you can use Gitlab pipelines to deploy the changes automatically when the branch is merged to the master.
You can use the HTML generator of AsyncAPI to render your YAML configuration. It will generate
All documentation files should be located inside docs/
directory. Then create another folder inside named source/
which will hold YAML files of AsyncAPI.
Feel free to create another directory inside source/
to separate entity-level topics such as we had to create another two directories for product/
and category/
.
Then create a new YAML file 0.0.1.yml
inside the directories that we created before to separate versions of API.
The file structure should look like the below:
.
└── docs
└── source
├── category
│ └── 0.0.1.yml
└── product
└── 0.0.1.yml
Copy the example AsyncAPI configuration below and paste it inside YAML files:
asyncapi: '2.4.0'
info:
title: Streetlights Kafka API
version: '1.0.0'
description: |
The Smartylighting Streetlights API allows you to remotely manage the city lights.
### Check out its awesome features:
* Turn a specific streetlight on/off 🌃
* Dim a specific streetlight 😎
* Receive real-time information about environmental lighting conditions 📈
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0
servers:
test:
url: test.mykafkacluster.org:8092
protocol: kafka-secure
description: Test broker
security:
- saslScram: []
defaultContentType: application/json
channels:
smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured:
description: The topic on which measured values may be produced and consumed.
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
publish:
summary: Inform about environmental lighting conditions of a particular streetlight.
operationId: receiveLightMeasurement
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/lightMeasured'
smartylighting.streetlights.1.0.action.{streetlightId}.turn.on:
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
subscribe:
operationId: turnOn
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/turnOnOff'
smartylighting.streetlights.1.0.action.{streetlightId}.turn.off:
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
subscribe:
operationId: turnOff
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/turnOnOff'
smartylighting.streetlights.1.0.action.{streetlightId}.dim:
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
subscribe:
operationId: dimLight
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/dimLight'
components:
messages:
lightMeasured:
name: lightMeasured
title: Light measured
summary: Inform about environmental lighting conditions of a particular streetlight.
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: "#/components/schemas/lightMeasuredPayload"
turnOnOff:
name: turnOnOff
title: Turn on/off
summary: Command a particular streetlight to turn the lights on or off.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: "#/components/schemas/turnOnOffPayload"
dimLight:
name: dimLight
title: Dim light
summary: Command a particular streetlight to dim the lights.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: "#/components/schemas/dimLightPayload"
schemas:
lightMeasuredPayload:
type: object
properties:
lumens:
type: integer
minimum: 0
description: Light intensity measured in lumens.
sentAt:
$ref: "#/components/schemas/sentAt"
turnOnOffPayload:
type: object
properties:
command:
type: string
enum:
- on
- off
description: Whether to turn on or off the light.
sentAt:
$ref: "#/components/schemas/sentAt"
dimLightPayload:
type: object
properties:
percentage:
type: integer
description: Percentage to which the light should be dimmed to.
minimum: 0
maximum: 100
sentAt:
$ref: "#/components/schemas/sentAt"
sentAt:
type: string
format: date-time
description: Date and time when the message was sent.
securitySchemes:
saslScram:
type: scramSha256
description: Provide your username and password for SASL/SCRAM authentication
parameters:
streetlightId:
description: The ID of the streetlight.
schema:
type: string
messageTraits:
commonHeaders:
headers:
type: object
properties:
my-app-header:
type: integer
minimum: 0
maximum: 100
operationTraits:
kafka:
bindings:
kafka:
clientId: my-app-id
Then, install the HTML generator module for AsyncAPI:
npm install -g @asyncapi/generator
Once installation is completed, create a new script file at the root level of your project. It will automatically detect YAML files and render them using the AsyncAPI HTML generator to create all required files.
generate_doc.sh
for f in $(find ./docs/source/ -name '*.yml');
do file=$(echo $f | sed 's/.yml//;s/docs//;s/source//'); ag --force-write $f @asyncapi/html-template -o ./docs/generated/$file/;
done
Run the script ./generate_doc.sh
and it will create another directory named generated/
with all static files of your rendered documentation.
Support 🌏
If you feel like you unlocked new skills, please share with your friends and stay connected :)