diff --git a/components/Asyncapi3Comparison.js b/components/Asyncapi3Comparison.js new file mode 100644 index 00000000000..c4e8800204b --- /dev/null +++ b/components/Asyncapi3Comparison.js @@ -0,0 +1,562 @@ +import React, { useState } from 'react'; + +/** + * Main comparison that shows the full picture between v2 and v3 + */ +export function Asyncapi3Comparison({ className = '' }) { + const [hoverState, setHoverState] = useState({ + Info: false, + Servers: false, + Paths: false, + PathItem: true, + Summary: false, + Operation: false, + Message: false, + Tags: false, + External: false, + Components: false, + Id: false, + Path: false, + Host: false + }); + + return ( +
+
+

AsyncAPI 2.x

+ +
+
setHoverState(prevState => ({ ...prevState, Info: true }))} onMouseLeave={() => setHoverState({ Info: false })}> + Info +
+
+
setHoverState(prevState => ({ ...prevState, Tags: true }))} onMouseLeave={() => setHoverState({ Tags: false })}> +

Tags

+
+
setHoverState(prevState => ({ ...prevState, External: true }))} onMouseLeave={() => setHoverState({ External: false })}> +

External Docs

+
+
+
+ Servers +
+
+ Server +
+
setHoverState(prevState => ({ ...prevState, Host: true, Path: true }))} onMouseLeave={() => setHoverState({ Host: false, Path: false })}> +

Url

+
+
+
+
+
+
setHoverState(prevState => ({ ...prevState, Paths: true }))} onMouseLeave={() => setHoverState({ Paths: false })}> + Channels + +
+
setHoverState(prevState => ({ ...prevState, PathItem: true }))} onMouseLeave={() => setHoverState({ PathItem: false })}> + Channel Item + +
+
setHoverState(prevState => ({ ...prevState, Operation: true }))} onMouseLeave={() => setHoverState({ Operation: false })}> + Operation (Publish and Subscribe) + +
+
+
setHoverState(prevState => ({ ...prevState, Message: true }))} onMouseLeave={() => setHoverState({ Message: false })}> + Messages +
+ Message + +
+ Headers +
+
+ Payload +
+
+
+
+
+
+
+
+
+
+
+
+
+

AsyncAPI 3.0

+ +
+
setHoverState(prevState => ({ ...prevState, Info: true }))} onMouseLeave={() => setHoverState({ Info: false })}> + Info +
+
setHoverState(prevState => ({ ...prevState, Tags: true }))} onMouseLeave={() => setHoverState({ Tags: false })}> +

Tags

+
+
setHoverState(prevState => ({ ...prevState, External: true }))} onMouseLeave={() => setHoverState({ External: false })}> +

External Docs

+
+
+
+
+ Servers +
+
+ Server +
+
setHoverState(prevState => ({ ...prevState, Host: true }))} onMouseLeave={() => setHoverState({ Host: false })}> +

Host

+
+
setHoverState(prevState => ({ ...prevState, Path: true }))} onMouseLeave={() => setHoverState({ Path: false })}> +

Pathname

+
+
+
+
+
+
setHoverState(prevState => ({ ...prevState, Paths: true }))} onMouseLeave={() => setHoverState({ Paths: false })}> + Channels + +
setHoverState(prevState => ({ ...prevState, PathItem: true }))} onMouseLeave={() => setHoverState({ PathItem: false })}> + Channel Item +
+
+ address +
+
+
setHoverState(prevState => ({ ...prevState, Message: true }))} onMouseLeave={() => setHoverState({ Message: false })}> + Messages +
+ Message + +
+ Headers +
+
+ Payload +
+
+
+
+
+
+
+
setHoverState(prevState => ({ ...prevState, Operation: true }))} onMouseLeave={() => setHoverState({ Operation: false })}> + Operations +
+
+ Operation + +
+
+ action (send or receive) +
+
+ channel +
+
+ messages +
+
+
+
+
+
+
+
+ ) +} + +/** + * Used to compare how channels, operations and messages have changed + */ +export function Asyncapi3ChannelComparison({ className = '' }) { + const [hoverState, setHoverState] = useState({ + Paths: false, + PathItem: false, + Operation: false, + Message: false, + }); + + return ( +
+
+

AsyncAPI 2.x

+ +
+
setHoverState(prevState => ({ ...prevState, Paths: true }))} onMouseLeave={() => setHoverState({ Paths: false })}> + Channels + +
+
setHoverState(prevState => ({ ...prevState, PathItem: true }))} onMouseLeave={() => setHoverState({ PathItem: false })}> + Channel Item + +
+
setHoverState(prevState => ({ ...prevState, Operation: true }))} onMouseLeave={() => setHoverState({ Operation: false })}> + Operation (Publish and Subscribe) + +
+
+
setHoverState(prevState => ({ ...prevState, Message: true }))} onMouseLeave={() => setHoverState({ Message: false })}> + Messages +
+ Message + +
+ Headers +
+
+ Payload +
+
+
+
+
+
+
+
+
+
+
+
+
+

AsyncAPI 3.0

+ +
+
setHoverState(prevState => ({ ...prevState, Paths: true }))} onMouseLeave={() => setHoverState({ Paths: false })}> + Channels + +
setHoverState(prevState => ({ ...prevState, PathItem: true }))} onMouseLeave={() => setHoverState({ PathItem: false })}> + Channel Item +
+
setHoverState(prevState => ({ ...prevState, Message: true }))} onMouseLeave={() => setHoverState({ Message: false })}> + Messages +
+ Message + +
+ Headers +
+
+ Payload +
+
+
+
+
+
+
setHoverState(prevState => ({ ...prevState, Operation: true }))} onMouseLeave={() => setHoverState({ Operation: false })}> + Operations +
+
+ Operation +
+
+ action (send or receive) +
+
+ channel +
+
+ messages +
+
+
+
+
+
+
+
+ ) +} + +/** + * Shows the comparison between v2 and v3 for the channel IDs and channel address + */ +export function Asyncapi3IdAndAddressComparison({ className = '' }) { + const [hoverState, setHoverState] = useState({ + Paths: false, + PathItem: false, + }); + + return ( +
+
+

AsyncAPI 2.x

+ +
+
setHoverState(prevState => ({ ...prevState, Paths: true }))} onMouseLeave={() => setHoverState({ Paths: false })}> + Channels +
setHoverState(prevState => ({ ...prevState, PathItem: true }))} onMouseLeave={() => setHoverState({ PathItem: false })}> + Channel Item +
+
+
+
+
+

AsyncAPI 3.0

+ +
+
setHoverState(prevState => ({ ...prevState, Paths: true }))} onMouseLeave={() => setHoverState({ Paths: false })}> + Channels + +
setHoverState(prevState => ({ ...prevState, PathItem: true }))} onMouseLeave={() => setHoverState({ PathItem: false })}> + Channel Item + +
+
+ address +
+
+
+
+
+
+
+ ) +} + +/** + * Compares how the server object changes from v2 to v3. + */ +export function Asyncapi3ServerComparison({ className = '' }) { + const [hoverState, setHoverState] = useState({ + Host: false, + path: false, + Servers: false, + }); + + return ( +
+
+

AsyncAPI 2.x

+ +
+
+ Servers +
+
+ Server +
+
setHoverState(prevState => ({ ...prevState, Host: true, Path: true }))} onMouseLeave={() => setHoverState({ Host: false, Path: false })}> +

Url

+
+
+
+
+
+
+
+
+

AsyncAPI 3.0

+ +
+
+ Servers +
+
+ Server +
+
setHoverState(prevState => ({ ...prevState, Host: true }))} onMouseLeave={() => setHoverState({ Host: false })}> +

Host

+
+
setHoverState(prevState => ({ ...prevState, Path: true }))} onMouseLeave={() => setHoverState({ Path: false })}> +

Pathname

+
+
+
+
+
+
+
+
+ ) +} + +/** + * Compare how the meta data moved place between v2 and v3 + */ +export function Asyncapi3MetaComparison({ className = '' }) { + const [hoverState, setHoverState] = useState({ + Info: false, + Tags: false, + External: false + }); + + return ( +
+
+

AsyncAPI 2.x

+ +
+
setHoverState(prevState => ({ ...prevState, Info: true }))} onMouseLeave={() => setHoverState({ Info: false })}> + Info +
+
+
setHoverState(prevState => ({ ...prevState, Tags: true }))} onMouseLeave={() => setHoverState({ Tags: false })}> +

Tags

+
+
setHoverState(prevState => ({ ...prevState, External: true }))} onMouseLeave={() => setHoverState({ External: false })}> +

External Docs

+
+
+
+
+
+

AsyncAPI 3.0

+ +
+
setHoverState(prevState => ({ ...prevState, Info: true }))} onMouseLeave={() => setHoverState({ Info: false })}> + Info +
+
setHoverState(prevState => ({ ...prevState, Tags: true }))} onMouseLeave={() => setHoverState({ Tags: false })}> +

Tags

+
+
setHoverState(prevState => ({ ...prevState, External: true }))} onMouseLeave={() => setHoverState({ External: false })}> +

External Docs

+
+
+
+
+
+
+ ) +} + +/** + * Compares how operations changed from v2 to v3 + */ +export function Asyncapi3OperationComparison({ className = '' }) { + return ( +
+
+

AsyncAPI 2.x

+ +
+
+ Channels + +
+
+ Channel Item + +
+
+ Operation (Publish and Subscribe) +
+
+
+
+
+
+
+
+

AsyncAPI 3.0

+ +
+
+ Operations +
+
+ Operation + +
+
+ action (send or receive) +
+
+
+
+
+
+
+
+ ) +} + +/** + * Compares how the schema and schemaFormat changed location from v2 to v3 + */ +export function Asyncapi3SchemaFormatComparison({ className = '' }) { + const [hoverState, setHoverState] = useState({ + SchemaFormat: false, + Payload: false + }); + + return ( +
+
+

AsyncAPI 2.x

+ +
+
+ components | channels + +
+
+ messages + +
+
+ message +
+
setHoverState(prevState => ({ ...prevState, SchemaFormat: true }))} onMouseLeave={() => setHoverState({ SchemaFormat: false })}> + schemaFormat +
+ +
setHoverState(prevState => ({ ...prevState, Payload: true }))} onMouseLeave={() => setHoverState({ Payload: false })}> + payload +
+
+ schema +
+
+
+
+
+
+
+
+
+
+
+
+

AsyncAPI 3.0

+ +
+
+ components | channels + +
+
+ messages + +
+
+ message +
+
setHoverState(prevState => ({ ...prevState, Payload: true }))} onMouseLeave={() => setHoverState({ Payload: false })}> + payload + +
+
setHoverState(prevState => ({ ...prevState, SchemaFormat: true }))} onMouseLeave={() => setHoverState({ SchemaFormat: false })}> + schemaFormat +
+
+ schema +
+
+
+
+
+
+
+
+
+
+
+
+ ) +} diff --git a/components/data/buckets.js b/components/data/buckets.js index a171d271885..8ecab700fa1 100644 --- a/components/data/buckets.js +++ b/components/data/buckets.js @@ -4,6 +4,7 @@ import IconUseCases from '../icons/UseCases' import IconGuide from '../icons/Guide' import IconSpec from '../icons/Spec' import IconUsers from '../icons/Users' +import IconMigration from '../icons/Migration' export const buckets = [ { @@ -51,7 +52,16 @@ export const buckets = [ borderClassName: 'border-yellow-200', Icon: IconSpec, }, - { + { + name: 'migration', + title: 'Migration', + description: 'Our migration guides on how to upgrade to newer AsyncAPI versions.', + link: '/docs/migration', + className: 'bg-blue-400', + borderClassName: 'border-blue-400', + Icon: IconMigration, + }, + { name: 'community', title: 'Community', description: 'Our Community section documents the community guidelines and resources.', @@ -61,7 +71,7 @@ export const buckets = [ Icon: IconUsers, }, ].map(bucket => { - // we need such a mapping for some parts of website, e.g navigation blocks use the `icon` property, not `Icon` etc. + // we need such a mapping for some parts of website, e.g navigation blocks use the `icon` property, not `Icon` etc. return { ...bucket, href: bucket.link, diff --git a/components/icons/Migration.js b/components/icons/Migration.js new file mode 100644 index 00000000000..77180efeba8 --- /dev/null +++ b/components/icons/Migration.js @@ -0,0 +1,15 @@ +export default function IconUsers({ ...rest }) { + // + return ( + + + + ); +} diff --git a/components/icons/Users.js b/components/icons/Users.js index c46a89deb77..9f43fdcb633 100644 --- a/components/icons/Users.js +++ b/components/icons/Users.js @@ -1,14 +1,14 @@ export default function IconUsers({ ...rest }) { return ( - diff --git a/components/navigation/learningItems.js b/components/navigation/learningItems.js index 65ba491f9a9..f04382b44ae 100644 --- a/components/navigation/learningItems.js +++ b/components/navigation/learningItems.js @@ -4,6 +4,7 @@ import IconPlant from '../icons/Plant' import IconGuide from '../icons/Guide' import IconPaper from '../icons/Paper' import IconUsers from '../icons/Users' +import IconMigration from '../icons/Migration' export default [ { href: '/docs/concepts', icon: IconRocket, className: 'bg-secondary-200', title: 'Concepts', description: 'Our Concepts section defines the concepts of AsyncAPI features and capabilities.' }, @@ -11,5 +12,6 @@ export default [ { href: '/docs/tools', icon: IconPlant, className: 'bg-green-200', title: 'Tools', description: 'Our Tools section documents the AsyncAPI tools ecosystem.' }, { href: '/docs/guides', icon: IconGuide, className: 'bg-primary-200', title: 'Guides', description: `Our Guides section teaches AsyncAPI's capabilities at a high level.` }, { href: '/docs/reference', icon: IconPaper, className: 'bg-yellow-200', title: 'Reference', description: `Our Reference section documents the AsyncAPI specification.` }, + { href: '/docs/migration', icon: IconMigration, className: 'bg-blue-400', title: 'Migrations', description: `Our migration guides on how to upgrade to newer AsyncAPI versions.` }, { href: '/docs/community', icon: IconUsers, className: 'bg-red-200', title: 'Community', description: `Our Community section documents the community guidelines and resources.` }, ] diff --git a/cypress/test/Asyncapi3Comparison.cy.js b/cypress/test/Asyncapi3Comparison.cy.js new file mode 100644 index 00000000000..15893bfd5f9 --- /dev/null +++ b/cypress/test/Asyncapi3Comparison.cy.js @@ -0,0 +1,40 @@ +import { mount } from '@cypress/react' +import {Asyncapi3Comparison, Asyncapi3ChannelComparison, Asyncapi3IdAndAddressComparison, Asyncapi3MetaComparison, Asyncapi3OperationComparison, Asyncapi3SchemaFormatComparison, Asyncapi3ServerComparison} from '../../components/Asyncapi3Comparison' + +describe('Asyncapi3Comparison.cy', () => { + describe('Asyncapi3Comparison', () => { + it('renders without errors', () => { + mount(); + }); + }); + describe('Asyncapi3ChannelComparison', () => { + it('renders without errors', () => { + mount(); + }); + }); + describe('Asyncapi3IdAndAddressComparison', () => { + it('renders without errors', () => { + mount(); + }); + }); + describe('Asyncapi3MetaComparison', () => { + it('renders without errors', () => { + mount(); + }); + }); + describe('Asyncapi3OperationComparison', () => { + it('renders without errors', () => { + mount(); + }); + }); + describe('Asyncapi3SchemaFormatComparison', () => { + it('renders without errors', () => { + mount(); + }); + }); + describe('Asyncapi3ServerComparison', () => { + it('renders without errors', () => { + mount(); + }); + }); +}); diff --git a/pages/docs/community/_section.md b/pages/docs/community/_section.md index 6874743a00f..1ef99a267b1 100644 --- a/pages/docs/community/_section.md +++ b/pages/docs/community/_section.md @@ -1,4 +1,4 @@ --- title: 'Community' -weight: 6 ---- \ No newline at end of file +weight: 7 +--- diff --git a/pages/docs/migration/_section.md b/pages/docs/migration/_section.md new file mode 100644 index 00000000000..48556fd333c --- /dev/null +++ b/pages/docs/migration/_section.md @@ -0,0 +1,4 @@ +--- +title: Migrations +weight: 6 +--- diff --git a/pages/docs/migration/index.md b/pages/docs/migration/index.md new file mode 100644 index 00000000000..195bcc35cf5 --- /dev/null +++ b/pages/docs/migration/index.md @@ -0,0 +1,15 @@ +--- +title: "Overview" +--- +Migration to a new major version is always difficult, and AsyncAPI is no exception, but we want to provide as smooth a transition as possible. + +If you are just looking to update your AsyncAPI document, then we suggest you use the [AsyncAPI converter](https://github.com/asyncapi/converter-js). You can do this directly in the CLI with: + +```bash +asyncapi convert asyncapi.json --output=new_asyncapi.json --target-version=x.x.x +``` + +For a detailed read-through about all the changes (non-breaking as well), please do [read the release notes](https://www.asyncapi.com/blog?tags=Release+Notes) for the desired version before hand, as it will give you some more context about the changes. + +Here are all the migration guides: +- [Migrating to v3](/docs/migration/migrating-to-v3) diff --git a/pages/docs/migration/migrating-to-v3.md b/pages/docs/migration/migrating-to-v3.md new file mode 100644 index 00000000000..9d20b8bbefc --- /dev/null +++ b/pages/docs/migration/migrating-to-v3.md @@ -0,0 +1,395 @@ +--- +title: "Migrating to v3" +--- +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](https://github.com/asyncapi/converter-js) directly in the CLI with the following command: + +```bash +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](/blog/release-notes-3.0.0) first to acquire additional context about the changes introduced in v3. + +import {Asyncapi3Comparison, Asyncapi3ChannelComparison, Asyncapi3IdAndAddressComparison, Asyncapi3MetaComparison, Asyncapi3OperationComparison,Asyncapi3SchemaFormatComparison, Asyncapi3ServerComparison} from '../../../components/Asyncapi3Comparison' + + + +## Moved metadata + +In v2, two properties of `tags` and `externalDocs` were placed outside of the [Info Object](https://www.asyncapi.com/docs/reference/specification/v3.0.0-next-major-spec.12#infoObject). For consistency, `info` has been moved in v3. + + + +```yml +asyncapi: 2.6.0 +info: + ... +externalDocs: + description: Find more info here + url: https://www.asyncapi.com +tags: + - name: e-commerce +``` + +```yml +asyncapi: 3.0.0 +info: + externalDocs: + description: Find more info here + url: https://www.asyncapi.com + tags: + - name: e-commerce +``` + +## Server URL splitting up +There was occasional confusion regarding what the URL of a [Server Object](https://www.asyncapi.com/docs/reference/specification/v3.0.0-next-major-spec.12#serverObject) should include. + + + +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. + +```yml +asyncapi: 2.6.0 +servers: + production: + url: "amqp://rabbitmq.in.mycompany.com:5672/production" + protocol: "amqp" +``` + +```yml +asyncapi: 3.0.0 +servers: + production: + host: "rabbitmq.in.mycompany.com:5672", + pathname: "/production", + 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. + + + +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. + +```yml +asyncapi: 2.6.0 +... +channels: + user/signedup: + publish: + message: + payload: + type: object + properties: + displayName: + type: string + description: Name of the user +``` + +```yml +asyncapi: 3.0.0 +... +channels: + UserSignup: + address: "user/signedup" + messages: + UserMessage: + payload: + type: object + properties: + displayName: + type: string + description: Name of the user +operations: + ConsumeUserSignups: + action: receive + channel: + $ref: "#/channels/UserSignup" +``` + +Read more about the confusion between publishing and subscribing in the [Operation keywords](#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](https://www.asyncapi.com/docs/reference/specification/v3.0.0-next-major-spec.12#channelObject). + + + +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. + +```yml +asyncapi: 2.6.0 +... +channels: + test/path: + ... +``` + +```yml +asyncapi: 3.0.0 +channels: + testPathChannel: + 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](https://www.asyncapi.com/docs/reference/specification/v3.0.0-next-major-spec.12#operationObject). + + + +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](https://github.com/asyncapi/spec/issues/618) +- Nic Townsend's blog post [Demystifying the Semantics of Publish and Subscribe](https://www.asyncapi.com/blog/publish-subscribe-semantics) + +Here is an example where the application both consumes and produces messages to the test channel: + +```yml +asyncapi: 2.6.0 +... +channels: + test/path: + subscribe: + ... + publish: + ... +``` + +```yml +asyncapi: 3.0.0 +channels: + testPathChannel: + address: "test/path" + ... +operations: + publishToTestPath: + action: send + channel: + $ref: "#/channels/testPathChannel" + consumeFromTestPath: + action: receive + channel: + $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](https://www.asyncapi.com/docs/reference/specification/v3.0.0-next-major-spec.12#messagesObject). 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. + +```yml +asyncapi: 2.6.0 +... +channels: + user/signedup: + message: + oneOf: + - ... + - ... + +asyncapi: 2.6.0 +... +channels: + user/signedup: + message: + ... +``` + +```yml +asyncapi: 3.0.0 +... +channels: + UserSignup: + address: user/signedup + messages: + UserMessage: + ... + UserMessage2: + ... + +asyncapi: 3.0.0 +... +channels: + UserSignup: + address: user/signedup + messages: + UserMessage: + ... +``` + +## 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](https://www.asyncapi.com/docs/reference/specification/v2.6.0#securitySchemeObject) 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](https://www.asyncapi.com/docs/reference/specification/v3.0.0-next-major-spec.12#serverObject) `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](https://www.asyncapi.com/docs/reference/specification/v3.0.0-next-major-spec.12#securitySchemeObject). + +```yml +asyncapi: 2.6.0 +servers: + production: + ... + security: + oauth_test: ["write:pets"] +... +channels: + test/path: + severs: + - production +components: + securitySchemes: + oauth_test: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/api/oauth/dialog + availableScopes: + write:pets: modify pets in your account + read:pets: read your pets + scopes: + - 'write:pets' +``` + +```yml +asyncapi: 3.0.0 +servers: + production: + ... + security: + - $ref: "#/components/securitySchemes/oauth_test" +... +channels: + test/path: + severs: + - $ref: "#/servers/production" +components: + securitySchemes: + oauth_test: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/api/oauth/dialog + availableScopes: + write:pets: modify pets in your account + read:pets: read your pets + scopes: + - "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: +```yml +messageId: userSignup +description: A longer description. +payload: + $ref: '#/components/schemas/userSignupPayload' +traits: + - summary: Action to sign a user up. + description: Description from trait. +``` + +In v2, after applying the traits, the complete message object appeared as follows. Note how the `description` was overridden: + +```yml +messageId: userSignup +summary: Action to sign a user up. +description: Description from trait. +payload: + $ref: '#/components/schemas/userSignupPayload' +``` +That is the default behavior of the [JSON Merge Patch](https://tools.ietf.org/html/rfc7386) 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: + +```yml +messageId: userSignup +summary: Action to sign a user up. +description: A longer description. # it's still description from "main" object +payload: + $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. + + + +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](https://www.asyncapi.com/docs/reference/specification/v3.0.0-next-major-spec.12#multiFormatSchemaObject) that consolidates this information. Consequently, whenever you utilize `schemaFormat`, you'll need to modify the schema as follows: + +```yml +asyncapi: 2.6.0 +... +channels: + user/signedup: + publish: + message: + schemaFormat: 'application/vnd.apache.avro;version=1.9.0' + payload: + type: record + name: User + namespace: com.company + doc: User information + fields: + - name: displayName + type: string +``` + +```yml +asyncapi: 3.0.0 +... +channels: + UserSignup: + address: user/signedup + messages: + userSignup: + payload: + schemaFormat: 'application/vnd.apache.avro;version=1.9.0' + schema: + type: record + name: User + namespace: com.company + doc: User information + fields: + - name: displayName + 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). + +```yml +asyncapi: 2.6.0 +... +channels: {} +``` + +```yml +asyncapi: 3.0.0 +... +```