CloudEvents + AsyncAPI


CloudEvents and AsyncAPI

I’ve been recently talking about CloudEvents and AsyncAPI,two of my favorite open-source specifications for event-driven architectures. In this blog post, I want to talk about how you can use CloudEvents and AsyncAPI together. More specifically, I’ll show you how to document CloudEvents enabled services using AsyncAPI, thanks to the flexibility and openness of both projects.

Recap: CloudEvents and AsyncAPI

Let’s first do a quick recap CloudEvents and AsyncAPI.

CloudEvents is an open-source specification for describing event data in a common way with the goal of increasing interoperability between different event systems. It has two formats: structured-mode and binary-mode.

AsyncAPI is another open source specification to define asynchronous APIs, similar to what OpenAPI (aka Swagger) does for REST APIs.

You can read more about both in my previous blog posts:

CloudEvents help to define the format of the events and AsyncAPI helps to define the event-driven APIs. If your event-driven service sends or receives CloudEvents, then you can use AsyncAPI to document that service and its CloudEvent format.

CloudEvents structured-mode

Let’s first take a look at documenting event-driven services using CloudEvents in structured-mode. As a reminder, this is how a CloudEvent looks like in structured-mode where the CloudEvent metadata (such as type, source) and the actual data are all in the same JSON format:

curl localhost:8080 -v \
  -X POST \
  -H "Content-Type: application/cloudevents+json" \
  -d '{
        "specversion": "1.0",
        "type": "com.mycompany.myapp.myservice.myevent",
        "source": "myservice/mysource",
        "id": "1234-5678",
        "time": "2023-01-02T12:34:56.789Z",
        "subject": "my-important-subject",
        "datacontenttype": "application/json",
        "extensionattr1" : "value",
        "extensionattr2" : 5,
        "data": {
          "foo1": "bar1",
          "foo2": "bar2"
        }
      }'

How can you document a service receiving CloudEvents in this mode? The beauty of AsyncAPI is that it allows you to include external schemas and that’s exactly what we’ll do here. We’ll include the CloudEvents schema in AsyncAPI definition.

Here’s the AsyncAPI spec for a service accepting CloudEvents in structured-mode. Notice how you can refer to the CloudEvents spec in the payload. You can still define the data field of the CloudEvent as you want:

# Account Service in CloudEvents structured format based on this blog post:
# https://developers.redhat.com/articles/2021/06/02/simulating-cloudevents-asyncapi-and-microcks#
asyncapi: '2.6.0'
info:
  title: Account Service CloudEvents - structured
  version: 1.0.0
  description: Processes user sign ups and publishes an event afterwards

channels:
  user/signedup:
    publish:
      message:
        $ref: '#/components/messages/userSignedUp'

components:
  messages:
    userSignedUp:
      name: userSignedUp
      title: User signed up message
      summary: Emitted when a user signs up
      headers:
        content-type:
          type: string
          enum:
            - 'application/cloudevents+json; charset=UTF-8'
      payload:
        $ref: '#/components/schemas/userSignedUpPayload'

  schemas:
    userSignedUpPayload:
      type: object
      allOf:
        - $ref: 'https://raw.githubusercontent.com/cloudevents/spec/v1.0.1/spec.json'
      properties:
        data:
          $ref: '#/components/schemas/userSignedUpData'
    userSignedUpData:
      type: object
      properties:
        displayName:
          type: string
          description: Name of the user
        email:
          type: string
          format: email
          description: Email of the user

account-service-ce-structured.yaml

Take a look this in AsyncAPI Studio and you see that the service is nicely documented with an example CloudEvent structured-mode payload, nice!

AsyncAPI Studio CloudEvent structured

CloudEvents binary-mode

In binary-mode of CloudEvents, the CloudEvent metadata is sent as headers and the data is sent in the body:

curl localhost:8080 -v \
  -X POST \
  -H "Content-Type: application/json" \
  -H "ce-specversion: 1.0" \
  -H "ce-type: com.mycompany.myapp.myservice.myevent" \
  -H "ce-source: myservice/mysource" \
  -H "ce-id: 1234-5678" \
  -H "ce-time: 2023-01-02T12:34:56.789Z" \
  -H "ce-subject: my-important-subject" \
  -H "ce-extensionattr1: value" \
  -H "ce-extensionattr2: 5" \
  -d '{
        "foo1": "bar1",
        "foo2": "bar2"
      }'

To document a service accepting CloudEvents in binary-mode, first, you need to define the required and optional CloudEvents headers. AsyncAPI has a useful feature called traits where you can add additional properties to operations, messages, etc.

In this case, you will first define a message trait with CloudEvent headers:

# Modified from this sample:
# https://raw.githubusercontent.com/microcks/microcks-quickstarters/main/cloud/cloudevents/cloudevents-v1.0.1-asyncapi-trait.yml
name: cloudevents-headers
summary: Message headers used by CloudEvents spec in binary content mode
headers:
  type: object
  required:
    - ce-specversion
    - ce-id
    - ce-source
    - ce-type
  properties:
    ce-specversion:
      type: string
      description: The version of the CloudEvents specification which the event uses.
      enum:
        - "1.0"
    ce-id:
      type: string
      minLength: 1
      description: Identifies the event.
    ce-source:
      type: string
      format: uri-reference
      minLength: 1
      description: Identifies the context in which an event happened.
    ce-type:
      type: string
      minLength: 1
      description: Describes the type of event related to the originating occurrence.
    ce-datacontenttype:
      type: string
      description: Content type of the data value. Must adhere to RFC 2046 format.
    ce-dataschema:
      type: string
      description: Identifies the schema that data adheres to.
    ce-subject:
      type: string
      description: Describes the subject of the event in the context of the event producer (identified by source).
    ce-time:
      type: string
      format: date-time
      description: Timestamp of when the occurrence happened. Must adhere to RFC 3339.
    content-type:
      type: string
      enum:
        - application/json

cloudevents-v1.0.1-asyncapi-traits.yaml

Then, you can define your service with AsyncAPI referring to the message traits you defined earlier:

# Account Service in CloudEvents binary format based on this blog post:
# https://developers.redhat.com/articles/2021/06/02/simulating-cloudevents-asyncapi-and-microcks#
asyncapi: '2.6.0'
info:
  title: Account Service CloudEvents - binary
  version: 1.0.0
  description: Processes user sign ups and publishes an event afterwards

channels:
  user/signedup:
    publish:
      message:
        $ref: '#/components/messages/userSignedUp'

components:
  messages:
    userSignedUp:
      name: userSignedUp
      title: User signed up message
      summary: Emitted when a user signs up
      traits:
        - $ref: 'https://raw.githubusercontent.com/meteatamel/asyncapi-basics/main/samples/account-service-cloudevents/cloudevents-v1.0.1-asyncapi-traits.yaml'
      payload:
        $ref: '#/components/schemas/userSignedUpPayload'

  schemas:
    userSignedUpPayload:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/userSignedUpData'
    userSignedUpData:
      type: object
      properties:
        displayName:
          type: string
          description: Name of the user
        email:
          type: string
          format: email
          description: Email of the user

account-service-ce-binary.yaml

If you take a look this in AsyncAPI Studio again, you see that the service is nicely documented with an example CloudEvent binary-mode with headers and payload:

AsyncAPI Studio CloudEvent binary


In this blog post, I showed how to use two of my favorite open source specifications, CloudEvents and AsyncAPI, together. Thanks to these excellent blog posts that helped me in my understanding of AsyncAPI and CloudEvents:


See also