Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: move operations to its own root object #806

Merged
merged 10 commits into from
Sep 26, 2022
126 changes: 80 additions & 46 deletions spec/asyncapi.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# ATTENTION: Work in progress

This version is not yet ready to be used. We're currently working on it. If you want to join the effort and participate in the development of the next major version of AsyncAPI, head over to [GitHub Issue that we use for tracking 3.0 development progress](https://github.com/asyncapi/spec/issues/691).

# AsyncAPI Specification

#### Disclaimer
Expand Down Expand Up @@ -58,6 +62,7 @@ It means that the [application](#definitionsApplication) allows [consumers](#def
- [Default Content Type](#defaultContentTypeString)
- [Channels Object](#channelsObject)
- [Channel Object](#channelObject)
- [Operations Object](#operationsObject)
- [Operation Object](#operationObject)
- [Operation Trait Object](#operationTraitObject)
- [Message Object](#messageObject)
Expand Down Expand Up @@ -168,6 +173,7 @@ Field Name | Type | Description
<a name="A2SServers"></a>servers | [Servers Object](#serversObject) | Provides connection details of servers.
<a name="A2SDefaultContentType"></a>defaultContentType | [Default Content Type](#defaultContentTypeString) | Default content type to use when encoding/decoding a message's payload.
<a name="A2SChannels"></a>channels | [Channels Object](#channelsObject) | The channels used by this [application](#definitionsApplication).
<a name="A2SOperations"></a>operations | [Operations Object](#operationsObject) | The operations this [application](#definitionsApplication) MUST implement.
<a name="A2SComponents"></a>components | [Components Object](#componentsObject) | An element to hold various reusable objects for the specification. Everything that is defined inside this object represents a resource that MAY or MAY NOT be used in the rest of the document and MAY or MAY NOT be used by the implemented [Application](#definitionsApplication).
<a name="A2STags"></a>tags | [Tags Object](#tagsObject) | A list of tags used by the specification with additional metadata. Each tag name in the list MUST be unique.
<a name="A2SExternalDocs"></a>externalDocs | [External Documentation Object](#externalDocumentationObject) | Additional external documentation.
Expand Down Expand Up @@ -703,37 +709,97 @@ userCompletedOrder:



#### <a name="operationsObject"></a>Operations Object

Holds a dictionary with all the [operations](#operationObject) this application MUST implement.

> If you're looking for a place to define operations that MAY or MAY NOT be implemented by the application, consider defining them in [`components/operations`](#componentsOperations).

##### Patterned Fields

Field Pattern | Type | Description
---|:---:|---
<a name="operationsObjectOperation"></a>{operationId} | [Operation Object](#channelObject) \| [Reference Object](#referenceObject) | The operation this application MUST implement. The field name (`operationId`) MUST be a string used to identify the operation in the document where it is defined, and its value is **case-sensitive**. Tools and libraries MAY use the `operationId` to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions.

##### Operations Object Example

```json
{
"onUserSignUp": {
"summary": "Action to sign a user up.",
"description": "A longer description",
"channel": {
Copy link
Member

@smoya smoya Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking that it might make sense to convert this into an array of channels (of references to channel) instead.
The use case that came to my mind was the fanout pattern, sending the same message to different channels.

For example, a user sign up action on a server might result in the server sending the same message to several queues on an EDA. Let's say those queues are owned by different departments, teams, or whatever.

WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, the fanout pattern is usually implemented by the broker, not the application. I mean, for instance, RabbitMQ has the fanout exchange that lets you connect various queues to a specific routingKey. Kafka, NATS, and others have consumer groups, with which you can implement the fanout pattern. The application sends a message to a particular channel and then it gets distributed to many other queues or consumers. Am I missing something?

Now, I see the value it can have by allowing us to group multiple operations into one. Batch operations if you want. We just need to be very clear that if a send operation is performed against 1 out of 3 channels and it fails, it shouldn't stop the code from sending the other 2. Aren't we getting into the land of workflow definition already? 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, the fanout pattern is usually implemented by the broker, not the application.

This statement doesn't reflect the reality out there. The fanout could be perfectly implemented by the application; writing the same message to different Kafka topics is a completely valid use case. For logistics, you might want to set up different retention policies, permissions, etc to each topic.

Also, we can still use an AsyncAPI document to describe a message broker (wich turns to be the application), where this pattern is way more common.
At the end, a client should know where to connect to and to which channels a particular message will be sent so it can subscribe to one of those (depending on each scenario).

Aren't we getting into the land of workflow definition already?

TBH I don't think we are that far. This is still referring to an application definition.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's my point. I wouldn't say it's to support fanout but to batch operations. That's a pretty valid and common use case.

@derberg @dalelane @char0n @magicmatatjahu what do you folks think?

Copy link
Member

@smoya smoya Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get why it's not to support the fanout pattern but batching/grouping operations. Messages are sent in parallel to different channels.

From Wikipedia:

In message-oriented middleware solutions, fan-out is a messaging pattern used to model an information exchange that implies the delivery (or spreading) of a message to one or multiple destinations possibly in parallel, and not halting the process that executes the messaging to wait for any response to that message.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only use case that comes to my mind right now is auditing. Some messages will be sent to their corresponding channel + the auditing channel. But I've never seen this done in the application code. It's always configured as a rule in the broker so you don't forget to audit anything.

Copy link
Member

@smoya smoya Sep 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use case I had in my mind is illustrated in the following pic grabbed from Salesforce blog. In particular, I'm talking about Point-to-Point architecture (even though the name is not correct IMHO) and not entering into which one is more scalable or less because it's not the main topic being discussed here.

I still believe this is part of the application definition and still not part of defining any message flow between apps.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but point to point is actually in contrast to EDA, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but point to point is actually in contrast to EDA, right?

The name on the picture is not correct as I mentioned in my previous comment. The right name I'm looking for is EventDriven Integration, and this article from Solace explains what it is: https://solace.com/blog/event-driven-architecture-difference-event-driven-integration

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smoya It looks like this requires its own discussion. Would you mind creating a follow-up issue? It would be great to find actual cases where this is required. Salesforce is great but I don't think it's a common use case.

"$ref": "#/channels/userSignup"
},
"action": "send",
"tags": [
{ "name": "user" },
{ "name": "signup" },
{ "name": "register" }
],
"bindings": {
"amqp": {
"ack": false
}
},
"traits": [
{ "$ref": "#/components/operationTraits/kafka" }
]
}
}
```

```yaml
onUserSignUp:
summary: Action to sign a user up.
description: A longer description
channel:
$ref: '#/channels/userSignup'
action: send
tags:
- name: user
- name: signup
- name: register
bindings:
amqp:
ack: false
traits:
- $ref: '#/components/operationTraits/kafka'
```



#### <a name="operationObject"></a>Operation Object

Describes a publish or a subscribe operation. This provides a place to document how and why messages are sent and received.
#### <a name="operationObject"></a>Operation Object

For example, an operation might describe a chat application use case where a user sends a text message to a group. A publish operation describes messages that are received by the chat application, whereas a subscribe operation describes messages that are sent by the chat application.
Describes a specific operation.

##### Fixed Fields

Field Name | Type | Description
---|:---:|---
<a name="operationObjectOperationId"></a>operationId | `string` | Unique string used to identify the operation. The id MUST be unique among all operations described in the API. The operationId value is **case-sensitive**. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions.
<a name="operationObjectAction"></a>action | `string` | **Required**. Allowed values are `send` and `receive`. Use `send` when it's expected that the application will send a message to the given [`channel`](#operationObjectChannel), and `receive` when the application should expect receiving messages from the given [`channel`](#operationObjectChannel).
<a name="operationObjectChannel"></a>channel | [Reference Object](#referenceObject) | **Required**. A `$ref` pointer to the definition of the channel in which this operation is performed. Please note the `channel` property value MUST be a [Reference Object](#referenceObject) and, therefore, MUST NOT contain a [Channel Object](#channelObject). However, it is RECOMMENDED that parsers (or other software) dereference this property for a better development experience.
<a name="operationObjectSummary"></a>summary | `string` | A short summary of what the operation is about.
<a name="operationObjectDescription"></a>description | `string` | A verbose explanation of the operation. [CommonMark syntax](http://spec.commonmark.org/) can be used for rich text representation.
<a name="operationObjectSecurity"></a>security | [[Security Requirement Object](#securityRequirementObject)]| A declaration of which security mechanisms are associated with this operation. Only one of the security requirement objects MUST be satisfied to authorize an operation. In cases where Server Security also applies, it MUST also be satisfied.
<a name="operationObjectTags"></a>tags | [Tags Object](#tagsObject) | A list of tags for API documentation control. Tags can be used for logical grouping of operations.
<a name="operationObjectExternalDocs"></a>externalDocs | [External Documentation Object](#externalDocumentationObject) | Additional external documentation for this operation.
<a name="operationObjectBindings"></a>bindings | [Operation Bindings Object](#operationBindingsObject) \| [Reference Object](#referenceObject) | A map where the keys describe the name of the protocol and the values describe protocol-specific definitions for the operation.
<a name="operationObjectTraits"></a>traits | [[Operation Trait Object](#operationTraitObject) &#124; [Reference Object](#referenceObject) ] | A list of traits to apply to the operation object. Traits MUST be merged into the operation object using the [JSON Merge Patch](https://tools.ietf.org/html/rfc7386) algorithm in the same order they are defined here.
<a name="operationObjectMessage"></a>message | [Message Object](#messageObject) &#124; [Reference Object](#referenceObject) &#124; Map["oneOf", [[Message Object](#messageObject) &#124; [Reference Object](#referenceObject)]] | A definition of the message that will be published or received by this operation. Map containing a single `oneOf` key is allowed here to specify multiple messages. However, **a message MUST be valid only against one of the message objects.**

This object MAY be extended with [Specification Extensions](#specificationExtensions).

##### Operation Object Example

```json
{
"operationId": "registerUser",
"summary": "Action to sign a user up.",
"description": "A longer description",
"channel": {
"$ref": "#/channels/userSignup"
},
"action": "send",
"security": [
{
"petstore_auth": [
Expand All @@ -747,28 +813,6 @@ This object MAY be extended with [Specification Extensions](#specificationExtens
{ "name": "signup" },
{ "name": "register" }
],
"message": {
"headers": {
"type": "object",
"properties": {
"applicationInstanceId": {
"description": "Unique identifier for a given instance of the publishing application",
"type": "string"
}
}
},
"payload": {
"type": "object",
"properties": {
"user": {
"$ref": "#/components/schemas/userCreate"
},
"signup": {
"$ref": "#/components/schemas/signup"
}
}
}
},
"bindings": {
"amqp": {
"ack": false
Expand All @@ -781,9 +825,11 @@ This object MAY be extended with [Specification Extensions](#specificationExtens
```

```yaml
operationId: registerUser
summary: Action to sign a user up.
description: A longer description
channel:
$ref: '#/channels/userSignup'
action: send
security:
- petstore_auth:
- write:pets
Expand All @@ -792,20 +838,6 @@ tags:
- name: user
- name: signup
- name: register
message:
headers:
type: object
properties:
applicationInstanceId:
description: Unique identifier for a given instance of the publishing application
type: string
payload:
type: object
properties:
user:
$ref: "#/components/schemas/userCreate"
signup:
$ref: "#/components/schemas/signup"
bindings:
amqp:
ack: false
Expand All @@ -818,15 +850,16 @@ traits:

#### <a name="operationTraitObject"></a>Operation Trait Object

Describes a trait that MAY be applied to an [Operation Object](#operationObject). This object MAY contain any property from the [Operation Object](#operationObject), except `message` and `traits`.
Describes a trait that MAY be applied to an [Operation Object](#operationObject). This object MAY contain any property from the [Operation Object](#operationObject), except the `message` and `traits` ones.

If you're looking to apply traits to a message, see the [Message Trait Object](#messageTraitObject).

##### Fixed Fields

Field Name | Type | Description
---|:---:|---
<a name="operationTraitObjectOperationId"></a>operationId | `string` | Unique string used to identify the operation. The id MUST be unique among all operations described in the API. The operationId value is **case-sensitive**. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions.
<a name="operationTraitObjectAction"></a>action | `string` | Allowed values are `send` and `receive`. Use `send` when it's expected that the application will send a message to the given [`channel`](#operationObjectChannel), and `receive` when the application should expect receiving messages from the given [`channel`](#operationObjectChannel).
<a name="operationTraitObjectChannel"></a>channel | [Reference Object](#referenceObject) | A `$ref` pointer to the definition of the channel in which this operation is performed. Please note the `channel` property value MUST be a [Reference Object](#referenceObject) and, therefore, MUST NOT contain a [Channel Object](#channelObject). However, it is RECOMMENDED that parsers (or other software) dereference this property for a better development experience.
<a name="operationTraitObjectSummary"></a>summary | `string` | A short summary of what the operation is about.
<a name="operationTraitObjectDescription"></a>description | `string` | A verbose explanation of the operation. [CommonMark syntax](https://spec.commonmark.org/) can be used for rich text representation.
<a name="operationTraitObjectSecurity"></a>security | [[Security Requirement Object](#securityRequirementObject)]| A declaration of which security mechanisms are associated with this operation. Only one of the security requirement objects MUST be satisfied to authorize an operation. In cases where Server Security also applies, it MUST also be satisfied.
Expand Down Expand Up @@ -1473,8 +1506,9 @@ Field Name | Type | Description
---|:---|---
<a name="componentsSchemas"></a> schemas | Map[`string`, [Schema Object](#schemaObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Schema Objects](#schemaObject).
<a name="componentsServers"></a> servers | Map[`string`, [Server Object](#serverObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Server Objects](#serverObject).
<a name="componentsServerVariables"></a> serverVariables | Map[`string`, [Server Variable Object](#serverVariableObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Server Variable Objects](#serverVariableObject).
<a name="componentsChannels"></a> channels | Map[`string`, [Channel Object](#channelObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Channel Objects](#channelObject).
<a name="componentsServerVariables"></a> serverVariables | Map[`string`, [Server Variable Object](#serverVariableObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Server Variable Objects](#serverVariableObject).
<a name="componentsOperations"></a> operations | Map[`string`, [Operation Item Object](#operationObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Operation Item Objects](#operationObject).
<a name="componentsMessages"></a> messages | Map[`string`, [Message Object](#messageObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Message Objects](#messageObject).
<a name="componentsSecuritySchemes"></a> securitySchemes| Map[`string`, [Security Scheme Object](#securitySchemeObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Security Scheme Objects](#securitySchemeObject).
<a name="componentsParameters"></a> parameters | Map[`string`, [Parameter Object](#parameterObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Parameter Objects](#parameterObject).
Expand Down