In my previous post, I discussed the need to adhere closely to the HTTP specifications. In a similar way, in this post I would like to discuss the need to standardize your resources and actions through vocabulary definitions.
Much of the work done while designing CRUD APIs is spent building the URI resource hierarchies and determining how to reveal behavioral capabilities through non-standard designs to consumers. There are many well-known and supported frameworks and technologies to support the CRUD API design pattern including OAS formerly Swagger, RAML, Apiary.io and more. However, these techniques all result in interfaces which are tightly coupled to their clients, are very brittle, and become increasingly difficult to maintain over time. Hypermedia APIs can alleviate many of the symptoms, but simply adding hypermedia to a brittle foundation (like Spring-HATEOAS) will not resolve these issues. The problem with all of these approaches is the static nature of the API definition, the contracts they publish or describe are not expected to change over time. If the contract is to change in any way, some external mechanism for handling the change is to be employed, and coordination or intervention is expected to resolve any disparities resulting from the change. In other words, the API provider makes changes and the all the consumers clients break.
The way to get around the fragility and brittle nature of the static binding is simple, don’t guarantee a static contract. Instead your design should aim to publish or subscribe to dynamic contracts which are malleable, robust, and capable of supporting constant interrogation. It is much less effort overall to define domain vocabularies which encapsulate the resource and message representations and behaviors which obviates the need for strict static contracts. There are a variety of ways to produce these vocabularies, including Schema.org and Application Level Profile Semantics (ALPS) among others. The suggested approach will vary based on the use case, however Schema.org definitions are tightly coupled to the HTTP transport making ALPS the better generic decision for a transport agnostic vocabulary definitions.
There are a few often cited perceived drawbacks to this approach, and they fall hand in hand with the standard arguments against hypermedia APIs. First, is the enhanced complexity of creating vocabularies makes the design process more difficult overall, not less. The second is the use of these published and discoverable semantics makes the consumption of the service a more complex and costly process.
The simple answer for the first perception is to understand the vocabulary definitions are not static. While it is good practice to attempt to account for the future the dynamic nature of the vocabulary definitions provides the opportunity to fix design mistakes and other changes. All too often voices from the semantic web community clamoring for the creation of perfect vocabularies are confused for proponents of hypermedia driven API design who simply advocate for designs to promote lower coupling. More importantly, the barrier to entry for contributing value in vocabulary design is not limited to the highly technical members of your team. Members of your team with domain expertise can play a much more direct role in the design of vocabularies, allowing more technical members to focus their effort on more technical tasks. Quite simply this perception is a fallacy, the design process should be no more complicated to define a vocabulary than to barter away on the URI structure of a CRUD API.
Discoverable semantics, late-binding definitions, or follow your nose APIs; regardless of how you want to refer to the concept there is truth to the notion that a hypermedia API is more complex to consume. Despite this admission the sky is not actually falling on the hypermedia concept. In fact this small complexity is one of the tradeoffs which allows the API and clients to be more loosely coupled. A vocabulary defined hypermedia API enhances the API design flexibility while increasing the clients’ longevity by strongly urging the client design to discover the API’s resources where the consumer can go next. Our first guideline has come back into play to provide us a solution to another difficult problem. The service does not need to be discovered on every single request to the API. In fact, all that needs to be done is to build a client which responds appropriately to correctly formatted cache control headers from the HTTP specification and we will greatly reduce the overhead of discovery. Additionally, by leveraging different HTTP status codes we can broadcast to consumers a temporary or permanent move of a resource. With these statuses the server is able to gently broadcast deprecation of resources providing a buffer period to allow clients and consumers time to adjust to the evolved API. This greatly reduces the stress and pressure on a designer to create a perfect API on the initial version by providing non-breaking ways to make changes over time.
Both of these common misconceptions about hypermedia APIs result from a fundamental misunderstanding of the benefits and difficulties of hypermedia. The burdens are overblown, the perceived risk is amplified, however a well-designed API should see a net reduction in complexity through its initial release and beyond.
By leveraging the documentation of your resources through vocabulary definition your design greatly reduces its fragility in the face of change, and your consumers can expect a much longer lifetime from their investments in clients.