As I have been ramping up my evangelizing of hypermedia APIs through various channels, I have noticed a common argument being thrown out against hypermedia. It’s taken a few forms, but the crux of the argument has been something like ‘hypermedia APIs just move the hard coded binding from URLs to link names but they don’t actually solve any problems’. In most of these discussions, the flexibility and maintainability benefits of hypermedia APIs have already been brushed aside as unimportant and irrelevant. Now I clearly have some things to say about those points, but as the people I’ve had this recurring conversation with had little interest in those properties I’ve decided to instead address the direct and immediate benefits of a hypermedia over CRUD APIs. Namely, these are the greatly enhanced usability and proper hiding of service implementation details. To accomplish this, I have put together a portion of an API description in the crud pattern which is intentionally not optimized to exaggerate the point I’m making, namely that crud APIs are less usable and require the consumer to know too much about the internal implementation details of a service to consume it. I will also list and describe the usability and proper hiding of implementation details of a hypermedia API using my hypermedia API design guidelines.
Ridiculous requirements with a CRUD API
A movie theater has created a CRUD API to managage their lighting system in order to provide optimal viewing experience for all patrons at the lowest cost for the theater. The following API is used to manage the lighting on an individual seat basis, to provide the best lighting conditions for each screen at the lowest costs.
/screen/{screen_id}/view/{view_id}/type/{type_id}/seat/{seat_id}/lightsource/{source_id}/natural/{mirror_id}/status
/screen/{screen_id}/view/{view_id}/type/{type_id}/seat/{seat_id}/lightsource/{source_id}/artificial/{light_id}/status
/screen/{screen_id}/view/{view_id}/type/{type_id}/seat/{seat_id}/lightsource/{source_id}/natural/{mirror_id}/status
/screen/{screen_id}/view/{view_id}/type/{type_id}/seat/{seat_id}/lightsource/{source_id}/artificial/{light_id}/status
/screen/{screen_id}/orientation
/weather/current
/weather/current/sun
/calendar/{day_id}/day/light-concentration-index?longitude={longitude},latitude={latitude}
/electricity/sources
/electricity/{source_id}/cost
/electricity/current_distribution
/usersuppliedcalculation
/usersuppliedcalculation/operations
/usersuppliedcalculation/types
/usersuppliedcalculation/{user-supplied-calculation_id}/calculate
/usersuppliedcalculation/{user-supplied-calculation_id}/result/{result_id}
Documentation
Screens have views where you can see them from, the view will have a type, and those types will have seats. Those seats will have a source of light, which is natural or artificial. In order to best optimize the viewing experience for the members of the audience, over the course of the day the lighting requirements will change seat to seat as the cost of electricity and the availability of sunlight fluctuate. It is also extremely important to keep track of the natural lighting conditions in order to balance the cost of providing artificial light with power provided by utilities with power supplied from the on-site solar installation.
During normal operating conditions at no point should the cost savings of providing natural light reduce the optimality of viewing by more than 5%. Any optimality below 80% should be immediately disregarded, unless the cost savings exceeds 90%. Views from a balcony will increase the optimality by 10% for natural light, as it requires fewer redirections on mirrors. The orientation of the screen will decrease the optimality of natural light by 50% at 0 degrees from north, and at 180 degrees from north there will be no reduction. There is an exponential growth curve of the decreased optimality from true south to true north. However, the position of the sun relative to the screen will offset some or all of this decreased optimality in logarithmic fashion as the relative position of the sun approaches 180 degrees. This factor will be then used in conjunction with sun elevation which will completely offset the orientation degradation at greater than 70% of maximum annual elevation and reduce the factor linearly as the offset approaches 50%, at which point the relative orientation factor is entirely eliminated.
The service will internally validate any request supplied and reject any status changes which do not adhere to these constraints. The switching mechanism has a finite life and it is critical to reduce the number of attempted switches. The service is metered to protect the switching mechanism to 1 attempted switch per hour per seat. Additionally, only 10 switches may be switched within a 60 second rolling window. If a seat is in the wrong state it will cause extra wear on the switching mechanism at the damage rate of 10 switches per hour that it is in the wrong switch state.
Resource Representations
weather/current
+++haze-rate – a measure of the transparency of the air.
+++overcast-percentage – a measure of the overcast percentage.
weather/current/sun
+++elevation – elevation of the angle of the top of the sun above the horizon.
+++orientation – the degrees from true north of 0 to the center of the sun.
light-concentration-index
+++value – the index value from peak of the intensity of the sun on this day for the given logitude and latitude.
electricity/sources
+++source_id – id.
+++name – name.
cost
+++source_id – source_id for this type of electricity.
+++value – cost of the electricity per unit.
+++unit – the unit of count.
current_distribution
+++sources – source name percentage value pair for current electric use. e.g solar:50%, grid 50%.
…
many other objects
…
In order to facilitate the optimization of these resources we have supplied a system for you to create calculations to simplify the process. The format for the calculation parameter is as follows: (? parameter_name : operation_id : parameter_name )?+ . Additionally, any open parenthesis is required to be closed, or it will fail validation. Names of all parameters must be unique.
usersuppliedcalculation
+++type – the numeric type to be used in this calculation, ex integer, float32, float64… etc.
+++parameters – the name of the parameters used in this calculation.
+++calculation – the calculation formula to be used in this calculation.
calculate
+++parameter_values – the name value pairs for the parameters to be used to create this calculation result. A calculation result_id can be used as a value in the format result_id={result_id}.
Most likely use case:
The consumer will obtain this document from some out of band process, and will begin to read it. First creating objects to contain the represented data within the service as described in the documentation, which is both shown and not shown in this example. Afterword the user will begin to exercise their client by retrieving data. After noting the physical ramifications of an incorrectly switched light source, they will go about creating local logic in order to perform the prescribed data calculations to accurately manage the lighting.
Depending upon how thoroughly the reader had gone through the entire document to fully understand the system before writing any code, the user will most likely stumble upon the helper endpoint which allows them to declare calculations to be defined and run by the service. This may or may not result in a rewrite of some of the functionality to leverage the service provided functionality to reduce traffic.
The clients are very tightly coupled to the servers’ representation of the resources in the current hierarchy. Any effort to simplify the service by changing this URI pattern will result in a broken client which needs to be completely rechecked, against the new version of this documentation which may have changed many of the hierarchies and relationships between resources. Additionally, the use of any versioning, especially in the case of a breaking change in the above hierarchies will require a complete regression testing of the consumer code in order to verify the service changes do not cause undue wear on the switching mechanisms.
This service requires the consumer to be extremely well versed in the internal workings of the switching mechanisms in order to avoid extremely undesirable outcomes. It also requires the consumer to duplicate logic from the service creator in order to prevent bad things from happening. The consumer is forced to take on the responsibility the service designer has decided to ignore.
Ridiculous requirements with Hypermedia
A movie theater has created a semantically driven hypermedia API to manage their lighting system in order to provide optimal viewing experience for all patrons at the lowest cost for the theater. The following profile is used to orchestrate the service to manage the lighting on an individual seat basis, to provide the best lighting conditions for each screen at the lowest costs.
screen
+++properties …
+++affordances …
+++relationships …
+++goals …
++++++optimize-screen-lighting
view
+++properties …
+++affordances …
+++relationships …
+++goals …
seat
+++properties …
+++affordances …
+++optimize-lighting – this will calculate the optimal lighting source and power source for a given time optimize-at – date time for the service to optimize the seating
+++relationships …
+++goals …
lightsource
+++properties …
++++++type – natural or artificial
+++affordances …
+++relationships …
+++goals …
weather
+++properties …
+++affordances …
+++relationships …
+++goals …
calendar
+++properties …
+++affordances …
+++relationships …
+++goals …
electricity
+++properties …
+++affordances …
+++relationships …
+++goals …
usersuppliedcalculation
+++properties …
+++affordances …
+++relationships …
+++goals …
Documentation
There is none. The service’s semantic profile contains the human readable descriptions of the resources, their properties, and their affordances which are freely discovered by requesting the root resource “/” of the API. These documents serve as both the services bounded domain and human readable documentation. The profile is defined by domain semantics and not by technical terminology, the profile semantics are then separately bound to protocol specific definitions to facilitate the implementation, but these bindings are opaque to the human consumer.
Most likely use case:
The consumer will be given the root URL of the service; with a hypermedia aware client the user will browse the available resources from root “/” where the home document will be served. Among the contents of this document will be links to the profile which will contextualize the resources returned in the document, as well as begin to populate the local cache-controlled copy of the profile to reduce redundant and unnecessary calls for meta-data. The user can see all of the resources available to them, where all resources are likely root resources as the hypermedia service has been appropriately flattened and complex representations are composed by link relations.
The user notes the goal ‘optimize-screen-lighting’, and that this sounds like a requirement of current effort, and navigates to the link provided by the home document for the root of the ‘screen’ resources.
The collection of ‘screens’ is returned, the service provides hypermedia metadata about the current affordances and relationships of each ‘screen’, which are rendered as links and forms with helpful descriptions to contextualize the information. The user notices the screen has the goal noted earlier, navigates to the link within the goal and the service then curates the experience guiding the user through all interaction necessary to accomplish the goal. However, the interaction necessary is very little, as the goal will supply the user with a link to the seats resource collection, already filtered for seats related to this screen. Each seat in this collection will have a related link named optimize-lighting.
The client will have already cached the description and documentation which cautions the caller from following this link more than 1 time an hour, and also provides the appropriate message structure to send to perform the optimize-lighting action.
The user will then be able to write this simple procedure within their more advanced client, which would simply follow the semantic links of interest at the root through optimizing every seat. This client would be able to skip any of the discovery steps which it has a valid cache value for, and if there is a question of validity or the cache period has expired the client can validate the ETag of the response through a HEAD call. In this way the client is utilizing HTTP caching tiers between itself and the service to enhance the apparent performance of the service, and preventing excess load on the application and better response time to the client. The client can then simply follow the final steps of the hypermedia discovery process in order to achieve its goal, in a much more streamlined, efficient, and dynamic manner.
At no point in this process is the user required to know anything about the technical requirements or implementation details of the service. By leveraging semantic hypermedia, the use of the service has become as intuitive as possible within the bounded domain of the profile definition, which should be written to maximize human readability and interoperability. Once understood by a human, a machine can easily follow the same steps. Ideally this process would be bound to user interface constructs to create generic clients with interface bindings dynamically responding to stateful hypermedia messages.
Disclaimers, conclusions, and more words
As hinted at in the very beginning of this novella of a blog entry, I do understand this example is almost over the top exaggerating the negatives of the CRUD pattern. I am not writing this with the intent of removing the CRUD pattern from any particular toolbox. I do hope to demonstrate to more API designers, developers, and most importantly consumers there really is a better way to do this API thing. The tooling for the CRUD pattern is fantastic, so good in fact that even consumers are happy to take on many burdens of the service provider in order to gain the ability for rapid prototyping and code generation.
I’ll pose some questions though, what if we could remove the entire requirements for rapid code generation to stand up a prototype? What if all we needed to do was to create the domain profile, and we could have a mock service running immediately? Would that be enough to start the trend of demanding more usable APIs?
I hope so, because I believe it is time we stop writing snowflake services on the internet, justifying them claiming our use-case is somehow special. Somehow we have convinced ourselves we don’t need to make our services easy to use. I can’t see how in the hysteria of speed to market rush, we all seem to have forgotten we need to build good products first before anything can go to market.
Perhaps I’ve been spending so much time looking at the oasis of the future that this example felt so ridiculous to me. I showed this example to a colleague today, prefacing this as a ridiculous example of a CRUD API, his first reaction “I’m never using this service, but why do you say it’s ridiculous?”. I think we can do better; I think we should all do better.