Increase API Stability by Binding to Protocols and Formats, not URLs and Objects
This post is an excerpt from Mike Amundsen's book "RESTful Web API Patterns and Practices Cookbook" from O'Reilly Media.
An important element to building successful hypermedia-enabled client applications is the work of binding the client to responses. Although programmers might not think about it, whenever we write an API consumer app, we’re creating a binding between producers (services) and consumers (clients).
Whatever we use as our "binding agent" is the thing that both clients and servers share. The most effective bindings are the ones that rarely, if ever, change over time. The binding we’re talking about here is the actual expression of the “shared understanding” between clients and services.
URLs and Objects
The most common binding targets for web APIs today are URLs (/persons/123
) and objects:
{ "id" :"q1w2e3r4",
{ "person" : {
"givenName" : "Mork",
"familyName" : "Morkelson",
"email" : "mork@example.org"
}
}
}
The most commonly-used API definition formats (SOAP, OpenAPI, AsyncAPI) all use URLs and objects as their binding agents. Also, lots of frameworks and generators use these binding agents (URLs and objects) in API definition documents to automatically generate static code for a client application that will work with a target service. This turns out to be a great way to quickly create and deploy a working API client application.
It also turns out generated application codebases are hard to reuse and easy to break. For example, any changes in an API’s storage objects will break the API consumer applications object bindings. Also, even if there is an identical service (one that has the same interface) running at a different URL, the generated client is not likely to successfully interact with that new service since the URLs are not the same as the old service.
Object-Bound Clients
Client applications that are “bound” to the object schema and URLs of the web service have source code that reflect this binding to the URLs and schema. The shape of the objects and the permitted actions on those objects are “bound into” the source code directly (see below).
Note that, in the above source code, you can see each action. In the expanded source code listing, you’d also be able to see the shape of each object passed back and forth. This means any changes to the actions or objects (or the URLs associated with them) will require changes to the application-level source code, too. That means, as the service interface grows (new operations, new properties), the client source code must grow, too.
Protocols and Formats
A better binding target model for web APIs is 1) the protocol (e.g., HTTP, MQTT, etc.) and 2) the message format (e.g., HTML, Collection+JSON, etc.). These are much more stable over time than URLs and objects. They are, in fact, the higher abstraction of each. That is to say, protocol binding is the higher abstraction of URL binding, and message format (or media types on the web) bindings are the higher abstraction of object schema bindings.
For example, using a message format as your binding agent means API consumers are bound to a message type instead of an object type. That means you can code clients to leverage formats to express (and discover) data properties:
<html>
<body>
<ul id="q1w2e3r5" class="person">
<li name="givenName">Mork</li>
<li name="familyName">Morkelson</li>
<li name="email">mork@example.org</li>
</ul>
</body>
</html>
Note that adding another property to the representation above (e.g. <li name="telephone" />
) does not automatically break the existing client application. This is because the client is "bound" to HTML, not "Person". The contents (Person) of the message (HTML) can change without causing the client application to fail.
Binding to a protocol means you can use formatted messages to share resources without specifying the exact location ahead of time:
<link href="..." rel="person"/>
<img src="..." name="logo" />
Note that the contents of the href and src properties can be filled in at runtime. The client doesn’t need to know them ahead of time. And, even more to the point, the location value can change over time without breaking the client application.
Protocol-Bound Clients
The code for client applications that focus on the formats and protocols looks very different than the code for object-bound clients (see below).
The source code above is more abstract then the code seen earlier. Here you can see that there is code for handling the response (processResponse
) and code for handling the protocol (MakeRequest
). If you expanded the source code, you’d be able to see that the showResponse
method does the work of rendering the message data as well as any action elements (links and forms) that were part of the service response.
It is important to point out that changes to the data properties (e.g. adding a telephone
data property) or actions (e.g. adding a new updateTask
operation) would not require any changes to this code. In fact, as the service interface grows over time, the source code of the client application stays the same.
Stability is the Pay-Off
Because they are more universal and less likely to change, protocol and format make for good binding agents on the web. For example, if a client application is bound to a format (like Collection+JSON), then that client application can be successfully used with any service (at any URL) that supports Collection+JSON bindings. Of course, that is what HTML web browsers have been doing for more than 30 years. That is part of the reason Web browsers have been so successful as client applications over the last few decades.
For web-based programming, it pays off (in the long run) to focus on stable binding agents when designing and building service interfaces (APIs) and service consumers (client applications). The challenge most of us face is that there are very few handy tools and document formats that promotes this kind of stability. That increases the short-term cost of selecting more reliable binding agents for our web APIs.