Handling additions to REST API in client side
Hi All,
Core's REST API is versioned and any change that break backwards-compatibility cause the version to bump so that clients can take care of that.
However, one question that I have been thinking about recently is how to handle additions to REST API that don't necessarily break the backwards compatibility.
For example, Core added max_message_size
attribute to MailingList's REST
endpoint, but it hasn't made into any released version yet. Also, Postorius
added max_message_size in Message Acceptance
settings. The problem here is
that the entire PUT/PATCH request is going to fail if the currently running
version of Core doesn't have max_message_size
attribute exposed (Unknown
Attribute Error).
There is no easy way to check for whether the Core has this attribute as API is versioned at 3.1 for both cases.
So, how do we actually handle this and maybe future cases like this?
-- thanks, Abhilash Raj
On 12/27/2017 09:49 AM, Abhilash Raj wrote:
Hi All,
Core's REST API is versioned and any change that break backwards-compatibility cause the version to bump so that clients can take care of that.
However, one question that I have been thinking about recently is how to handle additions to REST API that don't necessarily break the backwards compatibility.
For example, Core added
max_message_size
attribute to MailingList's REST endpoint, but it hasn't made into any released version yet. Also, Postorius added max_message_size inMessage Acceptance
settings. The problem here is that the entire PUT/PATCH request is going to fail if the currently running version of Core doesn't havemax_message_size
attribute exposed (Unknown Attribute Error).There is no easy way to check for whether the Core has this attribute as API is versioned at 3.1 for both cases.
So, how do we actually handle this and maybe future cases like this? How about defining it this way:
- The result of queries can be viewed as dictionaries
- New endpoints (urls) can be added anytime
- No endpoint is removed without a version bump
- Existing dict keys will not be dropped without a version bump
- The format of values assigned to existing keys will not change without a version bump
- New keys (and values) can be added anytime
This would make things like exposing new stuff easy. Clients that don't know about a field can just ignore it.
On 12/28/2017 08:33 AM, Simon Hanna wrote:
- New keys (and values) can be added anytime
This would make things like exposing new stuff easy. Clients that don't know about a field can just ignore it.
But that doesn't address the original issue which is that when a new key/value is added and a client is updated to use it, the client has no way to require a core API that provides it.
-- Mark Sapiro <mark@msapiro.net> The highway is for gamblers, San Francisco Bay Area, California better use your sense - B. Dylan
On 12/28/2017 06:19 PM, Mark Sapiro wrote:
On 12/28/2017 08:33 AM, Simon Hanna wrote:
- New keys (and values) can be added anytime
This would make things like exposing new stuff easy. Clients that don't know about a field can just ignore it.
But that doesn't address the original issue which is that when a new key/value is added and a client is updated to use it, the client has no way to require a core API that provides it.
Up until now I was under the impression that the clients (Postorius/Hyperkitty) always require the latest version of core. Do we really want to add the burden of having to care for different api versions in the clients?
If we would start from scratch something like http://www.coreapi.org/ might be feasible, adding that now would more or less require a complete rewrite of Postorius.
In my view backward compatibiliy should be applied so that old clients can still access newer versions of core. I don't think it's a good idea to require Postorius to be compatible with older(all?) versions of core.
Simon Hanna writes:
Up until now I was under the impression that the clients (Postorius/Hyperkitty) always require the latest version of core.
We don't require that the client be Postorius or Hyperkitty though. While in one sense we're not responsible for what third party clients do *at all*, in another I don't think we should make it hard for them.
It's also possible that a third party (hello, Systers!) might have modified versions of the code, and not realize that their "don't fix it 'cause it ain't broken" core isn't compatible with their up-to-the-minute Postorius that they pulled to get Abhilash's security patch.
Do we really want to add the burden of having to care for different api versions in the clients?
I don't think that's the question. The question is, "what can we do to make things easier for developers both in the Mailman project and out?"
In my view backward compatibiliy should be applied so that old clients can still access newer versions of core. I don't think it's a good idea to require Postorius to be compatible with older(all?) versions of core.
We don't. Only the current version. The problem is that "version" is a potentially large set. That *is* *our* fault (looking straight at Barry with one forearm on the emergency exit crashbar ;-).
Steve
Abhilash Raj wrote:
Core's REST API is versioned and any change that break backwards-compatibility cause the version to bump so that clients can take care of that.
The 3.1 version bump happened because there was no backward compatible way to handle the UUID as int vs hex string change required for some JavaScript libraries. It couldn't be autodetected in all cases. In general though, we've mostly be able to manage changes in a backward compatible way.
However, one question that I have been thinking about recently is how to handle additions to REST API that don't necessarily break the backwards compatibility.
This problem is analogous to adding arguments to functions. This is usually handled in backward compatible ways in Python by appending new arguments to the end of the parameter list and giving them default values. Or to use keyword-only arguments. The analogy breaks down in one specific case though.
For example, Core added
max_message_size
attribute to MailingList's REST endpoint, but it hasn't made into any released version yet. Also, Postorius added max_message_size inMessage Acceptance
settings. The problem here is that the entire PUT/PATCH request is going to fail if the currently running version of Core doesn't havemax_message_size
attribute exposed (Unknown Attribute Error).
PATCH won't fail because it allows for partial representations. PUT does fail because it requires the entire new representation to be included in the request (it's a complete replacement). This is where the analogy to function arguments break down.
I don't really know of any good way to handle this that still conforms to REST principles. I don't think we want to rev the API in these cases since that'll result in a lot of version churn.
There is no easy way to check for whether the Core has this attribute as API is versioned at 3.1 for both cases.
So, how do we actually handle this and maybe future cases like this?
Simon suggests:
- The result of queries can be viewed as dictionaries
- New endpoints (urls) can be added anytime
- No endpoint is removed without a version bump
- Existing dict keys will not be dropped without a version bump
- The format of values assigned to existing keys will not change without a version bump
- New keys (and values) can be added anytime
This is pretty much the criteria I've used in the past, and it works well enough in practice except for the PUT exception. A couple of thoughts on how to handle this include, using PATCH in preference to PUT, using PUT but catch any exception then fall back to PATCH, do a GET first to get the list of keys. None of those are great options, although some caching might help.
Mailman's REST API is very dynamic so we don't even have a static representation of it that can be queried. I did a quick scan of RESTful Web APIs (Richardson & Amundsen - my go to bible for REST design philosophy) and didn't find a specific discussion on this topic.
Cheers, -Barry
On Thu, 2017-12-28 at 15:25 -0500, Barry Warsaw wrote:
Abhilash Raj wrote:
Core's REST API is versioned and any change that break backwards- compatibility cause the version to bump so that clients can take care of that.
The 3.1 version bump happened because there was no backward compatible way to handle the UUID as int vs hex string change required for some JavaScript libraries. It couldn't be autodetected in all cases. In general though, we've mostly be able to manage changes in a backward compatible way.
However, one question that I have been thinking about recently is how to handle additions to REST API that don't necessarily break the backwards compatibility.
This problem is analogous to adding arguments to functions. This is usually handled in backward compatible ways in Python by appending new arguments to the end of the parameter list and giving them default values. Or to use keyword-only arguments. The analogy breaks down in one specific case though.
For example, Core added
max_message_size
attribute to MailingList's REST endpoint, but it hasn't made into any released version yet. Also, Postorius added max_message_size inMessage Acceptance
settings. The problem here is that the entire PUT/PATCH request is going to fail if the currently running version of Core doesn't havemax_message_size
attribute exposed (Unknown Attribute Error).PATCH won't fail because it allows for partial representations. PUT does fail because it requires the entire new representation to be included in the request (it's a complete replacement). This is where the analogy to function arguments break down.
I don't really know of any good way to handle this that still conforms to REST principles. I don't think we want to rev the API in these cases since that'll result in a lot of version churn.
I agree that we don't want to rev the API for additions.
PATCH won't fail when running partial updates, but it won't silently drop the attributes that it doesn't support. So from client side, there is no real way to understand when to include that attribute which was added in a later version.
There is no easy way to check for whether the Core has this attribute as API is versioned at 3.1 for both cases.
So, how do we actually handle this and maybe future cases like this?
Simon suggests:
- The result of queries can be viewed as dictionaries
- New endpoints (urls) can be added anytime
- No endpoint is removed without a version bump
- Existing dict keys will not be dropped without a version bump
- The format of values assigned to existing keys will not change without a version bump
- New keys (and values) can be added anytime
This is pretty much the criteria I've used in the past, and it works well enough in practice except for the PUT exception. A couple of thoughts on how to handle this include, using PATCH in preference to PUT, using PUT but catch any exception then fall back to PATCH, do a GET first to get the list of keys. None of those are great options, although some caching might help.
As I mentioned using PATCH doesn't really solve the problem as we don't know when we can update that particular attribute, however, it does help update other attributes in the same resource.
Exception caching might work, but then it would probably involve parsing the error message to find out which field is the problem before making a PATCH request without that attribute.
Doing a GET first sounds expensive to me, we would definitely need some caching to be able to use this idea.
We need some way to associate attributes with a minimum Core version, which we
can get from /system
endpoint. Although, for now, it probably is an overkill.
I will do some static stuff to take care of this.
Mailman's REST API is very dynamic so we don't even have a static representation of it that can be queried. I did a quick scan of RESTful Web APIs (Richardson & Amundsen - my go to bible for REST design philosophy) and didn't find a specific discussion on this topic.
How bad do you think would it be for Core to silently drop extra attributes and only use the ones that it needs?
-- thanks, Abhilash Raj
On 12/28/2017 04:50 PM, Abhilash Raj wrote:
How bad do you think would it be for Core to silently drop extra attributes and only use the ones that it needs?
The problem with that approach is it leads to "I set it in Postorius but it didn't take effect" kinds of issues.
-- Mark Sapiro <mark@msapiro.net> The highway is for gamblers, San Francisco Bay Area, California better use your sense - B. Dylan
Abhilash Raj writes:
How bad do you think would it be for Core to silently drop extra attributes and only use the ones that it needs?
I have no idea what the semantics of that would be. You'd need to come up with rules about semantics of adding attributes that guarantee this works as expected, but if you could, you probably wouldn't need the new attributes in the first place.
I think it would be better for Core to fail with a reason of unknown attribute and allow the client to retry. But if the client isn't prepared to retry, there's no existing guarantee that the client can rollback the transaction. We'll just have to document that clients need to be prepared to rollback if they use attributes not documented for the version they require.
Of course this is mitigated by the fact that in general clients wouldn't be keeping substantial state anyway.
On Dec 28, 2017, at 19:50, Abhilash Raj <maxking@asynchronous.in> wrote:
PATCH won't fail when running partial updates, but it won't silently drop the attributes that it doesn't support. So from client side, there is no real way to understand when to include that attribute which was added in a later version.
Yes, I see the problem with that.
We need some way to associate attributes with a minimum Core version, which we can get from
/system
endpoint. Although, for now, it probably is an overkill. I will do some static stuff to take care of this.
What happens when someone runs from say git head though? /system/version won’t keep up with that, and besides, it’s going to become an increasingly big compatibility matrix over time.
How bad do you think would it be for Core to silently drop extra attributes and only use the ones that it needs?
I think it could be problematic, as Steve points out.
I don’t really have a good answer, and I’m wondering what the state of the art is. Maybe we should ask some experts for advice (we do have some friends who are experts in this).
-Barry
Abhilash Raj writes:
There is no easy way to check for whether the Core has this attribute as API is versioned at 3.1 for both cases.
Barry has spoken (later, in a parallel thread) to the effect that this isn't a real problem "most" of the time. But to me it seems like the obvious ways are (1) to expose the whole API (as an HTTP collection attached at the version number -- Barry says this is hard, though), (2) to somehow ensure that client-side REST calls are transactional (I don't think this is possible), or (3) to provide the client a way to probe for an API that the server guarantees is a no-op, except for returning SUCCESS or NOTFOUND.
Steve
participants (5)
-
Abhilash Raj
-
Barry Warsaw
-
Mark Sapiro
-
Simon Hanna
-
Stephen J. Turnbull