of the API is as old as programming itself. Documents defining a series of
methods, or services with a given input and output are ubiquitous in
programming. Today, APIs are the defining mechanism for interacting with services
on the web: whether you’re logging in to Google, tweeting on Twitter, finding
Facebook friends, this can all be done through the services that are defined in
their respective APIs. How is it that these and other APIs come to be? How is
it that services can be altered, updated, or removed altogether? At DataXu there was much discussion about how to best implement our API,
and at PlanIt I took some of the results of those discussions and implemented
them in the form of a RESTful API written in Ruby on Rails.
There are many good reasons to create an API for your users and/or customers. Defining a set of services that can be relied upon to take some input and return some output, unaltered, acts as a virtual contract between consumer and server. Developing to this standard allows the creation of clients based on documentation alone. This means that a live endpoint is unnecessary to development and testing. Tests can be written using stubbed calls, those that are short-circuited, returning a static value instead of a dynamic one. In the context of PlanIt, a mobile app in which the mobile side is written in Xamarin, all of the calls out to the API are done in a single place, whether developing for Android or iPhone.
The contractual nature of the API means it can be trusted to be unchanging for the current version of the API, mitigating the possibility of introducing breaking changes to existing services. At DataXu, the decision was reached to make only non-breaking changes in a given version of an API and the next version (for this discussion we'll start at V0). At PlanIt I chose to include the addition of features (e.g., additional inputs to a service) assuming the old ones remained unbroken. In the next version of an API (V1) additional services can be defined and services that will be changed to a greater extent can be marked as deprecated, but these services will continue to be officially supported in V1. In the version after (V2) we can implement the breaking changes we earmarked in V1. To demonstrate, imagine what would happen if Facebook released a new version of the API with a new definition of their "friend" service, or if Twitter decided to change the definition of their “tweet” services; overnight many thousands of applications would be broken.
especially important in the case of multiple clients; when only a single
consumer will implement the calls to the API (e.g. a website) we can generally
expect the consumer and server to remain synchronized. In the case of many
consumers, e.g., applications that implement the calls to the Facebook API, we
can expect that some consumers will not have the ability or desire to upgrade
as soon as the latest version of the API is released. In the context of mobile users,
they have the power to choose when to update their applications, which will
leave portions of your user-base behind by at least one version. The mobile
developers may for whatever reason choose or be required to leave one of the
platforms behind as well; a good example is that the iOS version of your
application will require approval, and therefore may lag behind the Android
version by some days or even weeks. At the opposite end of the spectrum there
are consumers that always want to use the latest API; these tend to be the
"primary" clients of the API, or those that are implemented by the
same organization as the API itself. Logic dictates that the newest version of
the API should be the default if none is specified.
There's an on-going debate surrounding how a client should specify a version of the API to the server, which can be summarized as being in one of two camps. The first camp headers are heavily utilized; the extreme version of this is complete HATEOAS implementation, in which all relevant actions to the one being taken are returned as hypermedia with each call. In the second camp versions are specified in the URLs themselves (e.g., https://host/api/v0/service), as in service oriented architecture. The major difference here is that by using HATEOAS, the consumer will never need to know about changes to service endpoints, but rather by knowing where to look for the endpoint in the associated hypermedia it can simply adapt to those changes on the fly. My personal preference is to use the URL in the standard SOA style, in the belief that clients should generally be explicit in their choice of version.
power and flexibility must come great documentation. The PlanIt API
documentation describes more than 40 calls, some of which are already marked as
deprecated in V1. I'm in favor of having a document devoted to each API that
also describes the delta between the previous version and the current one,
linking and highlighting as deprecated where appropriate. Each service in the
API is ideally separated by category (in my case representing controllers),
with the method (CRUD is ideal for RESTful APIs), the expected inputs (and
input formats), the expected outputs (with output formats), a description, and
any relevant notes.
The choices you make for design and implementation of your API is disconnected from your choice of technology. Crafting a contract through your API creates trust, and enforcing that with best practices regarding versioning creates stability. By following these tips, you can create a maintainable API and simplify implementation and updates for API clients.