Trying to design an API for external applications with foresight for change isn't easy, but a little thought up front can make life easier later on. I'm trying to establish a scheme that will support future changes while remaining backward compatible by leaving prior version handlers in place.
The primary concern on this article is to what pattern should be followed for all defined endpoints for a given product/company.
Base Scheme
Given a base URL template of https://rest.product.com/
I have devised that all services reside under /api
along with /auth
and other non-rest based endpoints such as /doc
. Therefore I can establish the base endpoints as follows:
https://rest.product.com/api/...
https://rest.product.com/auth/login
https://rest.product.com/auth/logout
https://rest.product.com/doc/...
Service Endpoints
Now for the endpoints themselves. Concern about POST
,GET
,DELETE
is not the primary objective of this article and is the concern on those actions themselves.
Endpoints can be broken down into namespaces and actions. Each action must also present itself in a way to support fundamental changes in return type or required parameters.
Taking a hypothetical chat service where registered users can send messages we may have the following endpoints:
https://rest.product.com/api/messages/list/{user}
https://rest.product.com/api/messages/send
Now to add version support for future API changes which may be breaking. We could either add the version signature after /api/
or after /messages/
. Given the send
endpoint we could then have the following for v1.
https://rest.product.com/api/v1/messages/send
https://rest.product.com/api/messages/v1/send
So my first question is, what is a recommended place for the version identifier?
Managing Controller Code
So now we have established we need to support prior versions we need to thus somehow handle code for each of the new versions which may deprecate over time. Assuming we are writing endpoints in Java we could manage this through packages.
package com.product.messages.v1;
public interface MessageController {
void send();
Message[] list();
}
This has the advantage that all code has been separated through namespaces where any breaking change would mean that a new copy of the service endpoints. The detriment of this is that all code needs to be copied and bug fixes wished to be applied to new and prior versions needs to be applied/tested for each copy.
Another approach is to create handlers for each endpoint.
package com.product.messages;
public class MessageServiceImpl {
public void send(String version) {
getMessageSender(version).send();
}
// Assume we have a List of senders in order of newest to oldest.
private MessageSender getMessageSender(String version) {
for (MessageSender s : senders) {
if (s.supportsVersion(version)) {
return s;
}
}
}
}
This now isolates versioning to each endpoint itself and makes bug fixes back port compatible by in most cases only needing to be applied once, but it does mean that we need to do a fair bit more work to each individual endpoint to support this.
So there's my second question "What's the best way to design REST service code to support prior versions."