Migration to a new major version is always difficult, and AsyncAPI is no exception. To provide as smooth a transition as possible, this document shows the breaking changes between AsyncAPI v2 and v3 in an interactive manner.
If you want to update your AsyncAPI document, use the AsyncAPI converter directly in the CLI with the following command:
asyncapi convert asyncapi.json --output=asyncapi_v3.json --target-version=3.0.0
For a detailed read-through about all the changes (non-breaking as well), read all the v3 release notes first to acquire additional context about the changes introduced in v3.
Moved metadata
In v2, two properties of tags
and externalDocs
were placed outside of the Info Object. For consistency, info
has been moved in v3.
AsyncAPI 2.x
Tags
External Docs
AsyncAPI 3.0
Tags
External Docs
1asyncapi: 2.6.0
2info:
3 ...
4externalDocs:
5 description: Find more info here
6 url: https://www.asyncapi.com
7tags:
8 - name: e-commerce
1asyncapi: 3.0.0
2info:
3 externalDocs:
4 description: Find more info here
5 url: https://www.asyncapi.com
6 tags:
7 - name: e-commerce
Server URL splitting up
There was occasional confusion regarding what the URL of a Server Object should include.
AsyncAPI 2.x
Url
AsyncAPI 3.0
Host
Pathname
In v2, the URL was often a lengthy string, sometimes redundantly including details like the protocol.
In v3, the url
property has been divided into host
, pathname
, and protocol
—as was the case in v2—making the information more explicit.
1asyncapi: 2.6.0
2servers:
3 production:
4 url: "amqp://rabbitmq.in.mycompany.com:5672/production"
5 protocol: "amqp"
1asyncapi: 3.0.0
2servers:
3 production:
4 host: "rabbitmq.in.mycompany.com:5672",
5 pathname: "/production",
6 protocol: "amqp",
Operation, channel, and message decoupling
The decoupling of operations, channels, and messages is the most significant breaking change in v3, fundamentally altering how they relate to each other.
AsyncAPI 2.x
AsyncAPI 3.0
In v2, reusing channels and having multiple operations per channel, such as operation variants, was impossible.
In v3, this has become possible, emphasizing that a channel and message should be independent of the operations performed.
For message brokers like Kafka, this is akin to defining topics and their associated messages. In REST interfaces, it pertains to the path and request type (e.g., POST, GET), along with the corresponding request and response messages. For WebSocket, it encompasses all messages transmitted through the WebSocket server. For Socket.IO, it delineates all the rooms and their messages.
Channels are now reusable across multiple AsyncAPI documents, each facilitating a slightly different action.
1asyncapi: 2.6.0
2...
3channels:
4 user/signedup:
5 publish:
6 message:
7 payload:
8 type: object
9 properties:
10 displayName:
11 type: string
12 description: Name of the user
1asyncapi: 3.0.0
2...
3channels:
4 UserSignup:
5 address: "user/signedup"
6 messages:
7 UserMessage:
8 payload:
9 type: object
10 properties:
11 displayName:
12 type: string
13 description: Name of the user
14operations:
15 ConsumeUserSignups:
16 action: receive
17 channel:
18 $ref: "#/channels/UserSignup"
Read more about the confusion between publishing and subscribing in the Operation keywords section.
Channel address and channel key
Another breaking change is that the channel key no longer represents the channel path. Instead, it's now an arbitrary unique ID. The channel paths are now defined using the address
property within the Channel Object.
AsyncAPI 2.x
AsyncAPI 3.0
In v2, the channel's address/topic/path
doubled as its ID, hindering reusability and preventing the definition of scenarios where the same address was used in different contexts.
In v3, the address/topic/path
has been shifted to an address
property, allowing the channel ID to be distinct and arbitrary.
1asyncapi: 2.6.0
2...
3channels:
4 test/path:
5 ...
1asyncapi: 3.0.0
2channels:
3 testPathChannel:
4 address: "test/path"
Operation keywords
Another significant change is the shift away from defining operations using publish
and subscribe
, which had inverse meanings for your application. Now, you directly specify your application's behavior using send
and receive
via the action
property in the Operation Object.
AsyncAPI 2.x
AsyncAPI 3.0
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.
Read more information about the confusion between publishing and subscribing:
- Fran Méndez's Proposal to solve publish/subscribe confusion
- Nic Townsend's blog post Demystifying the Semantics of Publish and Subscribe
Here is an example where the application both consumes and produces messages to the test channel:
1asyncapi: 2.6.0
2...
3channels:
4 test/path:
5 subscribe:
6 ...
7 publish:
8 ...
1asyncapi: 3.0.0
2channels:
3 testPathChannel:
4 address: "test/path"
5 ...
6operations:
7 publishToTestPath:
8 action: send
9 channel:
10 $ref: "#/channels/testPathChannel"
11 consumeFromTestPath:
12 action: receive
13 channel:
14 $ref: "#/channels/testPathChannel"
Messages instead of message
In v2, channels were defined with one or more messages using the oneOf
property.
In v3, messages are defined using the Messages Object. For a channel with multiple messages, you specify multiple key-value pairs. For a channel with just one message, you use a single key-value pair.
1asyncapi: 2.6.0
2...
3channels:
4 user/signedup:
5 message:
6 oneOf:
7 - messageId: UserMessage
8 ...
9 - messageId: UserMessage2
10 ...
11
12asyncapi: 2.6.0
13...
14channels:
15 user/signedup:
16 message:
17 messageId: UserMessage
18 ...
1asyncapi: 3.0.0
2...
3channels:
4 UserSignup:
5 address: user/signedup
6 messages:
7 UserMessage:
8 ...
9 UserMessage2:
10 ...
11
12asyncapi: 3.0.0
13...
14channels:
15 UserSignup:
16 address: user/signedup
17 messages:
18 UserMessage:
19 ...
We have updated the structure of the Message Object by eliminating the messageId
property. We now use the ID of the Message Object itself as the key in the key/value pairing, rendering a separate messageId
property redundant.
Unifying explicit and implicit references
In v2, implicit references were allowed in certain instances. For instance, the server security configuration was identified by name, linking to a Security Schema Object within the components. Similarly, a channel could reference global servers by name.
In v3, all such references MUST be explicit. As a result, we made a minor modification to the Server Object security
property, transforming it from an object to an array. The details regarding required scopes for OAuth and OpenID Connect were then relocated to the Security Scheme Object.
1asyncapi: 2.6.0
2servers:
3 production:
4 ...
5 security:
6 oauth_test: ["write:pets"]
7...
8channels:
9 test/path:
10 servers:
11 - production
12components:
13 securitySchemes:
14 oauth_test:
15 type: oauth2
16 flows:
17 implicit:
18 authorizationUrl: https://example.com/api/oauth/dialog
19 availableScopes:
20 write:pets: modify pets in your account
21 read:pets: read your pets
22 scopes:
23 - 'write:pets'
1asyncapi: 3.0.0
2servers:
3 production:
4 ...
5 security:
6 - $ref: "#/components/securitySchemes/oauth_test"
7...
8channels:
9 test/path:
10 servers:
11 - $ref: "#/servers/production"
12components:
13 securitySchemes:
14 oauth_test:
15 type: oauth2
16 flows:
17 implicit:
18 authorizationUrl: https://example.com/api/oauth/dialog
19 availableScopes:
20 write:pets: modify pets in your account
21 read:pets: read your pets
22 scopes:
23 - "write:pets"
New trait behavior
In v2, traits invariably overwrote any duplicate properties specified both in the traits and the corresponding object. For instance, if both message traits and the message object defined headers, only the headers from the message traits would be recognized, effectively overriding those in the Message Object.
In v3, this behavior has been revised. The primary objects now take precedence over any definitions in the traits. Such an adjustment is consistent for traits in both operation and message objects.
Here is a message object and associated traits:
1messageId: userSignup
2description: A longer description.
3payload:
4 $ref: '#/components/schemas/userSignupPayload'
5traits:
6 - summary: Action to sign a user up.
7 description: Description from trait.
In v2, after applying the traits, the complete message object appeared as follows. Note how the description
was overridden:
1messageId: userSignup
2summary: Action to sign a user up.
3description: Description from trait.
4payload:
5 $ref: '#/components/schemas/userSignupPayload'
That is the default behavior of the JSON Merge Patch algorithm we use.
In v3, we've instituted a guideline stating, A property on a trait MUST NOT override the same property on the target object
. Consequently, after applying the traits in v3, the complete message object appears as follows:
1messageId: userSignup
2summary: Action to sign a user up.
3description: A longer description. # it's still description from "main" object
4payload:
5 $ref: '#/components/schemas/userSignupPayload'
Notice how the description
is no longer overwritten.
Schema format and schemas
One limitation with schemas has always been the inability to reuse them across different schema formats.
AsyncAPI 2.x
AsyncAPI 3.0
In v2, the details about which schema format the payload uses are found within the message object, rather than being directly linked to the schema itself. Such separation hampers reusability, as the two data points aren't directly correlated.
To address this in v3, we've introduced a multi-format schema object that consolidates this information. Consequently, whenever you utilize schemaFormat
, you'll need to modify the schema as follows:
1asyncapi: 2.6.0
2...
3channels:
4 user/signedup:
5 publish:
6 message:
7 schemaFormat: 'application/vnd.apache.avro;version=1.9.0'
8 payload:
9 type: record
10 name: User
11 namespace: com.company
12 doc: User information
13 fields:
14 - name: displayName
15 type: string
1asyncapi: 3.0.0
2...
3channels:
4 UserSignup:
5 address: user/signedup
6 messages:
7 userSignup:
8 payload:
9 schemaFormat: 'application/vnd.apache.avro;version=1.9.0'
10 schema:
11 type: record
12 name: User
13 namespace: com.company
14 doc: User information
15 fields:
16 - name: displayName
17 type: string
Optional channels
In v3, defining channels has become entirely optional, eliminating the need to specify channels as an empty object (required in v2).
1asyncapi: 2.6.0
2...
3channels: {}
1asyncapi: 3.0.0
2...
Restricted parameters object
Parameters have often prioritized convenience over accurately reflecting real-world use cases.
AsyncAPI 2.x
AsyncAPI 3.0
In v2, we significantly streamlined the Schema Object. While the previous version offered full capability with numerous, often underutilized options, it posed challenges in serializing objects or booleans in the channel path.
The new v3 simplifies this by consistently using the string type and limiting available properties. Now, you can only access enum
, default
, description
, examples
, and location
, ensuring a more focused and practical approach.
1asyncapi: 2.6.0
2...
3channels:
4 user/{user_id}/signedup:
5 parameters:
6 location: "$message.payload"
7 description: Just a test description
8 schema:
9 type: string
10 enum: ["test"]
11 default: "test"
12 examples: ["test"]
13 ...
1asyncapi: 3.0.0
2...
3channels:
4 userSignedUp:
5 address: user/{user_id}/signedup
6 parameters:
7 user_id:
8 enum: ["test"]
9 default: "test"
10 description: Just a test description
11 examples: ["test"]
12 location: "$message.payload"