AsyncAPI gets a new version 3.0 and new operations


Almost one year ago, I talked about AsyncAPI 2.6 and how confusing its publish and subscribe operations can be in my Understanding AsyncAPI’s publish & subscribe semantics with an example post.

Since then, a new 3.0 version of AsyncAPI has been released with breaking changes and a totally new send and receive operations.

In this blog post, I want to revisit the example from last year and show how to rewrite it for AsyncAPI 3.0 with the new send and receive operations.

AsyncAPI 3.0

AsyncAPI 3.0 was released in December 2023. Since it’s a major version, it has some breaking changes. These two pages does a good job explaining the changes and the rationale behind them:

I won’t go through all the changes. For me, the biggest change is the separation of operations and channels, and changing publish and subscribe operations to send and receive.

AsyncAPI 2.6 vs 3.0

Recap: Publish and subscribe operations in AsyncAPI 2.6

As a recap, AsyncAPI 2.6 has the following publish and subscribe operations with these semantics:

AsyncAPI Term From Server Perspective From User Perspective
Publish Server receives the message User publishes messages to the server
Subscribe Server sends the message User subscribes for messages from the server

In 2.6, publish and subscribe operations are from user’s perspective.

New: Send and receive operations in AsyncAPI 3.0

In 3.0, the publish and subscribe operations are replaced with send and receive operations. In Migrating to v3 page, the rationale is given as follows:

In v2, the publish and subscribe operations consistently caused confusion, even among those familiar with the intricacies.

When you specified publish, it implied that others could publish to this channel since your application subscribed to it. Conversely, subscribe meant that others could subscribe because your application was the one publishing.

In v3, these operations have been entirely replaced with an action property that clearly indicates what your application does. That eliminates ambiguities related to other parties or differing perspectives.

While I agree that publish and subscribe were confusing, I’m not sure if send and receive are less confusing. You still need to talk about whose perspective when defining these operations. AsyncAPI docs talk about application but that’s not clear either. Does application refer to the code sending the message (user) or the code receiving the message (server)?

In Async 3.0, send and receive operations are from server’s perspective. An example will clarify.

Account and Email Services

Let’s revisit our example from last year. You have two microservices: Account Service emits an userSignedUp event and Email Service receives that event:

Account and Email services

How do you define such an architecture in AsyncAPI 2.6 vs 3.0?

Account Service

For Account Service, in 2.6, you had to define a channel with subscribe operation because the user has to subscribe to receive messages:

asyncapi: 2.6.0

channels:
  user/signedup:
    subscribe:
      operationId: publishUserSignedUp
      message:
        $ref: '#/components/messages/userSignedUp'

account-service-2.6.yaml

In 3.0, the channel does not have an operation anymore. Instead, there’s a new publishUserSignedUp operation with send action that refers to the channel. This is because the server sends the message:

asyncapi: 3.0.0

channels:
  user/signedup:
    address: user/signedup
    messages:
      publishUserSignedUp.message:
        $ref: '#/components/messages/userSignedUp'

operations:
  publishUserSignedUp:
    action: send
    channel:
      $ref: '#/channels/user~1signedup'
    messages:
      - $ref: '#/channels/user~1signedup/messages/publishUserSignedUp.message'

account-service-3.0.yaml

Email Service

Similarly, in Email Service, in 2.6, you had to define a publish operation because the user had to publish a message to the server:

asyncapi: 2.6.0

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

email-service-2.6.yaml

However, in 3.0, the server receives a message, so the operation has receive action:

asyncapi: 3.0.0

channels:
  user/signedup:
    address: user/signedup
    messages:
      receiveUserSignedUp.message:
        $ref: '#/components/messages/userSignedUp'

operations:
  receiveUserSignedUp:
    action: receive
    channel:
      $ref: '#/channels/user~1signedup'
    messages:
      - $ref: '#/channels/user~1signedup/messages/receiveUserSignedUp.message'

In this blog post, I explored a small part of AsyncAPI and explained the differences between 2.6 operations publish and subscribe and 3.0 operations send and receive. In a nutshell, in 2.6, you need to think from user’s perspective and in 3.0 in server’s perspective when you define these operations.

If you’re interested in learning more, I have a talk on CloudEvents and AsyncAPI and a repo with some samples:


See also