spec icon indicating copy to clipboard operation
spec copied to clipboard

Allow grouping servers

Open smoya opened this issue 3 years ago • 17 comments

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 😅

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 avatar Nov 12 '21 17:11 smoya

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 avatar Dec 20 '21 12:12 jonaslagoni

@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 avatar Mar 21 '22 19:03 smoya

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?

fmvilas avatar Mar 21 '22 19:03 fmvilas

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.

smoya avatar Mar 21 '22 21:03 smoya

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

smoya avatar Mar 21 '22 22:03 smoya

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 🤔

fmvilas avatar Mar 21 '22 22:03 fmvilas

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.

smoya avatar Mar 22 '22 08:03 smoya

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

fmvilas avatar Mar 22 '22 19:03 fmvilas

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?

magicmatatjahu avatar Mar 23 '22 11:03 magicmatatjahu

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.

smoya avatar Mar 23 '22 11:03 smoya

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

magicmatatjahu avatar Mar 23 '22 12:03 magicmatatjahu

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

smoya avatar Mar 28 '22 14:03 smoya

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"

derberg avatar Apr 05 '22 08:04 derberg

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 avatar Apr 05 '22 15:04 smoya

For reference: Maybe there are more use cases or needs for Tags. https://github.com/asyncapi/spec/pull/744

smoya avatar Apr 26 '22 08:04 smoya

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

smoya avatar Jun 17 '22 15:06 smoya

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

smoya avatar Jun 17 '22 16:06 smoya

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

asyncapi-bot avatar Sep 29 '22 06:09 asyncapi-bot

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

asyncapi-bot avatar Jan 31 '23 09:01 asyncapi-bot

Recent comments about the release from the bot were added by mistake. More details in https://github.com/asyncapi/spec/issues/899

derberg avatar Jan 31 '23 10:01 derberg