After creating a few RESTful apis I’ve learnt the hard way that agreeing on the structure of the messages going backwards and forwards can be difficult but what can be even more difficult is managing the changes to these messages once the first implementation has been committed. Going with the default option of returning and accepting everything as application/json typically means that the client and server will need to synchronise their release cycles when breaking changes occur. In the age of CD this should be a red flag it shows us that we will have problems scaling development in future. The worst case is when the team developing the api does not have an influence over the release cycle of its consumers. Having your release cycle dictated by the releases of other systems isn’t a great, just the idea of holding the meetings to organise the process should be enough to send you looking for a way to avoid this happening to you.
Thankfully, the HTTP protocol already has a mechanism to help us, that we are already using knowingly or unknowingly, its content negotiation. When we tell our framework that we’re returning JSON and then use POSTMAN or another client to access the resource and we set the accept header to application/xml we’ll get a 406 response. This Not Acceptable response tells us that the server does have an XML representation of the resource we were asking for. We can leverage this mechanism by defining our own media types. As below
@GetMapping(produces = “application/json”)
ResponseEntity<Foo> getFoo() { ...
Becomes
@GetMapping(produces = “application/vnd.myco.myapp.foo.v1+json”
ResponseEntity<Foo> getFoo() { ...
Now the development cycles of our api and client are decoupled. When a braking change needs to be introduced a second version of the method can be added
@GetMapping(produces = “application/vnd.myco.myapp.foo.v2+json”
ResponseEntity<FooV2> getFooV2() { ...
These can co exist until the time that the first is no longer used and if the client want’s to be ready for the new version before it’s released it can request the updated version and then fallback to the older version if it receives a 406 response. Alternatively, if the use case supports it, the client could send an accept header for both types and use quality factors to prefer the new version, then cope with whatever comes back, saving a roundtrip.
The second benefit of this approach is that the same resource can be requested in different ways, if we have a use case where less data is needed the client can ask for a different representation and receive fewer fields.
@GetMapping(produces = “application/vnd.myco.myapp.foo-summary.v1+json”
ResponseEntity<SummaryFoo> getSummaryFoo() { ...
This technique can be applied for accepting variants for POST, PUT and PATCH requests although in this case it’s the consumes annotation that is used on the server side and client put the custom media type in the content-type header.
If this is a public application with a wide user base then you should register these with ICAN but for in house use you can skip that and so long as you have an agreed way of sharing the types and what they mean everything
Building this into the apis we create from the beginning will enable us to avoid the problems of versioning once the code is released into the wild (which we should think of as essentially when we merge into development). Additionally, custom media types help us document the system we are creating and allow us new design choices as we fully embrace the idea that each of our resources can have different representations.
“A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining extended relation names and/or hypertext-enabled mark-up for existing standard media types.”.
NB. This advice is aimed at those developing enterprise scale apis where arguably development velocity and ease of deployment are the major concerns. At internet scale performance and caching become more important and a different approach to versioning maybe more useful.
If you want to find out more here are a few links to a few resources that shaped this article.
https://www.django-rest-framework.org/api-guide/renderers/#designing-your-media-types
https://opensource.zalando.com/restful-api-guidelines/#114
https://blog.ploeh.dk/2012/04/24/VendorMediaTypesWiththeASP.NETWebAPI/
https://martinfowler.com/articles/enterpriseREST.html
https://www.bti360.com/rest-media-types/
https://ninenines.eu/docs/en/cowboy/2.6/guide/rest_principles/
https://restfulapi.net/content-negotiation/
https://dzone.com/articles/5-easy-to-spot-tells-that-your-rest-api-is-not-res
https://www.baeldung.com/spring-rest-custom-media-type
https://restfulapi.net - see the resource section
https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design - see the versioning section
https://www.thoughtworks.com/insights/blog/rest-api-design-resource-modeling
https://sites.google.com/site/restframework/content-negotiation