Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow grouping servers #654

Closed
Tracked by #735 ...
smoya opened this issue Nov 12, 2021 · 20 comments · Fixed by #842
Closed
Tracked by #735 ...

Allow grouping servers #654

smoya opened this issue Nov 12, 2021 · 20 comments · Fixed by #842

Comments

@smoya
Copy link
Member

smoya commented Nov 12, 2021

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 #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 😅

Mock containing a new tag on Servers description with the serviceName in place

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?

@smoya smoya added the 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md) label Nov 12, 2021
@smoya smoya changed the title Make servers identifiable by service name Make servers aware of their service name Nov 12, 2021
@jonaslagoni
Copy link
Sponsor Member

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 😅

@smoya smoya changed the title Make servers aware of their service name Allow grouping of servers Mar 21, 2022
@smoya
Copy link
Member Author

smoya commented Mar 21, 2022

@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.

@smoya smoya changed the title Allow grouping of servers Allow grouping servers Mar 21, 2022
@smoya smoya mentioned this issue Mar 21, 2022
55 tasks
@fmvilas
Copy link
Member

fmvilas commented Mar 21, 2022

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?

@smoya
Copy link
Member Author

smoya commented Mar 21, 2022

I'm surprised you didn't play the "environment" card.

Yeah, after the simplification I made, this is mostly the same as #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.

@smoya
Copy link
Member Author

smoya commented Mar 21, 2022

@fmvilas there is also this proposal #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).

@fmvilas
Copy link
Member

fmvilas commented Mar 21, 2022

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 🤔

@smoya
Copy link
Member Author

smoya commented Mar 22, 2022

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.

@fmvilas
Copy link
Member

fmvilas commented Mar 22, 2022

🤔 If we ever implement support for channel prefix, I don't think it should be a tag. That would be super unexpected 😅

@magicmatatjahu
Copy link
Member

magicmatatjahu commented Mar 23, 2022

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?

@smoya
Copy link
Member Author

smoya commented Mar 23, 2022

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?

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 gdpr AND soc2
  • 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 gdpr AND soc2
  • Scenario: I want to select servers that at least have the policy gdpr OR soc2
  • Scenario: I want to select servers that either have the policy gdpr or soc2
  • 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.

@magicmatatjahu
Copy link
Member

magicmatatjahu commented Mar 23, 2022

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 :)

@smoya
Copy link
Member Author

smoya commented Mar 28, 2022

I created the following PR's for adding the values field to the Tag Object:

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

@derberg
Copy link
Member

derberg commented Apr 5, 2022

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"

@smoya
Copy link
Member Author

smoya commented Apr 5, 2022

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?

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

@smoya
Copy link
Member Author

smoya commented Apr 26, 2022

For reference: Maybe there are more use cases or needs for Tags. #744

@smoya
Copy link
Member Author

smoya commented Jun 17, 2022

I'm going to create an RFC adding Tags to the Server Object. Candidate for 2.5.0 version of the spec.

@smoya
Copy link
Member Author

smoya commented Jun 17, 2022

@asyncapi-bot
Copy link
Contributor

🎉 This issue has been resolved in version 3.0.0-next-major-spec.2 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@asyncapi-bot
Copy link
Contributor

🎉 This issue has been resolved in version 2.5.0-next-spec.5 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@derberg
Copy link
Member

derberg commented Jan 31, 2023

Recent comments about the release from the bot were added by mistake. More details in #899

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants