-
Notifications
You must be signed in to change notification settings - Fork 60
feat: added support for AsyncAPI v3 #367
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
base: v1.1-dev
Are you sure you want to change the base?
Conversation
|
Let's have you join Nick, Mike and myself next week and the next few weeks for some follow up on how this works. We started meeting to discuss the best approach forward as well. As this would fundamentally alter the spec we'll definitely need a bit of time to go through what you have here and what we are looking in to.. see what might overlap, etc. Shoot me a msg on slack with your email so I can add you to our conversations there. |
|
Here is a simple example just for your reference. arazzo: "1.0.1"
info:
title: "Workflow for placing an Order"
version: "1.0.0"
sourceDescriptions:
- name: "OrderApi"
url: "./openapi/order.yaml"
type: "openapi"
- name: "AsyncOrderApi"
url: "./asyncapi/order.yaml"
type: "asyncapi"
workflows:
- workflowId: "PlaceOrder"
inputs:
required:
- "CreateOrder"
type: "object"
properties:
CreateOrder:
required:
- "orderRequestId"
- "productId"
- "quantity"
type: "object"
properties:
orderRequestId:
type: "string"
productId:
type: "integer"
quantity:
type: "integer"
steps:
- stepId: "CreateOrder"
operationId: "$sourceDescriptions.AsyncOrderApi.PlaceOrder"
stepType: "asyncapi"
action: "send"
parameters:
- name: "orderRequestId"
in: "header"
value: "$inputs.CreateOrder.orderRequestId"
requestBody:
payload:
productId: "$inputs.CreateOrder.productId"
quantity: "$inputs.CreateOrder.quantity"
outputs:
orderRequestId: "$message.header.orderRequestId"
- stepId: "ConfirmOrder"
operationId: "$sourceDescriptions.AsyncOrderApi.PlaceOrder"
stepType: "asyncapi"
action: "receive"
correlationId: "$steps.CreateOrder.outputs.orderRequestId"
timeout: 6000
outputs:
orderId: "$message.body.orderId"
- stepId: "GetOrderDetails"
operationId: "$sourceDescriptions.OrderApi.getOrder"
parameters:
- name: "orderId"
in: "path"
value: "$steps.ConfirmOrder.outputs.orderId"
successCriteria:
- condition: "$statusCode == 200"
components: {} |
|
As discussed, here is an example with fork and join arazzo: "1.0.1"
info:
title: "Workflow for placing an Order"
version: "1.0.0"
sourceDescriptions:
- name: "OrderApi"
url: "./openapi/order.yaml"
type: "openapi"
- name: "AsyncOrderApi"
url: "./asyncapi/order.yaml"
type: "asyncapi"
workflows:
- workflowId: "PlaceOrder"
inputs:
required:
- "CreateOrder"
type: "object"
properties:
CreateOrder:
required:
- "orderRequestId"
- "productId"
- "quantity"
type: "object"
properties:
orderRequestId:
type: "string"
productId:
type: "integer"
quantity:
type: "integer"
steps:
- stepType: "asyncapi"
stepId: "ConfirmOrder"
operationId: "$sourceDescriptions.AsyncOrderApi.PlaceOrder"
action: "receive"
correlationId: "$inputs.CreateOrder.orderRequestId"
fork: true # Converts ConfirmOrder to a Non Blocking Step
timeout: 6000
outputs:
orderId: "$message.body.orderId"
- stepType: "asyncapi"
stepId: "CreateOrder"
operationId: "$sourceDescriptions.AsyncOrderApi.PlaceOrder"
action: "send"
parameters:
- name: "orderRequestId"
in: "header"
value: "$inputs.CreateOrder.orderRequestId"
requestBody:
payload:
productId: "$inputs.CreateOrder.productId"
quantity: "$inputs.CreateOrder.quantity"
- stepId: "GetOrderDetails"
operationId: "$sourceDescriptions.OrderApi.getOrder"
join: # Waits for ConfirmOrder to complete or timeout
- ConfirmOrder # Can also be 'true' to join/wait all
parameters:
- name: "orderId"
in: "path"
value: "$steps.ConfirmOrder.outputs.orderId"
successCriteria:
- condition: "$statusCode == 200"
components: {} |
|
Thanks @nashjain - I will try to propose changes to the specification based on the examples later this week. There will no doubt be a few finer grained points that we'll need to discuss |
|
Taking the above examples into consideration here's a proposal outlining the specification changes to support AsyncAPI in v1.1.0. I've also provided an updated example which complies to the this proposal below. If we're in general agreement, I'll update this PR to reflect the proposal. Summary of the specification changes to add AsyncAPI support (not exhaustive)Source Description Object
|
|
Thanks @frankkilcommins The proposal mostly looks good to me. I just need sometime to think through a couple of items. I'm currently in Australia. Next week, once I'm back we could jump on a call to discuss and close it. |
|
@nashjain We discussed a bit about the removal of fork: true and kind: async implicitly indicates a fork, so removing that and then using dependsOn instead of join since we have dependsOn in the spec already elsewhere. Also the nature of a "fire and forget" (dont need response) vs "fire and wait for a response" which basically assumes if a dependsOn isn't indicating a given async kind that has a correlation id (or maybe thats not even needed) that it would indicate a fire/forget scenario. What do you think? If you can join the next meeting we can discuss on that call that would be great. |
|
Do we even need
But I also have a more general question/concern. I'm not clear about tooling support requirements. Provided AsyncAPI spec supports so many different protocols (websockets, kafka, mqtt, grpc, sns, sqs, etc, etc) would tooling be expected to support all of those? Or some subset? Or what? My concern is we may have almost no support from tooling as I don't even understand how "receive" can be implemented for some of those protocols. |
The proposal for
It's not set in stone at this point and as you mention the
Arazzo itself is agnostic to the specific protocols that can be modelled within AsyncAPI. Tooling implementations may choose to support a subset of protocols depending on use case or runtime environment. We can look to state a documented set of "Recommended" protocols for tooling advertising Arazzo AsyncAPI support. Off the top of my head (not final), something like:
To help perhaps tease out some of the further details, I've created a repo with some examples. Would be great for others to chime in and/or review/enrich the examples. See https://github.com/frankkilcommins/arazzo-examples for details. |
|
@frankkilcommins — +1 on |
I disagree. This requires more validation in fact and makes it worse. See example below. It just adds unnecessary duplication and also inconsistency:
Docs generators can display it nicely.
This makes sense in general but then those steps won't be linked to any source descriptions so let's consider it later when those extensibility is introduced
@mikeschinkel, any pros/cons you have in mind? |
|
@RomanHotsiy — My +1 came from being on the bi-weekly Arazzo call with @frankkilcommins and @kevinduffey where we discussed this and @frankkilcommins explained the rationale which I remember seemed logical and useful and where the three of us agreed, though I do not remember the specifics. I will let @frankkilcommins elaborate again, and/or maybe @kevinduffey can chime in. If you are available Wednesday Nov 26th 7pm EET you could join the next call if you like. |
|
@frankkilcommins @kevinduffey @mikeschinkel I've gone through the updates suggested by @frankkilcommins and it all makes sense to me. Good to go from my point of view. Once we agree on this, I can update the PR and also show a working demo of this spec with Specmatic in a couple of days. |
|
Thanks @nashjain Regarding To help evaluation, i've created another branch of the examples, which does not have the https://github.com/frankkilcommins/arazzo-examples/tree/without-kind/v1.1.0-prep/async-api-examples cc @RomanHotsiy |
|
I'm OK to drop
(I prefer calling it |
|
@frankkilcommins @kevinduffey @ndenny - as per our meeting on Nov 26th, I've updated the schema. Main changes:
|
Also cleaned up the spec to make it very clear that step-object can be oneOf openapi-step-object or asyncapi-step-object or workflow-step-object For AsyncAPI we really need support for timeout and dependOn. However, these are also useful for OpenAPI/Workflow steps so added it at the base step object. For OpenAPI we need at least one successCriteria but for AsyncAPI it can be optional.
…le AsyncAPI specification for Pet Purchase API
| | <a name="stepRequestBody"></a>requestBody | [Request Body Object](#request-body-object) | The request body to pass to an operation as referenced by `operationId` or `operationPath`. The `requestBody` is fully supported in HTTP methods where the HTTP 1.1 specification [RFC9110](https://tools.ietf.org/html/rfc9110#section-9.3) explicitly defines semantics for "content" like request bodies, such as within POST, PUT, and PATCH methods. For methods where the HTTP specification provides less clarity—such as GET, HEAD, and DELETE—the use of `requestBody` is permitted but does not have well-defined semantics. In these cases, its use SHOULD be avoided if possible. | | ||
| | <a name="stepSuccessCriteria"></a>successCriteria | [[Criterion Object](#criterion-object)] | A list of assertions to determine the success of the step. Each assertion is described using a [Criterion Object](#criterion-object). All assertions `MUST` be satisfied for the step to be deemed successful. | | ||
| | <a name="stepOnSuccess"></a>onSuccess | [[Success Action Object](#success-action-object) \| [Reusable Object](#reusable-object)] | An array of success action objects that specify what to do upon step success. If omitted, the next sequential step shall be executed as the default behavior. If multiple success actions have similar `criteria`, the first sequential action matching the criteria SHALL be the action executed. If a success action is already defined at the [Workflow](#workflow-object), the new definition will override it but can never remove it. If a Reusable Object is provided, it MUST link to a success action defined in the [components](#components-object) of the current Arazzo document. The list MUST NOT include duplicate success actions. | | ||
| | <a name="stepOnFailure"></a>onFailure | [[Failure Action Object](#failure-action-object) \| [Reusable Object](#reusable-object)] | An array of failure action objects that specify what to do upon step failure. If omitted, the default behavior is to break and return. If multiple failure actions have similar `criteria`, the first sequential action matching the criteria SHALL be the action executed. If a failure action is already defined at the [Workflow](#workflow-object), the new definition will override it but can never remove it. If a Reusable Object is provided, it MUST link to a failure action defined in the [components](#components-object) of the current Arazzo document. The list MUST NOT include duplicate failure actions. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if you already discussed how asyncApi steps should behave with retry on Failure actions.
Will it subscribe again, or do any other actions? How to handle potentially duplicated messages in queues?
Maybe I am missing smth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great question. I don't think we've have explicitly discussed this. Here is my thinking:
- If a
receiveasync step fails before an ack, retry would just try to fetch the message again from the topic/queue again. It is safe to retry. - If a
sendasync step fails, it could mean- the message was never sent (in which case it is safe to retry) OR
- message was sent, but we are not sure what happened. However since we recommend sending a correlationId, even if a duplicate message was sent, the consumer can ignore it
Most messaging systems focus on delivery and ordering guarantees, not uniqueness. It is expected that the consumer uses the correlationId to check for duplicates.
Do you think Arazzo should do something special in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nashjain what you state in your thinking seems reasonable to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@frankkilcommins / @nashjain
I am missing clear vision on how a mix of OpenAPI and AsyncAPI steps should work.
Specifically the time when outputs set even with the usage of dependsON.
Could you please help me understand future flow for tooling?
Lets imagine the structure
Workflow:
steps:
- step1 (Async)
- step2 (Sync) + dependsOn(step1) - to outputs to be set
- step3 (Async)
- step4 (Sync)
In current scenario:
- should we pause execution on step2, as it dependsOn step1?
- should step3 be executed after step2 or step1 ?
- what if step1 can receive not only one response ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DmitryAnansky here is a working example I demonstrated at the OAI Conference in Paris last week. Also attached it the sequence diagram for this flow. Please go through this example and let me know if this makes sense?
arazzo: "1.0.0"
info:
title: "Arazzo Workflow"
version: "1.0.0"
sourceDescriptions:
- name: "LocationApi"
url: "./openapi/location.yaml"
type: "openapi"
- name: "ProductApi"
url: "./openapi/product.yaml"
type: "openapi"
- name: "AsyncOrderApi"
url: "./asyncapi/order.yaml"
type: "asyncapi"
- name: "WarehouseApi"
url: "./openapi/warehouse.yaml"
type: "openapi"
- name: "OrderApi"
url: "./openapi/order.yaml"
type: "openapi"
workflows:
- workflowId: "PlaceOrder"
inputs:
required:
- "createOrderSend"
- "getUserLocation"
type: "object"
properties:
createOrderSend:
required:
- "requestId"
type: "object"
properties:
requestId:
type: "string"
getUserLocation:
required:
- "userEmail"
type: "object"
properties:
userEmail:
type: "string"
format: "email"
steps:
- stepId: "getUserLocation"
operationId: "$sourceDescriptions.LocationApi.getUserLocation"
parameters:
- name: "userEmail"
in: "query"
value: "$inputs.getUserLocation.userEmail"
successCriteria:
- condition: "$statusCode == 200"
outputs:
userId: "$response.body#/userId"
locationCode: "$response.body#/locationCode"
- stepId: "getProducts"
operationId: "$sourceDescriptions.ProductApi.getProducts"
parameters:
- name: "locationCode"
in: "query"
value: "$steps.getUserLocation.outputs.locationCode"
successCriteria:
- condition: "$statusCode == 200"
onSuccess:
- name: "IsArrayEmpty"
type: "end"
criteria:
- condition: "$response.body#/0 == null"
outputs:
productId: "$response.body#/0/productId"
inventory: "$response.body#/0/inventory"
- stepId: "createOrderSend"
operationId: "$sourceDescriptions.AsyncOrderApi.createOrder"
action: "send"
parameters:
- name: "requestId"
in: "header"
value: "$inputs.createOrderSend.requestId"
requestBody:
payload:
userId: "$steps.getUserLocation.outputs.userId"
productId: "$steps.getProducts.outputs.productId"
inventory: "$steps.getProducts.outputs.inventory"
outputs:
requestId: "$message.header.requestId"
- stepId: "createOrderReceive"
operationId: "$sourceDescriptions.AsyncOrderApi.createOrder"
action: "receive"
correlationId: "$steps.createOrderSend.outputs.requestId"
timeout: 6000
outputs:
orderId: "$message.payload.orderId"
- stepId: "reserveInventory"
operationId: "$sourceDescriptions.WarehouseApi.reserveInventory"
dependsOn:
- "$steps.createOrderReceive"
parameters:
- name: "orderId"
in: "query"
value: "$steps.createOrderReceive.outputs.orderId"
successCriteria:
- condition: "$statusCode == 200"
- stepId: "orderAccepted"
operationId: "$sourceDescriptions.AsyncOrderApi.orderAccepted"
action: "receive"
correlationId: "$steps.createOrderSend.outputs.requestId"
timeout: 6000
- stepId: "outForDelivery"
operationId: "$sourceDescriptions.AsyncOrderApi.outForDelivery"
action: "send"
dependsOn:
- "$steps.createOrderReceive"
parameters:
- name: "requestId"
in: "header"
value: "$steps.createOrderSend.outputs.requestId"
requestBody:
payload:
orderId: "$steps.createOrderReceive.outputs.orderId"
- stepId: "getOrderDetails"
operationId: "$sourceDescriptions.OrderApi.getOrderDetails"
dependsOn:
- "$steps.createOrderReceive"
parameters:
- name: "orderId"
in: "path"
value: "$steps.createOrderReceive.outputs.orderId"
successCriteria:
- condition: "$statusCode == 200"
components: {}… add pattern for dependsOn in schema.yaml to clarify that it can ref to a step from another workflow
frankkilcommins
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nashjain can you please update the PR to target v1.1-dev branch? Thanks
While we've tried to incorporate as much as possible from #270 not everything is covered.