spec
spec copied to clipboard
Allow grouping servers
Context
Current Server Object is identified by the key on servers map. E.g.:
servers:
production: # 'production' is the name of the server.
url: localhost:8080
In the previous example, the word production identifies the server. We could also call it by it's server machine name or service name, e.g. yavin04.yavin.systems.
No matters what name do we use for the server as far as are unique in the servers map.
One common pattern (See https://github.com/asyncapi/spec/issues/628) is to declare reusable servers in common AsyncAPI files, then reference them in the specific AsyncAPI files. E.g.
# common.asyncapi.yaml
components:
servers:
yavin04.yavin.systems:
url: https://yavin04.yavin.systems:8080
protocol: http
# app.asyncapi.yaml
servers:
production:
$ref: 'common.asyncapi.yaml#/components/servers/yavin04.yavin.systems'
What if a server exposes multiple ports with different protocols? Current Spec forces to create a different Server Object for this matter, since only one protocol is allowed per server. E.g.:
# common.asyncapi.yaml
components:
servers:
yavin04.yavin.systems-http:
url: https://yavin04.yavin.systems:8080
protocol: http
yavin04.yavin.systems-websocket:
url: https://yavin04.yavin.systems:5000
protocol: ws
# app.asyncapi.yaml
servers:
productionHTTP:
$ref: 'common.asyncapi.yaml#/components/servers/yavin04.yavin.systems-http'
productionWebsocket:
$ref: 'common.asyncapi.yaml#/components/servers/yavin04.yavin.systems-websocket'
Just as side note, I wouldn't say no to evaluating the idea of allowing several protocols to be declared (linked to ports). But this is for another issue :)
The problem
How can a user answer the question: "How can I identify the list of real/physical servers or service names in my system?", where system refers to the infrastructure around this or all my applications.
Also from the point of view of code generators, the same answer could be formulated. For example. "Generate all servers from service name yavin04.yavin.systems". This will end up generating one for http protocol, and another one for ws.
Another example is for tooling like the Event-Gateway, where several servers relate to the same service. See this.
Possible solutions
Solution 1: Use the url field
By using the url field, we could group all described servers. Of course getting rid of any variable on it such as the port, etc.
This seems a quick and easy solution, but it has some important drawbacks; the most important to me is that URLs can differ from one protocol to another. Not only the port but also the DSN on it. E.g.:
# common.asyncapi.yaml
components:
servers:
yavin04.yavin.systems-http:
url: https://api.yavin.systems:8080
protocol: http
yavin04.yavin.systems-websocket:
url: https://ws.yavin.systems:5000
protocol: ws
Having reached this point, there is no way to identify the real server behind, unless we play with some particular server name format. More in next bullet.
Solution 2: Use a custom name format
The user could set a known format for the server name, e.g. {servername}-{protocol}-{port}, which will look like yavin04.yavin.systems-http-8080 and yavin04.yavin.systems-ws-5000. This way, we could identify the physic/real server behind by extracting it from the name, in this case {servername} section.
Even though I consider this a really good practice that should be followed anyway, there is a clear drawback: not supported by tooling. As it is no a standard in the spec, tooling won't care that much about the name following a format. Grouping Server Objects by real/physic servers when for example generating documentation won't be a thing.
Solution 3: Add a new field on Server Object.
Supporting a new optional field on Server Object, such as service, serviceName or just group. E.g.:
# common.asyncapi.yaml
components:
servers:
yavin04.yavin.systems-http:
url: https://api.yavin.systems:8080
group: yavin04.yavin.systems
protocol: http
yavin04.yavin.systems-websocket:
url: https://ws.yavin.systems:5000
group: yavin04.yavin.systems
protocol: ws
This new field would be totally optional and won't act as a object identifier. Thanks to this new field, we could generate some cool documentation, grouping servers by their serviceName, or rather just add a new tag to those with a value on it. I generated a simple mock of the most simple solution, but I think we could do much better 😅

Suggestion
I would prefer following Solution 3 and adding a new field to the Server Object. What do you all think? Do you find it useful?
How can I identify the list of real/physical servers or service names in my system?
A server, to the application, is in most cases an endpoint (for WS and HTTP, it is a little bit more), that the application can connect to. In my mind, it should not know anything about the physical infrastructure (physical servers) as that has a tendency to be volatile. So defining the endpoint has always been enough for my use case when designing the API.
So even though it is not possible to explicitly describe the physical servers, my rebound question would be why do you really want to describe them? 🤔
Generate all servers from service name yavin04.yavin.systems
Sorry, I think you need to specify this one a bit further, as I don't see the use-case 😅
@jonaslagoni I've updated the description of the PR and title to simplify the concept. It's just about grouping servers. A way of grouping servers under a group name, that can map with a service name, a bounded context, a team, etc.
I'm surprised you didn't play the "environment" card. Some people already requested we add a way to group servers by the environment, e.g., production, staging, development, etc. This way, you could have generators or Glee to run the code for production or for development, without having to change which servers it should use/create.
Since there are a myriad of cases, I'm wondering if we shouldn't just allow tags inside the server objects. This way, people can name their tags as environment, boundedContext, serviceName, or whatever they mean with the grouping. Furthermore, they could be adding more "filters" like "use all the servers from the X bounded context of Y environment". It adds flexibility but lacks specific meaning. What do you think? Sounds better or worst?
I'm surprised you didn't play the "environment" card.
Yeah, after the simplification I made, this is mostly the same as https://github.com/asyncapi/spec/issues/623 (different fashion).
I like the tags approach. In fact, tooling could use some kind of selectors for filtering as input, like serviceName: 'foo' , or even more advanced one with mathers like serviceName in ('foo', 'bar'). Just thinking in far feature possibilities. (something like https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#resources-that-support-set-based-requirements).
The downside is that tooling won't be able to assume any those tags are present, nor set an expected tag name. For example, the html-template won't be able to group by environment but just by tags, which loses a bit the context and perhaps some specific logic (we might want to do in the future) behind.
I think going with a generic approach makes total sense; making servers be discoverable, filtered, and categorized by anything is the best extendable approach.
Not adding anything you didn't mention already @fmvilas.
@fmvilas there is also this proposal https://github.com/asyncapi/spec/issues/527 which won't be able to take profit of the tags mechanism, so it will require a dedicated field (I mean, we could somehow reuse it but IMHO will look complex).
I think they're unrelated. This proposal is suggesting a way to prefix channel names, which I'd highly discourage now that we can associate channels with servers. And in v3 you'll be able to fully reuse channels so no need for it IMHO. But yeah, not sure what does it have to do with this proposal here 🤔
I think they're unrelated. This proposal is suggesting a way to prefix channel names, which I'd highly discourage now that we can associate channels with servers. And in v3 you'll be able to fully reuse channels so no need for it IMHO. But yeah, not sure what does it have to do with this proposal here 🤔
The fact that just one field could work for both features. I just wanted to mention it here. Of course, it could be done by selecting a tag for the prefix. But not important to this issue.
🤔 If we ever implement support for channel prefix, I don't think it should be a tag. That would be super unexpected 😅
I would prefer to go in the direction of something more generic like the mentioned tags, but there is one problem, because currently tags work like classic tags, so their key (the name field) is treated as a value. What do you think about adding an optional values field, which would specify what value(s) for given tag. Example:
- name: environment
values: [production, ...]
Note: we can also introduce it in the 2.x.x, because it won't be breaking change.
Why a list of values and not a single value? In the case of the environment it's not that necessary, but for other keys there may be a need to define several values for better filtering, e.g. adding a given server etc to the given group(s).
Thanks to such solution it would be possible to filter (in Studio and html-template) by given type of object (server/operation etc) and key and tag value.
Additionally, we could always create official tags (just like Kubernetes has created official labels, which can be overwritten, but then the tooling stops working) - like this one channelContext, which would define appropriate behavior? What do you think about it?
I would prefer to go in the direction of something more generic like the mentioned tags, but there is one problem, because currently tags work like classic tags, so their key (the
namefield) is treated as a value. What do you think about adding an optionalvaluesfield, which would specify what value(s) for given tag. Example:- name: environment values: [production, ...]Note: we can also introduce it in the 2.x.x, because it won't be breaking change.
Why a list of values and not a single value? In the case of the environment it's not that necessary, but for other keys there may be a need to define several values for better filtering, e.g. adding a given server etc to the given group(s).
Thanks to such solution it would be possible to filter (in Studio and html-template) by given type of object (server/operation etc) and key and tag value.
Additionally, we could always create official tags (just like Kubernetes has created official labels, which can be overwritten, but then the tooling stops working) - like this one
channelContext, which would define appropriate behavior? What do you think about it?
The reality is that with current tags you can make it work by composing a unique tag, such as name: "env:prod". The same could work for multiple values, i.e. name: "owner:platform,product" or declare multiple tags with different names (one for owner:platform, another with owner:product.
However, this gives too much flexibility to users, but it will penalize tooling and it's UX since no format standard will be in place. Especially hard when displaying those tags in documentation, or when filtering by any of those tags. For example, filtering by the owner tag above.
Regarding your suggestion @magicmatatjahu, I like the idea of adding a new field for declaring the value. We should be mindful that by allowing multiple values, filters/selectors will be harder to implement because you will need to specify which strategy the user wants to follow (so implementation as well), especially around inclusion/exclusion of values. For example, let's say I have the following tags:
- name: privacy-policy
values: [soc, soc1, soc2, gdpr]
- Scenario: I want to select servers with only the policy
gdpr - Scenario: I want to select servers with only the policy
gdprANDsoc2 - Scenario: I want to select servers that at least have the policy
gdpr - Scenario: I want to select servers that at least have the policy
gdprANDsoc2 - Scenario: I want to select servers that at least have the policy
gdprORsoc2 - Scenario: I want to select servers that either have the policy
gdprorsoc2 - ETC
Another alternative solution for not having an array in there will be to declare the same tag multiple times but with different value. But this is just the same afaik.
I know it can be done through a discriminator but I want to avoid it.
I like the idea of adding a new field for declaring the value. We should be mindful that by allowing multiple values, filters/selectors will be harder to implement because you will need to specify which strategy the user wants to follow (so implementation as well), especially around inclusion/exclusion of values.
Yeah, but have in mind that this "problem" is in tooling side, not in the spec itself. You have possibility to cover you cases with values, but without it, even tooling doesn't help you (in current solution).
Another alternative solution for not having an array in there will be to declare the same tag multiple times but with different value. But this is just the same afaik.
You cannot do this, you can define only one time given name in tags array :)
I created the following PR's for adding the values field to the Tag Object:
- Spec: https://github.com/asyncapi/spec/pull/744 (~TODO add examples~)
- spec-json-schemas: https://github.com/asyncapi/spec-json-schemas/pull/191
If we finally move forward, I will then create a new PR allowing Tags to be defined at server level.
cc @magicmatatjahu @fmvilas @ekozynin @dalelane
Well, yes, I know, I'm pretty late in this convo, but still.. 😄
After looking on the PR and also this issue I'm not sure what use case are we exactly solving. I saw description about concept with file with common servers, I also saw this:
# common.asyncapi.yaml
components:
servers:
yavin04.yavin.systems-http:
url: https://api.yavin.systems:8080
group: yavin04.yavin.systems
protocol: http
yavin04.yavin.systems-websocket:
url: https://ws.yavin.systems:5000
group: yavin04.yavin.systems
protocol: was
but I do not understand how it helps. Would you expect a kind of possibility to reference from my asyncapi file to common.asyncapi.yaml with something like common.asyncapi.yaml#/components/servers/yavin04.yavin.systems or common.asyncapi.yaml#/components/servers/#group/yavin04.yavin.systems (this one seems to be odd). And how would that be resolved?
The only use case I see is from a documentation perspective, that you can somehow mark a set of servers as test so it is visible clearly in documentation. Then during generation, I can also say, do not generate code for given server name but server environment. Reusing current Tag object is not much useful IMHO. How about getting inspiration from Open Service Broker API Spec? They have a dedicated metadata blob object that users can use for whatever they want, but there is some official convention. Maybe Tag object could have a type property and agreed convention would be any or environment.
Anyway, my entire convo is about the fact that I can see only one use case - environments, that some time ago was already brought in, when we talked about connecting "servers" with specific "channels"
Would you expect a kind of possibility to reference from
myasyncapi file tocommon.asyncapi.yamlwith something likecommon.asyncapi.yaml#/components/servers/yavin04.yavin.systemsorcommon.asyncapi.yaml#/components/servers/#group/yavin04.yavin.systems(this one seems to be odd). And how would that be resolved?
No, I wouldn't expect that at all. If we wanted to somehow do that we would need to change how servers are defined, like:
servers:
development:
server1:
url: https://api.foo.systems:8080
protocol: http
server2:
url: https://ws.foo.systems:5000
protocol: was
Which I don't think it is ideal nor needed.
The only use case I see is from a documentation perspective, that you can somehow mark a set of servers as a test so it is visible clearly in the documentation. Then during generation, I can also say, do not generate code for given server name but server environment.
Exactly, that's the right case at this moment. As you can see, we just need a way of marking servers with a tag or label, that can be added to any servers.
How about getting inspiration from Open Service Broker API Spec?
I took a look at it. Even though I see Metadata-style object could be a fit, it seems that Metadata's purpose is not to categorize or group things together. However, If we read the tags description beginning:
Tags provide a flexible mechanism to expose a classification, attribute, or base technology of a service...
Which made me give another think to this. See below.
Reusing current Tag object is not much useful IMHO.
At this moment, and after giving several thoughts, I'm more convinced than ever about the fact Tags are enough for what we need here. Let me explain it.
By reading our current description or what Tags are for, it reveals the nature of it, which is adding metadata to resources that can be used for grouping:
- At the AsyncAPI level:
-
A list of tags used by the specification with additional metadata.
-
- At Operation level:
-
A list of tags for API documentation control. Tags can be used for logical grouping of operations.
-
- At Message level:
-
A list of tags for API documentation control. Tags can be used for logical grouping of messages.
-
The question I'm doing myself is: Should we really add this new values field?
I pronounced earlier about this with:
The reality is that with current tags you can make it work by composing a unique tag, such as name: "env:prod". The same could work for multiple values, i.e. name: "owner:platform,product" or declare multiple tags with different names (one for owner:platform, another with owner:product.
However, this gives too much flexibility to users, but it will penalize tooling and it's UX since no format standard will be in place. Especially hard when displaying those tags in documentation, or when filtering by any of those tags. For example, filtering by the owner tag above.
But the reality is that everything is possible. For most of the use cases, composable tags (i.e. owner: team_a, team_b) are not needed. Grouping by environment, service name, or other value can be done by setting the tag -name: "env:prod". In the case we need to add multiple values, multiple tags can be added:
- name: "env:prod"
- name: "env:dev"
However, In the case we need composable tag names, such as owner: team_a, team_b, people will need to look for a workaround such as:
- name: "owner:team_a"
- name: "owner:team_b"
In summary, I would say we could go discard this PR, and rather write some examples or even an optional format convention (like the one I suggested in my examples above) that can be later on supported by tooling.
Do you think it makes more sense to use Tags now @derberg ? cc @magicmatatjahu @fmvilas @dalelane
For reference: Maybe there are more use cases or needs for Tags. https://github.com/asyncapi/spec/pull/744
I'm going to create an RFC adding Tags to the Server Object. Candidate for 2.5.0 version of the spec.
PRs adding support for Tags at Server level:
- Spec https://github.com/asyncapi/spec/pull/809
- JSON Schemas: https://github.com/asyncapi/spec-json-schemas/pull/232
- Parser-JS: https://github.com/asyncapi/parser-js/pull/565
:tada: This issue has been resolved in version 3.0.0-next-major-spec.2 :tada:
The release is available on GitHub release
Your semantic-release bot :package::rocket:
:tada: This issue has been resolved in version 2.5.0-next-spec.5 :tada:
The release is available on GitHub release
Your semantic-release bot :package::rocket:
Recent comments about the release from the bot were added by mistake. More details in https://github.com/asyncapi/spec/issues/899