# CloudEvents + AsyncAPI

Author: [Mete Atamel](https://atamel.dev/) | Published: 2023-05-23 | Topics: AsyncAPI, CloudEvents, Event-driven, How To | Canonical: https://atamel.dev/posts/2023/05-23_asyncapi_cloudevents/


![CloudEvents and AsyncAPI](/img/2023/cloudevents_asyncapi.png)

I've been recently talking about [CloudEvents](https://cloudevents.io/) and
[AsyncAPI](https://www.asyncapi.com/),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](https://cloudevents.io/) 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](https://www.asyncapi.com/) 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 Basics](https://atamel.dev/posts/2023/04-03_cloudevents_basics/)
* [AsyncAPI Basics](https://atamel.dev/posts/2023/05-12_asyncapi_basics/)

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:

```sh
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:

```yaml
# 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](https://github.com/meteatamel/asyncapi-basics/blob/main/samples/account-service-cloudevents/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](/img/2023/asyncapistudio_ce_structured.png)

## CloudEvents binary-mode

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

```sh
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:

```yaml
# 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](https://github.com/meteatamel/asyncapi-basics/blob/main/samples/account-service-cloudevents/cloudevents-v1.0.1-asyncapi-traits.yaml)

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

```yaml
# 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](https://github.com/meteatamel/asyncapi-basics/blob/main/samples/account-service-cloudevents/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](/img/2023/asyncapistudio_ce_binary.png)

---

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:

* [AsyncAPI and CloudEvents](https://www.asyncapi.com/blog/asyncapi-cloud-events).
* [Simulating CloudEvents with AsyncAPI and
  Microcks](https://developers.redhat.com/articles/2021/06/02/simulating-cloudevents-asyncapi-and-microcks).

