21

I have an object hierarchy that I want to expose through a REST API, and I want to discuss best practices. I have seen this question asked before (e.g. here at the end, here, here, and especially here), but never really any conclusions arrived at.

Suppose I have one base class, say Animal, and many different classes that inherit, say Antelope, Bird, ..., Zebra. Each kind of animal has unique attributes.

Which is better?

  1. One endpoint path, /animals. You send and receive slightly different bodies depending on kind. There is a type field to help parse.
  2. A separate endpoint path for each kind of animal, /animals/antelopes, /animals/birds, ..., /animals/zebras. Each endpoint would always accept and return a consistent body (but these bodies would be different from each other).
emft
  • 376
  • 1
  • 2
  • 11

2 Answers2

8

I would go with option #1. Reason is: What if your list - Antelope, Bird, ..., Zebra is increased in future? You will end-up creating separate endpoints for every animal.

Assumption is that there are not much differences between payloads of /animals/antelopes and /animals/birds. If differences are more, then you will need to create separate endpoints for each payload.

If there are minor differences between payloads, I would suggest to add extra map containing key value pairs and these pairs are the values which are specific to that particular animal type.

As you have mentioned, extra map can be of type -

{
 'shared_animal_attribute_1': 'foo', 
 'shared_animal_attribute_n': 'bar', 
 'extra_attributes': 
     { 
         'antelopiness': 10
     }
}

You will need to extra processing logic of these attributes on the server side. This will reduce the pain of maintaining separate endpoints. If you have schema to validate, it's a hassle-free implementation.

asg
  • 2,248
  • 3
  • 18
  • 26
  • Hm, @asg, I'm not sure how it seems like payloads and responses are the same for each animal. In fact they are different--"You send and receive slightly different bodies depending on kind" and "Each kind of animal has unique attributes". I agree, if every kind of animal had the same payload and response (1) would be the obvious choice. The tricky thing here is that they are different. – emft Sep 07 '17 at 16:57
  • hey @emft this is what you have mentioned in your question - #2 - 'The payloads and responses are the same for each endpoint'. If it is same, then why do you need a separate endpoint.. thats the point. – asg Sep 07 '17 at 17:07
  • Ah, I see what you mean. What I mean is, if there were an endpoint for each animal, then each endpoint would receive and return consistent bodies _within the endpoint_. That is, `/animals/antelopes` would always have Body A, and `/animals/birds` would always have Body B, but Body A =/= Body B. I will edit to reflect this. – emft Sep 07 '17 at 17:10
  • Okay.. got it. Question is - How much is the difference between Body A and Body B? How many attributes will be different? If those are not high, you can have an extra map containing key value pairs and these pairs are the values which are specific to that particular animal type. Again it depends on whether these extra attributes are manageable. If not , you may need to create a separate endpoint. But should be a last resort. – asg Sep 07 '17 at 17:19
  • Interesting. Can you explain a little more what you mean by "an extra map"? Something like `{'shared_animal_attribute_1': 'foo', ..., 'shared_animal_attribute_n': 'bar', 'extra_attributes': {'antelopiness': 10}}` maybe? – emft Sep 07 '17 at 17:24
  • Yes, thats correct. For every new property - shared_animal_attribute_1, shared_animal_attribute_2 etc, you will need to add extra processing logic on server side. This reduce the pain of maintaining separate endpoint for each animal. – asg Sep 07 '17 at 17:38
  • Extra server-side logic is no problem. I'm most concerned about my API consumers -- I want them to easily process the responses. Perhaps this could be fixed if I sent a schema along too, what do you think? Also, this has been helpful and I'd be happy to accept it, if you want to make a quick edit to the original answer to reflect our conversation. – emft Sep 07 '17 at 17:44
  • With schema, map works like a charm. It's a hassle free implementation. We have implemented this way and working for us. – asg Sep 07 '17 at 17:47
  • Hey @asg, I'd love to give you credit -- can you revise your answer a little to reflect our conversation and then I can accept it? Thanks! – emft Sep 08 '17 at 22:22
  • @emft Sure.. will do it :). Thanks for the reminder! – asg Sep 09 '17 at 04:36
  • IMHO, polymorphism in a REST API is an anti-pattern. – Finster Jan 25 '22 at 15:12
6

OpenAPI 3.0 (former Swagger) comes with true support for polymorphism in JSON.

It's been possible to combine schemas using keywords such as oneOf, allOf, anyOf and get a message payload validated since JSON schema v1.0.

https://spacetelescope.github.io/understanding-json-schema/reference/combining.html

However, schemas composition in Swagger has been enhanced by the keywords discriminator (v2.0+) and oneOf (v3.0+) to support inheritance and polymorphism.

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaComposition

I have provided an example of a polymorphic POST method here.

dbaltor
  • 2,737
  • 3
  • 24
  • 36
  • It's true that OAS allows this. However, there is no support for the feature to be displayed in Swagger UI ([link](https://github.com/swagger-api/swagger-ui/issues/2438)), and I think a feature is of limited use if you can't show it to anyone. – emft Jan 08 '18 at 17:38
  • 2
    @emft, not true. As of writing this answer, Swagger UI already supports that. – Andrejs Cainikovs Feb 07 '18 at 09:06
  • also the server code cannot handle oneOf schema. it becomes 'Object'. – PeiSong Oct 12 '19 at 08:07
  • 2
    OAS supports this but looks like code generators don't https://github.com/OpenAPITools/openapi-generator/issues/15 – DarVar Feb 06 '20 at 16:38
  • For Java, it has seemingly fixed by the OpenAPITools PR #5120 https://github.com/OpenAPITools/openapi-generator/pull/5120 – dbaltor Feb 06 '20 at 23:00