Let's assume I have this API on /api/v2/persons
that enables me to create new entries by POST
ing this JSON:
{
"name": "me"
}
The API is implemented using Spring Boot and if someone sends this JSON ...
{
"name": "me",
"age": 47
}
... Spring refuses to handle it because it can't map the additional value of age
to the defined POJO.
Because of this I was asked to change the API to simply ignore unsupported values like age
. While this could be done rather easily I don't like the idea. In order to compare my personal opinion I searched and checked the API design book I own but didn't find an authoritative source that gives me some guidance which approach (ignore or reject) is better.
6 Answers 6
Whether the API rejects or ignores unsupported fields is a concern for the documentation used by consumers. Pick one, and then document this behavior so clients know what to expect. Let them decide whether or not sending unsupported fields is what they want to do.
There are benefits and drawbacks to either strategy. There is no best practice or design pattern for this. You will need to judge for yourselves based on the sensitivity of data and the potential security impacts of rejecting the request versus simply ignoring it.
Just remember to document this, and inform your clients even if you decide to ignore unsupported fields. Clients might send requests with unsupported fields as a means of feature detection.
If service X supports the
age
field, do this, otherwise fall back to this other thing.
This change in the outward behavior of your API should be considered a breaking change, because it requires downstream changes to how clients handle this response. This justifies an increment to the major version number of the API if you use semantic versioning.
-
If age is optional, would you consider this a breaking change and compulsory to create a v+1 API ?Marged– Marged2021年10月02日 05:45:02 +00:00Commented Oct 2, 2021 at 5:45
-
If this changes the behavior of what clients expect, then yes. Increment the major version if using semantic versioning.Greg Burghardt– Greg Burghardt2021年10月02日 13:25:34 +00:00Commented Oct 2, 2021 at 13:25
-
@Marged: I updated my answer with that last bit of info.Greg Burghardt– Greg Burghardt2021年10月02日 13:30:51 +00:00Commented Oct 2, 2021 at 13:30
This comment is very relevant.
It is only an internal API. The argument of the consumers is that they will ask me to support the age field ans want to start changing their app and send that field before the API was updated. Although I understand the urgency I don't like that approach.
There is nothing wrong with having a planned API change and your endpoint being forwards compatible. It is especially useful when you already know the change is coming and the client is hard to change (e.g. a mobile app typically has a longer release cycle than web service).
Make sure that the semantics of the API are loosely defined and client doesn't make any assumptions. E.g. the semantics could "I want to register someone to an event, here is everything I know about them, do what you want with that data." and the service defines the details (whether to only the name or both the name and the age).
Imagine you already know the age in the client and are planning to use ages in the future on your service and make it mandatory, but don't have time to implement the backend feature right now. You would have to do multiple steps:
- Deploy the backend service with v2.0 (without age) of the API.
- Deploy the client with v2.0 of the API
- Deploy the backend service with v2.1 (with age), have a feature that allows you to make the presence of the age field mandatory
- Deploy all the clients using v2.1 of the API (many users don't update apps immediately or even within years).
- Turn on the feature to make age field mandatory.
Now look at the scenario where you do send ages from the beginning:
- Deploy the service with a forward compatible API v2.0 or use v2.1 already but ignore the "name" field.
- Deploy the app the users, using v2.1 of the API
- Deploy a new version of the service using the age field.
Software is there to be useful for the users. Software principles should serve your users and not serve themselves. Don't make then stand in the way of having a good user experience, without a strong reason.
REST APIs publish contracts so that consumers may know how to use them.
A contract is a written agreement between the publisher and consumer.
Extraneous fields can be handled however you like, provided the publisher and consumer have an agreement.
That's all there is to it.
The Robustness principle, also called "Postel's law" states:
be conservative in what you do, be liberal in what you accept from others
For your case it means to accept the unknown fields and just ignore them.
-
I would suggest this only applies to the consumer of the API, not the provider; the provider should reject any requests not matching a (strict) validation; lest a consumer calls your service with
dry_ruun=true
and expects not to drop an entire database.Duroth– Duroth2021年10月06日 10:35:05 +00:00Commented Oct 6, 2021 at 10:35 -
1Then this should be a required field and not implicitly assume false. The Robustness principle says that as the API provider, you should be forgiving, but as the consumer of the API you should be strict with regards to the parameters. For the response it is the other way around.EricSchaefer– EricSchaefer2021年10月06日 13:00:00 +00:00Commented Oct 6, 2021 at 13:00
In your example, the operation is successful if it sets name to "me" and age to 47. It can fail for any number of reasons, but in this case it must fail because there is no field "age" that can be set to 47.
I would report an error and make sure it doesn’t make any change. Well, that’s me.
Imagine your database has a field age, but by mistake someone called the field "ape" in the request. Same situation, so whatever strategy you use for access to a missing field must also work for a misspelled field. You wouldn’t want the "ape" to be silently ignored, so you can’t allow "age" to be silently ignored.
-
Yes, it's situations like the age/ape scrnario that make me think accepting anything is not a good ideaMarged– Marged2021年10月02日 05:35:46 +00:00Commented Oct 2, 2021 at 5:35
As other answers note, there are prod and cons to both strictness and leniency here. Which approach is correct depends on the specifics of your use case and how you weight those costs and benefits. There's no cut-and-dried right answer here.
I will note though, that my experience with XML-schema defined interfaces tells me that strictness makes evolving an API much more difficult and I think it's a big reason for why things have moved away from XML (among a number of other problems.)
The problem is that (under strictness) when you want to add an element to your document, the old spec becomes invalid. That means that if client A needs age
but client B doesn't, you can't add the field to the interface without either:
- Client B updating their code
- Versioning the interface
Both add to the cost of making changes. However, if the cost of potential errors is very high, you may want to implement strictness around unknown fields. The norm with JSON tends to be lenient with regard to unknown fields which leads to the kind of pushback on this that got you here.
age=47
will perhaps never be stored and nobody will tell you (the user of the API) because I can only return http status 200 and not 199,5 (to signal: almost OK ;-)). I'm not sure if one approach would be to return 202, because I think it has a different meaning.