Documentation for your Event-Driven API

Documentation for your Event-Driven API
Documentation for your Event-Driven API

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 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 subscribe to the youtube channel to not miss any valuable information.

Thumbnail Reference - Document icons created by Freepik - Flaticon