Closed damaru-inc closed 9 months ago
Welcome to AsyncAPI. Thanks a lot for reporting your first issue.
Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out this issue.
Hey Michael, it's an interesting and at least for me a confusing point too. I did indeed notice you had the switch in the paho-python and thought it was a good idea.
IMHO, The ability to define the point of view is a MUST. In my day to day job most of my conversations are around how a client can interact with a service, rather than the actual server definition so being available in the core spec and therefore consistent would be a great solution.
Thanks for your support. I put the same flag in the java-spring-cloud-stream template, and it was recently added to the java-spring-template with the name 'inverseOperations.'
Hey Michael, i'm agree with the idea, but as i explained i other issues\PR I would be very careful with client
and provider
words.
I had a worry, that when someone will read client
he could mix up it with client-server
paradigm. But for sure, in AsyncAPI we couldn't have terms like server
or client
in common understanding, since AsyncAPI could describe what application produces and what it consumes.
Let's consider following case, my AsyncAPI consist with only subscribe
operations. So, in your terms we will name application that only supply messages like provider
, and application that will actually creates messages like client
. A little bit confusing, from my point of view.
So, maybe counterpart
or partner
instead of provider
and source
instead of client
?
...And regarding view
, maybe perspective
? Just a though.
I agree the terms aren't perfect, those are the terms that our group in Solace came up with. I'll take this back to them for further discussion. Ultimately it's up to Fran to decide, should he agree to put this in the spec.
Hi Michael, I agree with the idea. Couple of questions w.r.t "provider" perspective:
+1 to this feature. What if we use Consumer / Provider instead of Client / Provider?
I would rather not have two versions of a spec based on a field (in this case, 'view' is being proposed to indicate which perspective you are viewing).
Has anyone considered a shift in the language for the spec from being "application-centered" to being channel-centered? In that way, the channel defines what it accepts and what it publishes, leaving the spec to describe the message structures and assoc bindings for generators to leverage based on if the generator is designed to support publishing, consuming, or both.
I would rather not have two versions of a spec based on a field (in this case, 'view' is being proposed to indicate which perspective you are viewing).
Has anyone considered a shift in the language for the spec from being "application-centered" to being channel-centered? In that way, the channel defines what it accepts and what it publishes, leaving the spec to describe the message structures and assoc bindings for generators to leverage based on if the generator is designed to support publishing, consuming, or both.
I understand where you are coming from and this is where I started as well. My fear was we needed something for the way the spec is written today (possibly a band-aid) and really think this through (in my opinion) for the next major release. But, to your point, if it was channel-centered, we could say a channel has a 1..* relationship with a schema and not use a verb (because to me, a channel is a pipe and is worthless unless someone put something into it, and someone pulls it out ;) ). I think then you could have "Application" specifications that reference channels and use verbs that say "what it does" (super helpful for internal enterprise ICD's and code gen) and "what you could do" which would be useful for B2B (external) use cases.... So my only point is that I like your proposal to have something channel centered, but could leverage that for "Application-centric" modeling as well. -- I will also in another reply, explain in better detail what we were thinking around view for the community to review as a near-term solution since we get tons of questions from our customers on this aspect/view (no pun intended).
I like the debate this is generating. In my opinion, this flaw of the spec raises deeper intrinsic problems that need to be solved in a solid way. Let me expose my point of view here:
I like this proposal for what it represents, however, we should take into account possible side-effects it can cause. This flag does not only change the meaning of the publish/subscribe verbs but the way protocol bindings are interpreted. For instance, HTTP, WebSockets, NATS, and other protocols supporting the concept of client and server or the concept of request and reply will be affected by this flag too. As an example, it's not the same to say that a publish
operation is a NATS request the application is performing than saying the publish
operation is a NATS reply. The same applies to HTTP.
Not going to say this is impossible to solve but definitely is not just about adding a flag on the info object. It has a lot of side-effects. And that's precisely what I would try to avoid in the spec at all costs: side-effects.
The spec is built with reusability in mind, even though it has its flaws on this area too. Let me explain this by example. Given the following AsyncAPI document:
asyncapi: 2.0.0
info:
title: My user management service
version: 1.0.0
channels:
user/registered:
description: In this channel, we get a message every time a user registers on our platform.
subscribe:
description: Subscribe to this channel to get a message every time a user signs up.
message: ...
If we add the view
flag, suddenly some of the things don't make sense anymore:
asyncapi: 2.0.0
info:
title: My user management service
version: 1.0.0
view: provider
channels:
user/registered:
description: In this channel, we get a message every time a user registers on our platform.
subscribe:
description: Subscribe to this channel to get a message every time a user signs up.
message: ...
The description message Subscribe to this channel to get a message every time a user signs up.
doesn't make sense now. It should be "Publish to this channel every time a user signs up" or "This application is subscribing to messages about user signups" because the point of view changed. It is an inconsistency, and might not be a big deal if you're the owner of the whole file but it gets worst if we extract the channel definition for others to use it:
asyncapi: 2.0.0
info:
title: My user management service
version: 1.0.0
view: provider
channels:
user/registered:
$ref: 'shared.yaml#/channels/userRegistered'
Suddenly, it's not that obvious. And it gets worse as more files depend on the shared.yaml#/channels/userRegistered
definition.
If I understood correctly, the purpose is to band-aid the spec to solve a common misconception regarding the publish
and subscribe
meanings. I agree it's utterly weird and should be addressed. That said, I don't think this flag is really providing any "aid" but instead, it's bringing more confusion to the table, as you saw in my examples above.
Don't get me wrong, I'm all for fixing it but the more I think about a solution the less likely it seems to me it's going to happen in a minor version. This deserves an elegant solution and we shouldn't be afraid of releasing version 3.0.0 soon.
I think @jhigginbotham pointed us in a great direction. Not only with the comment above but in this tweet about OpenAPI newest version (mate, you got me thinking).
The problem of channel reusability and this problem can actually be addressed by going channel-centered. However, we'd lose the value of an application-centered definition. So, why not do both? And why not making operations first-class citizens instead-of/as-well-as channels? Maybe this discussion is out of the scope of this issue. Or maybe not, that really depends on how much do we want to change.
Everything is better with examples, so here goes some food for thought. Please, don't treat it as a proposal. I just want everyone's mind to be as open as possible to solve this problem:
Application (asyncapi.yaml)
asyncapi: 3.0.0
kind: application # Optional. Defaults to "application". Can also be "library".
info:
title: Trains API
description: Real-time train updates
version: 1.0.0
operations:
publishTrainUpdate:
type: publish
channel:
$ref: 'library.yaml/channels/trainUpdates'
subscribeTrainUpdate:
type: clientSubscribe
channel:
$ref: 'library.yaml/channels/trainUpdates'
Library (library.yaml)
asyncapi: 3.0.0
kind: library
channels:
trainUpdates:
name: trains/updates # Not sure about "name". Maybe "path"? "address"?
message:
payload: ...
I'll elaborate on this idea as soon as I find some time but please don't hesitate to leave your thoughts and/or concerns here.
I think your last examples are what I was thinking in that what you have with a "Library" actually becomes sort of a catalog of available channels. In the kind "application" what does the verb represent? We know that in the kind "library" that you could publish or subscribe to any channel and that it would be access control rules that would limit what a client would actually be able to do (thus not described in the spec, just like openapi). Therefore the operations represent what the application does or the operations available for another application to perform? I think that brings us back to the crux of the discussion in that the Library is list of what channels are available and what you could do, but I would like the kind "Application" to be the operations performed by an application. Thoughts? I think this discussion is super interesting and good!
In the kind "application" what does the verb represent?
Yes, sorry, I should have clarified this point. In this example, publish/subscribe means what the application is doing (opposite as now). Just so we don't screw the other point of view that's sometimes necessary, we should add something like clientPublish
and clientSubscribe
. Then it means we'll end up having, at least, 4 verbs: publish
, subscribe
, clientPublish
, and clientSubscribe
.
@jhigginbotham and other people are questioning if we should have operation verbs at all. I think we should but let's keep our minds open and understand when is this information valuable and why.
Good explanation on the potential additional confusion for using a view band aid. Couple of points that occur to me.
First a question, what is the value of retaining publish, subscribe as is ? rather than having 4 verbs: applicationPublish, applicationSubscribe, clientPublish and clientSubscribe or something similar. Would continuing to use publish & subscribe still lead to confusion when you first pick up the spec?
And actually taking a step back, does going down the route of adding verbs run the risk of making it to verbose? i.e. if I understood the point made about NATS in the community call, would you potentially need more verbs to support its req/response via the same channel? or did I misunderstand the point being made.
I have a question: I was under the impression that one would define a yml file at the application level to document what the application does and allow for code generation at the application level. At the system level with many applications, the various yml files would point to a "library" of message definitions to support the publishing and consuming of the defined messages. My vote would be to keep current publish & subscribe verbs but document them as being from the application's perspective... aka messages application publishes and/or subscribes to. Great spec and very useful for document our current system. Many thanks to the group. Great work.
This issue has been automatically marked as stale because it has not had recent activity :sleeping: It will be closed in 30 days if no further activity occurs. To unstale this issue, add a comment with detailed explanation. Thank you for your contributions :heart:
@fmvilas
By separating channel from operation, it looks like message
is the same regardless of the operation type. The discussion above regarding NATS (and https://docs.nats.io/developing-with-nats/tutorials/reqreply) suggests that to support req/reply there is a definite need for the message
to vary per operation.
Ignoring NATS - what if the message
varies between publish
and consume
on the same channel? For example a chat application - your user ID is added by the server as a header or message field when you publish (but it's not part of the publish message payload).
My vote would be to keep current publish & subscribe verbs but document them as being from the application's perspective... aka messages application publishes and/or subscribes to.
@cappelaere I've been (incorrectly?) treating AsyncAPI as a way to describe the broker/event backbone - e.g "This channel contains weather data from IOT sensors - publish to it if you're a new device, subscribe to it if you want to process the data".
However, I think the existing paradigm as documented (https://www.asyncapi.com/docs/tutorials/streetlights) makes sense from an application view too - you're stating "to use this application publish new messages here, and if you want to see output you need to subscribe here".
Also, this means that you can continue (if acceptable) to use the spec to also describe the broker/event backbone - the inversion would be even more confusing - "this is a Kafka broker, any new messages will be published to the channel, but if you want to add a message you need to use the subscribe operation"
To me, while I think "subscribe to the published messages" makes sense, "produce to the subscribe endpoint" is harder to wrap your head round as a new user:
asyncApi: 2.0.0
channels:
newOrders:
subscribe:
summary: 'Send new orders here'
orderResult:
publish:
summary: 'Results are output here'
What use cases have you seen where the inverse is needed for applications?
Or - is the problem fundementally regarding code generation - that tools need to be able to understand the perspective (i.e generating mocks of applications, vs generating consumers)?
In my case, I have dozens of applications. Each one of them has a unique YML file describing what each application generates (publish) or ingest (subscribe). So the YML file is application centric. It allows for discovery of the API's (description of the services) and generation of code for applications. As a developer, this helps me keeping track of who does what and who needs what. I am the user of those YAML files :)
I, too, see an AsyncAPI document as formalizing the (message-based) contract of an application, and i think it is wise not to depart from this clear application-centric perspective almost accidentally, as a side-effect of clarifying the meaning of publish
and subscribe
. Maybe all that is needed is to replace publish
and subscribe
with more unequivocal terminology, maybe even emphasizing the application-centric nature of an AsyncAPI document, such as
subscribe
with applicationSent
publish
with applicationReceived
.(This could work well with a solution to #415 .)
Code generation has a lot to do with why I opened this issue. We usually don't use these files to generate message brokers, we use them to generate client applications. So if I were thinking about what I want the application to do, I'd hope that the app would publish messages in the publish operations.
I can understand why the spec was written this way, there have been numerous discussions, my understanding (and Fran will I'm sure correct me because I'm probably wrong) is that they were trying to adopt the same perspective that OpenAPI does, which is to say, when OpenAPI exposes a GET, and you're generating server code, it's not the server doing the GET.
But the thing is, in EDA there's no server doing server-side code, there are just applications. The brokers are just plumbing. So does the document say what one application is doing in relation to another application, one publishes and one subscribes, but we're looking at it from the other application's perspective? Or is it the broker's point of view?
Thank you for helping me understand the challenge of the different perspectives. I'm interested in the "socialising" side of AsyncAPI - the ability to describe "data sources" rather than "applications".
Example use case: I have a series of different types of sensors and I want to provide access externally to a combined stream of data.
From an (internal) application perspective - I would write a spec that says "this application publishes sensor data to channel x
with payload y
". And then application developers for each sensor type implements the applications with a common payload. In that case, the spec would be "provider" view.
But what I want to broadcast is the availability of the data source - "Channel x
contains a stream of data from ALL the different sensors" so that others can then write their own applications that subscribe to that data.
I could externally publish a "client" view application that describes the pseudo application - but my worry is that going application-centric means that this use case becomes more fiddly to articulate - the "it's not an application, but instead a collection of data from many applications".
I guess the bigger question is whether this is an acceptable use case for AsyncAPI at all?
My suggestion is to keep it simple. To my thinking, the subscribe:
attribute should relate to behavior that happens when a service/app/person subscribes to the given channel. In other words, it should receive messages off of the subscription. The 'publish:
attributes should relate to what happens when a service/app/person publishes a message into a channel. Right now I add suffixes to my operationId
values to reflect this intention like so:
channels:
wisesayingNeeded:
subscribe:
operationId: onWiseSayingNeededSub
message:
$ref: "#/components/messages/wiseSayingNeeded"
publish:
operationId: onWiseSayingNeededPub
message:
$ref: "#/components/messages/wiseSayingNeeded"
Presently, the current result is that after the code is generated I need to actually switch the generated functions calls around in order for incoming messages to be routed to the subscribe handler. In terms of the publish handler, I'm still not getting any behavior.
Here my case that's motivating me to ask for the simple approach I describe: https://github.com/asyncapi/nodejs-template/issues/44
@reselbob : what you describe is the current meaning of publish
and subscribe
: https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#channelItemObject
Thanks, @GeraldLoeffler . The problem for me is that when I auto-generate the code using the Node-template incoming messages from the subscription go to the function I associate with the operationId
in the publish
attribute. Thus, I have to twiddle with the generated code to make things line up. The impact for me is that I was planning to demonstrate the code autogeneration feature in an article I am writing. But, until this gets fixed, the article is on hold. Same for my demonstration project in Katacoda. If you need me to provide a video that demonstrates the problem, please let me know.
I have run into the confusion about publishing or subscribing with code generation and been thinking a lot about this lately, and I am ready to chime into the discussion. I am mostly working with broker architectures (Kafka, I've done some MQTT) but I think I also have enough of a handle on some of the other models (serverless applications, websockets) that are in use. I've also been bothered by the limitation that any asyncapi spec can only describe one component's point of view, even though event-driven systems are usually a bit of a spider's web when you see their architecture diagrams.
As I see it, payloads need to be decoupled from channels, and we're missing a piece of vocabulary that I'll call "actor". (It's missing because OpenAPI doesn't have it, there's only a client and a server, we don't need to introduce a load of different players. It also doesn't really have much of a concept of who can do what, it describes all the operations available in the system.)
Each "actor", so a client/application/whatever thing, then has payloads that it sends or receives to other actors. This covers the brokerless setups (such as websockets, which really will just be a client and a server and they are the inverse of one another but that's a special case IMO) or microservices, where the order service might receive payloads from another component and then send specific payloads to the stock and billing systems.
What this does not cover is the brokered setups, like Kafka or MQTT. To describe these systems, our actors need to be sending to or receiving from channels rather than other actors. Let's keep the channel descriptions but without the publish/subscribe stuff attached (this needs to be different per-actor), and without the payloads being attached. Then each actor has data associated with it that describes which payload it can send where - either to a channel, or to an actor.
What's either neat or really confusing about this is that now we can have one AsyncAPI specification that describes a whole system, and a full picture of which actors do what with which data. It removes the confusion about publish and subscribe (this is difficult with the brokers especially because "both" is always the answer) and means that things like generator tooling probably need to take an actor as a parameter so that they again only represent one point of view.
So to recap:
payloads describe the data packets, with whatever binding-specific context is needed, but they're independent.
channels the topics/channels and other broker-hosted pipes to put data into and out of but no other information like pub/sub or payload.
actors :sparkles: new addition :sparkles: represent the various system components (microservice, client, application), have their own metadata and then have a collection of items that combine:
This makes it clear which thing does what, in which direction, for as many components as we want to include. It does kind of add a third dimension to a two-dimensional existing idea but, well, this is my suggestion.
Our customers (Solace) continue to struggle on this topic, and I am seeing a proliferation of different ways people are using the spec in unintended ways. While on the surface this shows flexibility, the problem is that they end up with unforeseen consequences when it comes to tooling. I think many people in this thread have brought up reasonable approaches, but it feels like a working group should be formed to determine the best approach to move forward? How can we collectively drive this forward? Thank you @lornajane for your proposal!
This is the result of the discussion between @lornajane and me during the first episode of Thinking Out Loud.
publish
would mean the application is sending a message and subscribe
would mean the application is receiving a message.actors
.asyncapi: '3.0.0'
actors:
website-client:
description: This is the web application.
publish:
- user_signup_intent
website-server:
description: This is a WebSockets server.
subscribe:
- user_signup_intent
publish:
- user/signedup
email-service:
description: Sends welcome emails to users.
subscribe:
- user/signedup
channels:
user/signedup:
message:
$ref: '#/components/messages/UserSignedUp'
user_signup_intent:
message:
$ref: '#/components/messages/UserSignUpIntent'
components:
messages:
UserSignedUp:
description: This message is sent from the WebSockets server to the broker.
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user
UserSignUpIntent:
description: This message is sent from the browser to the WebSockets server.
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user
First off, I thoroughly enjoyed the thinking loud conversation between @lornajane and @fmvilas It was an honest and open dialogue.
I have had a lot of confusion understanding and trying to adopt the async api spec refer to my slack conversation https://asyncapi.slack.com/archives/CQVJXFNQL/p1625749552465500
I liked @lornajane's idea of a single file to define the full aysnc api architecture
My proposal for the spec is based on the fact that channel
is core communication mechanism that enables the actors publish
or subscribe
to message payloads.
I envision the spec as a list of channels
, which have a message payload schema described and can have actors that can publish or subscribe or do both (websockets use case).
My idea is a slight modification to Lorna's idea where I propose actors / roles
be tied to the specific channel.
I propose we introduce two attributes called publishers
and subscribers
that are arrays and are nested as part of the channel
attribute.
An example of an async api spec would be:
asyncapi: '3.0.0'
channels:
user/signedup:
message:
$ref: '#/components/messages/UserSignedUp'
publishers:
- website-client
subscribers:
- website-server
- email-server
order/placed:
message:
$ref: '#/components/messages/OrderPlaced'
publishers:
- website-server
subscribers:
- logistics
- shipping
- customer-service
components:
messages:
UserSignedUp:
description: This message is sent from the WebSockets server to the broker.
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user
OrderPlaced:
description: This message is sent from server when an order is placed.
payload:
type: object
properties:
orderId:
type: string
description: Name of the user
amount:
type: float
format: currency
description: Order amount
As a consumer of this spec when I look at a channel, I know the following a. Name of the channel b. Schema of the messages that are going to be published / subscribed on this channel c. Actors who are going to subscribe / publish on this channel
A spec like this can be used by to standup the required messaging infra structure. With infrastructure as code gaining popularity, I would like to generate my kafka brokers, sqs queues, sns topics, rabbitmq queues on the basis of this spec. Going a step further we could have a terraform tool that can generate the infrastructure as code based on the async api spec.
For a tool to generate the necessary code , the tool can be passed parameters like the channel name and actor name and based on this the tool can generate code based on the role played by the actor
Consider the above file as an example. website-server
is a consumer of the UserSignedUp
messages but is also the publisher of the OrderPlaced
messages.
Let's say we want to generate the code for website-server
, our tooling can take the asyncpi file and the actor name, in this case the website-server
as arguments.
<code-generator-tool> --file asycnapi.yaml --actor website-server
Since in the above file website-server
is listed as the subscriber
for user/signedup
channel, the tool will know to generate the subscriber
code.
Similarly since website-server
is listed as the publisher
for the order/placed
channel, the tool will know to generate the publisher
code.
The advantage is that using the actor
passed as an argument to the tool, one can generate all the necessary code for that particular actor
even though the actor
is a publisher
in certain cases and a subscriber
in certain cases
@fmvilas mentioned that sometimes it is not manageable to have a huge asyncapi file. We can get around that problem as well, by breaking down the spec file into the grain of a channel A channel, along with its message contract and list of subscribers and publishers array can be defined in a single file. So for an organization with a large number of channels would have multiple files and each application uses the file(s) it is interested in based on the channels.
It however does not solve the problem of where to locate these files. Again it is upto the team adopting the spec. They could create duplicates of it have copies of it on the subscribers and publishers code repo OR They could house it in a single repo and use it as a git submodule
The heart of the spec's aim in my mind is to define the channels
and the contract of the message payloads
being published on them.
The perspective of the actor
is more from a tooling standpoint of the spec.
With that in mind what if we removed the concept of actors
and just have the channels and message payloads.
From a tooling perspective when I pass it the asycn api spec , i need to pass in the channel
and the role
<code-generator-tool> --file asycnapi.yaml --channel user/signedup --role=publisher
From this the tooling now knows that it needs to generate a publisher
code for the channel user/signedup
A simplified spec would then look like
asyncapi: '3.0.0'
channels:
user/signedup:
message:
$ref: '#/components/messages/UserSignedUp'
order/placed:
message:
$ref: '#/components/messages/OrderPlaced'
components:
messages:
UserSignedUp:
description: This message is sent from the WebSockets server to the broker.
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user
OrderPlaced:
description: This message is sent from server when an order is placed.
payload:
type: object
properties:
orderId:
type: string
description: Name of the user
amount:
type: float
format: currency
description: Order amount
Again these are my initial thoughts, open to feedback
I think this is great progress. I agree that a purely logical Channel
concept, independent of the actors that use the Channel or how they use them is key to improving the spec.
To that end, I would argue:
This setup slightly reduces the ability to see "Actors who are going to subscribe / publish on this channel". You would instead have to walk the dependencies to see the actors. However, in a large enterprise, there are multiple files anyways, and that advantage is minimized.
publishers
and subscribers
are associated with a pub-sub model of interaction. Whereas there are many async interactions that are not pub-sub (point to point, queueing through brokers). I like @geraldloeffler suggestion (slightly modified by me) of changing this to actorSends
and actorReceives
. By genericized the terms to only signify who is putting data into the pipe and taking data out of the pipe, we can accommodate many more use cases without twisting the meaning of publish and subscribe.endpoint
, rather than actor
In v2.1 an AsyncAPI spec describes how other components may interact with the focal component. The focal component is assumed to implement the inverse of what is listed in the spec. In other words, if interacting components can subscribe from the focal component, the spec assumes that focal component publishes those messages. The 1:1 view of the world is a result of AsyncAPI’s efforts to strongly parallel OpenAPI. The single point of view doesn’t cause issues in OpenAPI because in a sync system, as there's only a client and a server, assuming the server is the inverse of the client is generally accurate.
However, event-driven systems are not exclusively (or even predominantly) 1:1 interaction. In fact, async architectures that use event brokers are typically a network of decoupled nodes interacting in complex ways to achieve desired outcomes. Here the assumption of inverse relationships fails, as components can be:
Endpoint
Both channel reuse and the perspective issues can be addressed by: 1) Removing the tight coupling between components and channels that is implicitly created by including “publish” and “subscribe” information in the channel declaration. 2) In its place, introduce a concept of an “endpoint” to AsyncAPI. Endpoint represents either a single component, or a generic stereotype (interface) of a class of components. The endpoint concept explicitly defines, from the perspective of the endpoint:
The resulting AsyncAPI structure looks like:
The introduction of “endpoint” and the resulting loose coupling addresses both the perspective and the channel reuse issue. Because endpoints are defined from their own perspective, using intuitive verbs, the new user experience is vastly improved and code generation becomes less problematic. And by decoupling endpoints from channels by using references, channels can be reused by multiple endpoints with different styles of interaction.
The current verbs in v2.1 (publish and subscribe) are tightly coupled to the pub-sub model of async interaction. Queues, mailboxes, websocket endpoints all can serve as asynchronous channels, but do not require either publishing or subscribing. This proposal
subscribe
verb with endpointSends
publish
verb with endpointReceives
This change further abstracts the “Channel” concept away from any implementation choice. The implementation details can in turn be specified as bindings, either attached to the channel or the endpoint-operation, depending if they apply to the channel, or to an individual endpoint’s use of the channel.
On a practical level, further abstraction of Channel allows point-to-point asynchronous communication, either brokered or unbrokered, to be represented by Channels. This in turn allows the spec to remain consistent across a wider range of interaction styles.
In the following examples, the endpoint, channel and message entities are presented as separate files. This is solely for the sake of consistency. The use of references allows for a wide range of implementation options including
Currently AsyncAPI defines how a component MAY interact with the focal component. This reflects the intention of the document to serve as a true API. For consistency, this convention is continued in this proposal.
That said, a separate and legitimate use of AsyncAPI is generating and documenting a particular endpoint’s implementation. OpenAPI (and REST) is tightly coupled to the HTTP/S protocol, so that verbs, URL, etc. can be assumed to be implemented using HTTP/S. In contrast, the asynchronous world uses a wide variety of protocols. Evidence of this is seen by provision of the specific binding information present in many AsyncAPI files and the growing number of code generation options. The need to explicitly provide implementation information in the asynchronous world forces a more explicit spec that allows for implementation details to be recorded in a systematic way. To accommodate this reality, this proposal:
To achieve decoupling, the proposed spec relies heavily on references. This (and the introduction of separate implementation and interface concepts) requires the spec to formalize the requirements with regards to inheritance when referencing another entity. In the case where Entity A references another entity B (endpoint, channel, message), Entity A MUST inherit all the properties of Entity B (and likewise all the Entities that have been previously referenced). Entity A MAY modify a property of Entity B (and previously referenced entities) ONLY IF ONE OR MORE OF THESE CONDITIONS ARE TRUE:
(Perhaps giving a little more context with our existing Streetlights example)
Currently the Streetlights AsyncAPI demo is an API for an event broker. In other words, it describes what operations all clients can conduct against an event broker. However, this seems like a difficult example: Event brokers are traditionally not considered applications, but rather one type of "pipe" connecting one or many endpoints. Other examples of channels would be mailboxes or async socket endpoints.
By moving to an endpoint model and abstracting channels it:
asyncapi: '3.0.0'
channels:
light/measured:
summary: Inform about environmental lighting conditions for a particular streetlight.
operationId: onLightMeasured
message:
$ref: 'URI/components/messages/LightMeasured'
endpoints:
StreetLight833SparrowAve:
id:StreetLight833SparrowAve
intent:implementation
endpointSends:
light/measured
$ref: 'URI/components/channels/light/measured'
binding:
mqtt:
endpointType:topic
qos:1
CentralControlHub:
id:CentralControlHub
intent:implementation
endpointReceives:
light/measured
$ref: 'URI/components/channels/light/measured'
binding:
mqtt:
endpointType:topic
qos:1
messages:
name: LightMeasured
payload:
type: object
properties:
...
We can then make the demo more realistic:
To accommodate these changes:
channels:
light/measured:
summary: Inform about environmental lighting conditions for a particular streetlight.
operationId: onLightMeasured
message:
$ref: 'URI/components/messages/LightMeasured'
control/power/{clientId}:
summary: Control a particular street light.
message:
$ref: 'URI/components/messages/PowerControl'
endpoints:
StreetLightClass:
id:StreetLightClass
intent:interface
endpointSends:
light/measured
required:true
$ref: 'URI/components/channels/light/measured'
binding:
mqtt:
endpointType:topic
qos:{qos}
endpointReceives:
control/power
$ref: 'URI/components/channels/control/power/{clientId}'
binding:
mqtt:
endpointType:topic
qos:1
endpoints:
StreetLight833SparrowAve:
id:StreetLight833SparrowAve
intent:implementation
endpointSends:
light/measured
$ref: 'URI/components/users/StreetLightClass'
binding:
mqtt:
endpointType:topic
qos:1
endpointReceives:
control/power
$ref: 'URI/components/channels/control/power/{clientId}'
binding:
mqtt:
endpointType:topic
qos:1
topic:{clientId}
endpoints:
StreetLight835SparrowAve:
id:StreetLight835SparrowAve
intent:implementation
endpointSends:
light/measured
$ref: 'URI/components/users/StreetLightClass'
binding:
mqtt:
endpointType:topic
qos:1
endpoints:
CentralControlHub:
id:CentralControlHub
intent:implementation
endpointReceives:
light/measured
$ref: 'URI/components/channels/light/measured'
binding:
mqtt:
endpointType:topic
qos:1
endpointSends:
control/power
$ref: 'URI/components/channels/control/power/{clientId}'
binding:
mqtt:
endpointType:topic
qos:1
I can't fault @lornajane 's, @fmvilas , and @jmenning-solace 's analyses, but TBH it makes me very uneasy to radically change perspectives and concepts of a spec that just recently reached version 2 - presumably a sign of maturity.
I also feel that AsyncAPI is actually very effective at describing the application-level view of messaging (some confusion about the meaning of "publish"/"subscribe" notwithstanding).
Maybe we could introduce a second, system-level spec that defers to individual AsyncAPI 2.x docs for the application-level view?:
$ref
s?) and be usable in its own right to characterize that application (just like now in AsyncAPI 2.x). It just doesn't give the full (holistic, system-wide) picture.That way we could rescue the application-level meaning and definition of AsyncAPI 2.x, yet still introduce that higher-level description that seems to be missing?
I think that the problem for me here is that we would want an AsyncAPI file per endpoint, not a single central file. In this context whilst both the producer and consumer know about the channel - neither knows about the other.
From a perspective of messaging practice, we don't want to couple the producer and consumer into using a single file. It should be possible to add a new consumer, by the act of describing the consumer, and the channel it consumes from.
From a perspective of microservices, this is important to 'independent deployability' we want Team A writing the producer and Team B writing the consumer to work independently of each other, including in having separate repos. This allows Team B to deploy a consumer, independently of Team A.
Now, I appreciate some folks might want to have producer and consumer in the same file, perhaps for a work queue, but this model, putting both in the same file won't work for us. At a deal breaking level.
For me should separate between:
As Tooling can look in files, produce a list of who produces to and consumes from a file, there is no need to explicitly state that. AsynAPI could agree a format for such a 'graph' and provide tooling to generate one for sure, but I don't think its how we should describe an endpoint. I would stick to the ABCs for that.
This applies to IaC too - we are explicitly using it in that context.
I'd also suggest that endpoint is a far better term than actor as it is less loaded:
https://www.enterpriseintegrationpatterns.com/patterns/messaging/MessageEndpoint.html
Although WCF is mostly dead, it was authored by folks like Clemens Vasters, who have credibility in this space and represents and earlier attempt at this. It's definition of an endpoint and ABCs remains useful prior art IMO: https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/endpoints-addresses-bindings-and-contracts
I think that the problem for me here is that we would want an AsyncAPI file per endpoint, not a single central file.
totally agreed - but that's the current situation, isn't it?: You define an AsyncAPI 2.x doc for an application, either for one of its endpoints, or some, or all. The granularity of AsyncAPI 2.x docs is up to you. But whatever the choice, the AsyncAPI 2.0 docs are for that application only.
totally agreed - but that's the current situation, isn't it?: You define an AsyncAPI 2.x doc for an application, either for one of its endpoints, or some, or all. The granularity of AsyncAPI 2.x docs is up to you. But whatever the choice, the AsyncAPI 2.0 docs are for that application only.
Agreed. During pre-2.0, I had proposed the following:
AsyncAPI was designed from the application perspective, which creates confusion with certain system topologies especially when they aren't client-broker. The specification tries to clarify this in multiple places, as it gets confusing when defining a channel but from an application-centric perspective. The choice so far has been to optimize for the tooling, rather than a description file format that centers on the channel. Thus, the answer so far has been to share the channel definitions across applications to better support the code generators. That seems to have worked so far, but that doesn't fit my desired model.
I'd prefer to have a file format for a channel-centric definition with bindings that are independent of system topology. The channel becomes the focal point and declares what messages it will accept and publish - independent of the system topology that makes it happen. A separate file may be created to reference this channel definition and declare a topology, which could be used by code generators and other tooling. I think we could keep most of the backward compatibility of we define these two separate file formats and allow teams to select which one or both they wish to use. In addition, there could be multiple files of each format that make up a much larger system, allowing for decomposition.
Unfortunately, I have no time to pull together a strawman of what this would look like. If someone else likes this idea, feel free to run with it and let's see what comes from it. Otherwise, we may need to remain application-centric for now as that seems to be the consensus barring any improvements to the existing specification to allow files to be channel-centric.
From a perspective of microservices, this is important to 'independent deployability' we want Team A writing the producer and Team B writing the consumer to work independently of each other, including in having separate repos. This allows Team B to deploy a consumer, independently of Team A.
As Tooling can look in files, produce a list of who produces to and consumes from a file, there is no need to explicitly state that. AsynAPI could agree a format for such a 'graph' and provide tooling to generate one for sure, but I don't think its how we should describe an endpoint. I would stick to the ABCs for that.
Is this then the flexibility from @lornajane original post https://github.com/asyncapi/spec/issues/390#issuecomment-843407765 - it provides:
An AsyncAPI document becomes actor centric - and tooling can combine documents to build the interconnected graph (which can also be described using the same document format).
Channels then purely define the "pipework" between actors and have little substance themselves.
My concern is that we don't want to lose sight of AsyncAPI for describing a consumable API - I should be able to read a spec - know what "channel" I need to connect to, and understand what "message" I should be sending/receiving to interact with an actor. Along with being able to document all existing actors so I can gain an understanding of the overall interactions in the system.
My concern is that we don't want to lose sight of AsyncAPI for describing a consumable API - I should be able to read a spec - know what "channel" I need to connect to, and understand what "message" I should be sending/receiving to interact with an actor. Along with being able to document all existing actors so I can gain an understanding of the overall interactions in the system.
agreed that it's important to not lose the "simple", application-centric view of AsyncAPI 2.x: after all, this captures the contract for a given message-based API as exposed by a given application. The system-wide view is important, and insightful, but on a higher level - and sometimes not pragmatically attainable or even desired: there still are siloed projects that are served best by the application-centric contract and don't care about the whole. Hence my proposal to compose the optional system-level doc by referencing individual AsyncAPI 2.x docs.
I'd also suggest that endpoint is a far better term than actor as it is less loaded: https://www.enterpriseintegrationpatterns.com/patterns/messaging/MessageEndpoint.html
@iancooper , great point. I updated actor -> endpoint
@iancooper , thanks for the comments, I owe you some on your bindings proposal.
I think that the problem for me here is that we would want an AsyncAPI file per endpoint, not a single central file. In this context whilst both the producer and consumer know about the channel - neither knows about the other.
This a very common request from our clients as well. In the example above, the endpoints StreetLight833SparrowAve
and CentralControlHub
would be housed in separate files, potentially in different repos. As noted:
In the following examples, the endpoint, channel and message entities are presented as separate files. This is solely for the sake of consistency. The use of references allows for a wide range of implementation options including
From a perspective of messaging practice, we don't want to couple the producer and consumer into using a single file. It should be possible to add a new consumer, by the act of describing the consumer, and the channel it consumes from.
Definitely. I think the example shows how that could be accomplished through refs. (As a side note, I think considering endpoints as either producers or consumers perhaps moves us too close to a synchronous, client-server view of the world. Most endpoints serve as both producers and consumers, and not necessarily in a 1:1 fashion where everything produced by an endpoint is consumed by an inverse endpoint.)
From a perspective of microservices, this is important to 'independent deployability' we want Team A writing the producer and Team B writing the consumer to work independently of each other, including in having separate repos. This allows Team B to deploy a consumer, independently of Team A.
Now, I appreciate some folks might want to have producer and consumer in the same file, perhaps for a work queue, but this model, putting both in the same file won't work for us. At a deal breaking level.
Instead of endpoints, in this proposal the location of the channel
becomes the challenge, since as a "pipe" there is necessarily a dependency that exists between both endpoints and the channel. If endpoint A is defined in repo A, and endpoint B is defined in repo B, then does the channel live in:
IMHO, that needs to be an implementation decision. But as seen by the channel reuse problem, the current spec forces people to adopt solution #4.
- A system-level doc would define shared resources like servers, channels, schemata, ..., the role of individual applications (actors), message-exchange patterns, etc.. This doc would be optional and only required if that holistic, system-wide view of how individual applications exchange messages is required. If present, this system-level doc must reference (re-use) one application-level (AsyncAPI 2.x) doc for each application in the system.
@GeraldLoeffler, this may be more than you want to attempt, but could you mock up an example of these files?
My concern is that because v2 combines channel and the application into a single entity, the reusability of "shared resources like ... channels" would not be possible at the proposed system level. Likewise, requiring references to v2 application docs (which are in turn required to include channel definitions) seems like it would prevent reuse of channels and leave the verb confusion.
@jmenning-solace Thanks for the detailed reply, the discussion is clarifying for me at least
Definitely. I think the example shows how that could be accomplished through refs. (As a side note, I think considering endpoints as either producers or consumers perhaps moves us too close to a synchronous, client-server view of the world. Most endpoints serve as both producers and consumers, and not necessarily in a 1:1 fashion where everything produced by an endpoint is consumed by an inverse endpoint.)
I guess it depends on whether you think that a process or service Foo has just one Endpoint or many, if it has different roles. An open question for us internally, and I suspect that we would treat those as separate files. In the first 'Thinking Out Loud' I believe this idea was raised as something definable in the Info object - what endpoint is this.
Instead of endpoints, in this proposal the location of the
channel
becomes the challenge, since as a "pipe" there is necessarily a dependency that exists between both endpoints and the channel. If endpoint A is defined in repo A, and endpoint B is defined in repo B, then does the channel live in:1. Repo A? 2. Repo B? 3. Some sort of enterprise-wide shared repo C? 4. Both repo A and repo B as a copy?
IMHO, that needs to be an implementation decision. But as seen by the channel reuse problem, the current spec forces people to adopt solution #4.
Yes, reuse of a channel definition is interesting as a problem because multiple endpoints use the channel definition. I don't dislike @lornajane's idea of explicitly surfacing endpoints (it's more using the name actors over endpoints that concerns me). I'd assume something like:
info:
servers:
channels:
endpoints:
components:
and then within endpoints, you would swallow up much of the operation object today, but moves it from under the channel. I'd presume we might want to define objects for send and receive (and keep publish/subscribe with existing semantics for migration or needs of WebSockets folks I don't fully grok). You still need bindings, payload, header et al here still of course. (I don't think you can put the message on the channel - not all channels are datatype channels that only support a single message type)
Within that you could just reference the channel by name - assuming you need some sort of unique naming scheme anyway, otherwise we need some sort of namespace.
So an endpoint (and you could have many) would look something like:
fooConsumer:
receive:
channel: Foo
description: Receives Foo
message:
$ref: "#/components/messages/fooDone"
binding:
name: queueName
delaySeconds: 0
maximumMessageSize: 262144
visibilityTimeout: 10
redrivePolicy:
maxReceiveCount: 30
dlq:
messageRetentionPeriod: 604800
delaySeconds: 0
And ultimately that seems to be the key observation from Lorna's talk - that we need to lift the 'operation' into a top level object, whether we call that endpoint or actor
So overall @jmenning-solace I think I am pretty aligned, as always 'details', but surfacing endpoints, which I think is the kernel of @lornajane's proposal does seem to fix issues for us
(I don't think you can put the message on the channel - not all channels are datatype channels that only support a single message type)
I agree - but we'd need to think about the https://www.asyncapi.com/docs/specifications/v2.0.0#channelItemObject - at this point, what is there to reuse - or does the channel essentially become metadata for tooling to build a "system wide" view (to collate endpoints by channel?)
Because originally I understood the re-use problem with channels was that the channel owned the message definition. If instead we have the endpoints
owning the definition of the messages they send/receive - has the reuse problem been solved? Or have we just created the same problem but pushed it onto tooling to solve?
I agree - but we'd need to think about the https://www.asyncapi.com/docs/specifications/v2.0.0#channelItemObject - at this point, what is there to reuse - or does the channel essentially become metadata for tooling to build a "system wide" view (to collate endpoints by channel?)
Well, pragmatically a channel may need bindings to help teams work with IaC or code generation. But yes, for messaging it is really a logical address that lets you route between senders and receivers.
Because originally I understood the re-use problem with channels was that the channel owned the message definition. If instead we have the
endpoints
owning the definition of the messages they send/receive - has the reuse problem been solved? Or have we just created the same problem but pushed it onto tooling to solve?
Well the message has always been re-usable by a $ref to another file or URI. The message itself then usually lives in "#/components/messages" over under the channel definition. I understood the problem as being:
I want to reuse the channel, but not the operations which live under the channel
The operations are 'backward' when used in a messaging context
Would I be simplifying the problem too much if I were to state that,
We have some pipe work via which applications communicate using a specified message structure ?
What if we simplified the spec to consist of channel definitions along with their message structures alone ?
It is upto the consumer of the spec to decide what sort of code they want to generate when interacting with a given channel. The consumer, before developing any code, would know if they are going to consume from a channel or send to a channel or perform both operations.
From spec standpoint if one can define a channel, its bindings and it message contract, wouldn't that suffice and make it simple.
The concept of an actor and the concept of the verb / action performed by the actor holds significance from a code generation / tooling standpoint.
The spec definitely should accommodate tooling concerns, but from how I look at it currently, it feels like the tooling concerns are defining how the spec should be defined.
If we look at OpenAPI spec as an example, it is defined as a list of paths and the messages on those paths. There is no reference to the consumer and the producer. The code generation tools provide options to generate code from a client or a server perspective.
What if we simplified the spec to consist of channel definitions along with their message structures alone ? If we look at OpenAPI spec as an example, it is defined as a list of paths and the messages on those paths. There is no reference to the consumer and the producer. The code generation tools provide options to generate code from a client or a server perspective.
This hits the same problem with channel reuse - "who" is in charge of writing that document? The pipe
owner? Anyone who interacts with the pipe
?
From my understanding the aim of this discussion is to provide an answer to the following questions:
In OpenAPI - you can answer 1 and 2 by looking at the spec, but not 3 because you only talk about servers and not clients. In AsyncAPI you can only answer 1,2 and 3 by writing a combined EDA document - which you can definitely do in AsyncAPI 2.0 - but needs the channel reuse and pub/sub to be solved for tooling to be able to combine documents to get that answer
@GeraldLoeffler, this may be more than you want to attempt, but could you mock up an example of these files?
you're right, sadly i don't have the bandwidth to do that, sorry.
My concern is that because v2 combines channel and the application into a single entity, the reusability of "shared resources like ... channels" would not be possible at the proposed system level. Likewise, requiring references to v2 application docs (which are in turn required to include channel definitions) seems like it would prevent reuse of channels and leave the verb confusion.
the "verb confusion" would remain, true: it can only be resolved by a backwards-incompatible renaming: trivial to do and trivial to automate "upgrade" tooling for AsyncAPI docs.
but it seems that all other restrictions of the v2 spec can be resolved by introducing fully backwards-compatible relaxations of what's required (say, channel
) and what can be referenced via an $ref
rather than having to be specified there-and-then by containment (similar to how #531 relaxes the association between channels and servers in a backwards-compatible way).
in general, AsyncAPI 2.1.0 still very much operates by associating objects through containment in the YAML (channels contain their operations which contain their messages), but this can backwards-compatibly be relaxed by making the contained objects optional, allowing them on the top level, and allowing $ref
s to reference them). This would then allow, say, channels and servers to be specified in a system-level doc, and operations in app-level docs, using $ref
s to refer to their channels, say. (But an AsyncAPI 2.x doc would still be valid as per this new spec version.)
visually, nested objects A
, B
, and C
:
A:
B:
C:
can be transformed to:
A:
B:
$ref: #/A
C:
$ref: #/B
or
A:
$ref: #/B
B:
$ref: #/C
C:
whatever is more convenient for decoupling the objects A
, B
, and C
.
Would I be simplifying the problem too much if I were to state that, We have some pipe work via which applications communicate using a specified message structure ?
It may not be your intent, but note that a channel is not necessarily constrained to a particular message schema. Yes, the DataType Channel pattern would constrain a channel to a single message type, but I don't think that AsyncAPI should constrain to just one type.
The key issue is that two endpoints communicating over a channel need to share a reference to that channel, and implementers wishing to support IaC will want to create a binding on the definition of that channel. And those endpoints may be in separate files.
Hence many of the suggestions introducing a new concept 'endpoint' (originally proposed as actor) to hold the operation being performed on the channel separately from the channel.
An endpoint might be able to be described as one of a different set of types that give metadata about the type of interaction, and we might possibly need to support both broker based contexts and point-to-point scenarios.
The specification regards publishing and subscribing from the point of view of what a client is allowed to do. For example, if the spec has a publish operation, then applications will actually be subscribing. This is consistent with OpenAPI server side code, where if one sees a GET, it is not the server that does the GET but rather other applications.
However some users of AsyncAPI find it more intuitive to consider things from the opposite perspective. If they have a document with a publish operation and want to generate code, they expect that the code will publish, not subscribe.
I propose adding a parameter called 'view' that can have two values:
'client' - this is consistent with the current specification. It means that the document says what a client of the application can do. 'provider' - this means that the document describes what the provider of the service is doing, i.e. a subscribe operation means that the application is subscribing.
Can't it be tackled using specification extensions? It can be done using specification extensions, but so far there are three generator templates using their own extensions and terminology - it would be nice to make this consistent.
Describe the solution you'd like I would like to see an addition to the info section that supports this parameter, e.g.
Describe alternatives you've considered The java-spring-cloud-stream and paho-python templates currently support this through the use of specification extentions and parameters. The java-template also has this feature but uses different terminology.