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

Review API for exposing channel attach flags #279

Closed
SimonWoolf opened this issue Feb 27, 2017 · 11 comments
Closed

Review API for exposing channel attach flags #279

SimonWoolf opened this issue Feb 27, 2017 · 11 comments
Milestone

Comments

@SimonWoolf
Copy link
Member

ably/ably-js#377 adds an undocumented API (a secret additional argument to attach()) to specify channel attach flags, which is used by the realtime channel_attach_flags tests. Per ably/ably-js@1cfe662#commitcomment-21065665 , we should review this to discuss how we want to expose this officially (and whether we want to at all).

The ably-js temporary api currently takes an array of strings specified in the ProtocolMessage flags object, e.g. channel.attach(['PUBLISH','SUBSCRIBE'], (err) => console.log(err)).

@mattheworiordan
Copy link
Member

@SimonWoolf @paddybyers FYI I've tagged this for v1.1

@SimonWoolf
Copy link
Member Author

This is a prerequisite for #374 and a couple other proposed channel flags, we should make a decision on this so client libs can start implementing for 1.1.

two most obvious options:

  1. An explicit extra param of channel#attach that takes an array (of strings for js and enumerated constants for strongly typed languages, like presence actions). To change flags, call attach() again. Example: channel.attach(['publish','subscribe'], (err) => ...);

  2. A channelOption that takes an array (of strings or an enum as above). To change, call channel#setOptions. Example: realtime.channels.get(channelName, { flags: ['publish','subscribe'] });

any opinions, or third options?

In either case, we need to decide:

  1. Whether publish(), subscribe() etc implicitly reattach with the relevant flags if you were previously attached without them.
  2. Whether passing any flags at all means you need to pass a complete set of flags. On one hand it'd be nice for someone to just pass ['no_backlog'] if that's the only thing they want to change and still attach with a complete set of modes, but on the other hand it's a bit weird if some flags imply the absence of others when others don't (i.e. if ['no_backlog', 'publish'] implies no subscribe mode, but ['no_backlog'] on its own doesn't)
  3. Whether realtime should start allowing a publish if you're attached without the publish flag, by analogy with transient publishes (seems a bit odd that we'd allow a publish if you're not attached at all, but not if you're attached with subscribe-only flags. But maybe that's ok because it's a state the user specifically requested. And I guess there's no reason for anyone to actually do that, so it doesn't really matter, so maybe we should leave it as it is at the moment just to save effort)

cc: @mattheworiordan @paddybyers @funkyboy

@funkyboy
Copy link
Contributor

I like this

realtime.channels.get(channelName, { flags: ['publish','subscribe'] });

we can even simplify it as

realtime.channels.get(channelName, ['publish', 'subscribe']);

Whether publish(), subscribe() etc implicitly reattach with the relevant flags if you were previously attached without them.

Yes, all the way. But I am not sure what's the impact of this on the backend.

Whether passing any flags at all means you need to pass a complete set of flags...

I think we should come up with default values. So this:

realtime.channels.get(channelName)

under the hood becomes

realtime.channels.get(channelName, [DEFAULT_VALUES_HERE])

and this

realtime.channels.get(channelName, ['publish'])

becomes

realtime.channels.get(channelName, ['publish', DEFAULT_VALUES_EXCLUDING_PUBLISH])

@SimonWoolf
Copy link
Member Author

SimonWoolf commented Mar 19, 2018

we can even simplify it as realtime.channels.get(channelName, ['publish', 'subscribe']);

For js we could, but for strongly typed languages it's a bit of a hassle for it to take either a channelOptions or an array, so probably better stick with just a channelOptions for simplicity.

I think we should come up with default values. So this: ...

I don't follow what you're proposing. The default values of the flags that are modes are all on, so ['publish', DEFAULT_VALUES_EXCLUDING_PUBLISH] would be equivalent to [DEFAULT_VALUES_HERE]. E.g. how would you specify a publish-only attachment with your proposal?

To re-iterate: the problem here is whether passing any modes at all means you need to pass a complete set of modes, given that we want the modal flags to default to on but be arbitrarily specifiable if needed, but the non-modal flags to default to off.

I think my preferred solution is to separate them in the api -- so you have a channelOptions that has a mode property that takes an array where you're expected to supply every mode you want if you supply that option at all; with non-modal flags having their own, separate properties. e.g. realtime.channels.get(channelName, { mode: ['publish','subscribe'], noBacklog: true, ... });. Then it's up to the client lib to concoct the flags field out of that.

@funkyboy
Copy link
Contributor

Why not having them all belonging to the same options object? Like:

realtime.channels.get(channelName, { publish: true, subscribe: true , noBacklog: true, ... });

Feels more explicit to me.

@mattheworiordan
Copy link
Member

I think the proposal to modify channels.get is wrong because get and the associated ChannelOptions currently only affect the client SDK configuration of a channel. The channel attach flags on the other hand affect the server state (mostly), and thus we should not consider combining the two.

So my proposal would be channel options for get remain options for the REST or Realtime Channel object, but are not sent to the server. One could mutate the Channel object (which you can do now), but that change only changes the Channel object config and is not relayed to the server. This fits well with our encryption model where we would not want customers ever to think encryption key details are sent to the Ably servers along with the new attach flags we are proposing.

Additionally, we've discussed this before in detail and agreed this was the correct way to approach it.

An attach now currently changes the state of a channel, but only so much as it's a temporary state change whilst it waits for an ACK from Ably (from ATTACHING to ATTACHED etc). This concept should remain as it is now with the exception that now you can now call attach again to change the flags of a channel.

If we are considering flags though, I think we should think a little broader if we are making an API change now. For example:

So before we can think about the API in clients, I think we also need to think carefully about the protocol itself.

At a protocol level, I would argue that:

  • Any request for an ATTACH can optionally include attach options which are exposed in two attributes: flags (currently supported), options which is an arbitrary set of key/value pairs.
  • All ATTACH requests must be complete i.e. no previous state is intersected / merged in any way. What you ask for in terms of flags or options is treated as the complete state you are after.
  • All ATTACH requests are responded to with with ATTACHED response with all flags and options included.
  • A strict flag should be included that rejects an ATTACH request that cannot be fully satisfied. I am not sure if we should make this the default. I think it would too much of a breaking change i.e. if a user attaches, then subscribes, which results in two attach

Considerations of this:

  • If we default an ATTACH request to not set any flags i.e. no messages are received, it's not yet set up to publish, no presence etc, then we will be increasing the traffic between client and server significantly under most circumstances as people who publish and subscribe may now do an attach, publish and subscribe each resulting in a new ATTACH request.
  • If a user calls attach({ flags: ['PUBLISH', 'STRICT'] }) and that request is inflight, and they later call subscribe which implicitly modifies that request to { flags: ['PUBLISH', 'SUBSCRIBE', 'STRICT'] }, the subscribe operation will now fail yet subscribe in most languages has no failure API. I suppose we'd maintain the STRICT request of the first attach and simply move the channel to the FAILED state as it does not have suitable permissions.

Now moving onto the API, I think we should approach this as:

  • attach now supports options and flags as part of a AttachedState type.
  • flags should be enums where possible to avoid stupid typos. Even if JS, we could have Ably.Types.AttachFlags.Presence for example, and a user could perhaps do something simple like const flags = Ably.Types.AttachFlags to simplify the readability of their code. Most languages have shorthands for enums anyways. If developers really hate this, they can always use the integer values and simply add them together i.e. 65536 + 131072 = AttachFlags.PRESENCE + AttachFlags.PUBLISH.
  • options should be key/value string pairs allowing flexibility in the API in future for queries, filters, aggregations and transformations to be applied at a channel level.
  • If you call attach with no AttachedState, you are given the lowest set of flags available. For consistency, any subscribe operation you later perform that the client does not have explicit permission to perform will result in an ATTACH upgrade request. This request could fail if the user does not have that right, but unless STRICT is set to true, it will simply result in the channel emitting an UPDATE event with the error and remain attached. For example, a request to subscribe on a channel that was attached with no options would trigger a request to ATTACH with the SUBSCRIBE flag. The odd thing here is the subscribe ATTACH request could fail now yet the subscribe could succeed, but I don't think that is fundamentally different to before except that the subscribe method could now wait for an ACK of the ATTACH request but wouldn't know which ATTACHED response corresponds to the ATTACH request it makes.
  • A publish or presence enter|update* operation does not need to implicitly do an ATTACH upgrade. Server-side this could be done and an ATTACHED could be sent to the client notifying of it of a new state if it wants to, this is fortunately at the discretion of the server.
  • For presence subscribe, I believe we can also wait for the user to call get or subscribe before we need to request PRESENCE_SUBSCRIBE (unless already explicitly requested)
  • Unless you explicitly request a PUBLISH flag, your channel capacity for publishing is not reserved.
  • flags and options are available on the channel object as a read-only attributes of channel.properties so that users can inspect the state if they want.
  • A user can explicitly request a new attach state at any time by simply calling attach again such as channel.attach({ flags: [AttachFlags.PRESENCE, AttachFlags.SUBSCRIBE], options: { "filter": "name == 'foo'"}, (err) => { ... }). The callback will provide a success / failure response, and an UPDATE event will be emitted if the channel was previously attached, an ATTACHED event will be emitted if the channel was not ATTACHED, and a FAILED event will be emitted if the request cannot be satisfied (typically only if the STRICT flag is included).

So to summarise:

  • attach takes a new optional AttachedState argument.
  • AttachedState contains current ProtocolMessage flags values plus additionally supports new STRICT flag, and QOS_* flags which for now will be QOS_NO_BACKLOG, but we'll possibly add something like QOS_LOW in the future for publishes that require lower replication or delivery guarantees. AttachedState also includes options which is key/value string pairs which are unused for now but supported.
  • attach can be called with AttachedState to update the attach status, which results in an ATTACHED, UPDATE or FAILED channel event. If called without AttachedState, it behaves as it does now.
  • channel.subscribe, presence.subscribe, and presence.get all result in implicit channel attach state upgrades if they don't already have that state set.
  • A channel can receive an ATTACHED ProtocolMessage update at any point (as it does now) but the options and flags will be stored on the channel object and made available through channel.properties.flags and channel.properties.options
  • The new STRICT flag will ensure an implicit or explicit channel state changes that cannot be fully fulfilled result in the channel becoming FAILED
  • This should be entirely backwards compatible

@paddybyers
Copy link
Member

A few comments/questions:

  • Why the difference between flags and options? Is it just because we already have a flags word in the message or is there going to be some difference in behaviour?

  • I'm not sure we should specify and implement support for options without having any application for it.

  • I'm inclined to think that strict mode would be the default - why ask for a mode that you don't need? The legacy attach() with no mode specified is, as now, a request to attach with all permitted modes, but that doesn't have to mean that the default behaviour of the new API is non-strict.

  • If you want to ask for a mode non-strictly then I think there is a use-case for that but it's not the default use-case. Conceptually a request would then include a set of requested modes and a "minimum" subset of modes. The request fails if you can't satisfy everything in the minimum set.

  • enter() does require a non-transient attach - while present on a channel you need a permanent association between the connection and the channel.

  • we either need to disallow overlapping attach requests or the attaching state becomes more complex - ie you can be attached with a specific mode and attaching with one or more other modes

@mattheworiordan
Copy link
Member

Is it just because we already have a flags word in the message or is there going to be some difference in behaviour?

flags already exist as an enum and are pre-defined.
options gives us the flexibility for customers to specify anything they want moving forwards with flexible k/v pairs. I appreciate they could be the same thing, but how if one is an enum and the other is a k/v pair?

I'm not sure we should specify and implement support for options without having any application for it.

I strongly disagree. We didn't implement options support for presence for this reason. Modifying the client libraries again in the future is painful, we know this.

I'm inclined to think that strict mode would be the default - why ask for a mode that you don't need?

Because if the client lib now implicitly requests a state it does not have access to, the channel will become failed. Case in point, an attach() request, then a subscribe() request in the existing lib will always pass so long as the client has at least one privilege for that channel. In the new API the subscribe request will result in the channel becoming failed.
Our default is non strict for capabilities too, so consistent.

The request fails if you can't satisfy everything in the minimum set.

We don't do this with capabilities.

enter() does require a non-transient attach - while present on a channel you need a permanent association between the connection and the channel.

Sure, but that is a server responsibility and not an implementation detail the client needs to worry about.

we either need to disallow overlapping attach requests or the attaching state becomes more complex - ie you can be attached with a specific mode and attaching with one or more other modes

I thought that too at first, but then realised that's unnecessary.

If you request ATTACH with none, then ATTACH with subscribe, then ATTACH with subscribe + publish, before any ATTACHED is returned, who cares? You still end up with the same result. If any ATTACH request was STRICT and not allowed, the client would end up in the FAILED state anyway.

@SimonWoolf
Copy link
Member Author

@mattheworiordan
Copy link
Member

@SimonWoolf how much of this is now done with the new channel params? Just wondering if we can close this long issue perhaps and open another with only what is outstanding.

@SimonWoolf
Copy link
Member Author

Sure, I think we can say this is now basically all fixed by #766 👍

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

No branches or pull requests

4 participants