As the title suggests, in Best Practices For HTTP API Evolvability, Benjamin Carlyle, set out to define priciples and practices for designing systems, that are built around HTTP API’s. Systems, that are extensible and can evolve over time. He starts out with pointing out the distinction between REST which is an architectural style vs. HTTP API which programmatic interface exposed via HTTP.
HTTP API is a programmer-oriented interface to a specific service, and is known by other names such as a RESTful service contract, resource-oriented architecture, or a URI Space.
I say closely related because most HTTP APIs do not comply with the uniform interface constraint in it's strictest sense, which would demand that the interface be "standard" […]
At the outset he identifies the different elements in API construction that determine the evolution of an API over time. In general, the API evolution involves designing around client and server compatibility; specifically backwards and fowards compatibile changes to the API. In increasing order of frequency-of-change:
- The generic semantics of methods used in the API, including exceptional conditions and other metadata
- The generic semantics of media types used in the API, including any and all schema information
- The set of URIs that make up the API, including specific semantics each generic method and generic media types used in the API
The least changing aspect of API evolution are the method semantics. The best practices described in the article are the recognition of forward and backward compatible changes and the application of Postel’s law that allows the service and client to evolve in a more tolerant fashion. He suggests the use of HTTP Error codes, whereever possible, to convey compatibility issues.
Best Practice 3: A new method name should be chosen for a method that is not forwards-compatible with any existing method - i.e. a new method name should be chosen if the new features of the method must be understood for the method to be processed correctly (must understand semantics)
Best Practice 4: Services should ignore headers they do not understand or the components of which they do not understand. Proxies should pass these headers on without modification or the components they do not understand without modification.
[…]
Best Practice 7: A new status should be assigned a status code within a numeric range that identifies a coarse-grained understanding of the condition that already exists
[…]
Best Practice 9: If a new status is a subset of an existing status other than 400 Bad Request or 500 Internal Server Error then refine the meaning of the existing status by adding information to response headers rather than assigning a new status code.
He points out the important distinction in the symmetric nature of interaction between clients and servers as far as media types go ...
[…] Unlike methods and status which are asymmetrical between client and server, media types are generally suitable to travel in either direction as the payload of a request or response. For this reason in this section we won't talk about client and server, but of sender and recipient.
... which form the basis of best practices identified in regards to media types
[…]
Best Practice 11: Validation of documents that might fail to meet Best Practice item 10 should only occur if the validation logic is written to the same version of the API as the sender of the document, or a later version of the API
Best Practice 12: If the new information can be added to the schema of an existing media type in alignment with the design objectives of that media type then it should so added
He advocates the use of Content-Types and Accept headers to manage the compatibility between sender and the recipient.
that HTTP APIs use to make backwards-incompatible media type schema changes. The new media type with the backwards-incompatible changes in its schema is requested by or supplied by new clients. The old media type continues to be requested by and supplied by old clients. It is necessary for recent media types to be supported on the client and server sides until all important corresponding implementations have upgraded to the current set of media types.
In his opinion changing the resource space, specifically uri’s, are a server centric concern and in general shouldn’t introduce compatibility issues if the clients are designed to be driven by the hypermedia constraint. He advocates the use of cookies to route the client requests to appropriate service instances/servers.
No longer are we dealing with a generic method or media type, but a specific URL with specific semantics when used with the various generic methods. […] I tend to take a very server-centric view in thinking of it a service contract that looks something like:
- GET /invoice/{invoice-id}, returns application/invoice+xml, Return the invoice denoted by invoice-id
- GET /invoice/{invoice-id}/paid, returns text/plain (xsd:bool syntax), Return the invoice paid status for the invoice denoted by invoice-id
- PUT /invoice/{invoice-id}/paid, accepts text/plain (xsd:bool syntax), Set the invoice paid status for the invoice denoted by invoice-id
Additionally he also identifies capabilities in clients that afford transitions and evolution in server side uri space as best practices.
Best Practice 19: Services should keep track of whether a client should be pinned to old servers or new servers during an upgrade using cookies, or a similar mechanism
Best Practice 20: Clients should follow redirection status responses from the server, even when they are not in response HEAD or GET requests [Caveat :RFC2616]
Best Practice 21: When redesigning a URL space, ensure that new URLs exist that have the same semantics as old URLs, and redirect from old to new.
The article provides the foundation principles that allow HTTP API systems to evolve over time. Be sure to check out the original article goes into this topic in depth.