I was talking with some colleagues a few days ago about APIs and the impact of a bad RESTful design. That’s why I decided to create this article to explain what an API needs to be considered RESTful.
Big word alert: REST
REST is an acronym for Representational State Transfer, which is a style of architecture based on a set of predefined principles that describe how networked resources are defined and addressed.
Let’s take a look at a typical scenario involving a web service for managing a user profile. Here are some example endpoints for doing basic create, read, update, and delete (CRUD) operations on a profile, returning the results in Extensible Markup Language (XML) format:
/getAllProfiles /getProfile?id=2 /createProfile /deleteProfile?id=4 /updateProfile?name=Edgar
These endpoints don’t look too harmful. The first two endpoints,
/getProfile?id=2, get all profiles and get a specific profile with an ID of 2, respectively. The
/createProfile endpoint is responsible for creating a new profile, and as you might have guessed by now, the last two endpoints,
/updateProfile?name=Edgar, delete and update a specific profile accordingly.
After some time in production, the business requested that more features be added, like the ability to retrieve additional friend information with a specific profile response as well as the capability to search for profiles by name. Typically, developers tend to just implement these capabilities in a quick and dirty fashion by adding two more endpoints to the collection, resulting in Version 2 of the service looking like the following:
/getAllProfiles /getProfile?id=2 /getProfileWithFriends?id=2 /searchProfileByName?name=Kelvin /createProfile /deleteProfile?id=4 /updateProfile?name=Oscar
The additional endpoints may meet the requested business requirements but start to make the code very redundant by having the same type of information be served with slightly different aspects of it.
For Version 3 of the service, it is further requested that it support JSON responses on some of the current functionality in order for it to be “RESTful.” Keeping to the consistency of naming conventions and to prevent breaking changes (which can be a good thing), the developers might simply add more endpoints to the collection:
/getAllProfiles /getAllProfilesJson /getProfile?id=2 /getProfileJson?id=2 /getProfileWithFriends?id=2 /getProfileWithFriendsJson?id=2 /searchProfileByName?name=Alexis /searchProfileByNameJson?name=Alexis /createProfile /deleteProfile?id=4 /updateProfile?name=JeanCarlos
As you can see, by just adding support for an additional output format, you can basically multiply the read operations. Going forward with this pattern would be a recipe for disaster, and one can imagine what the impact would be, given another simple request by the business.
Unlike apps, the general end users of our applications are not typical business or consumer users, but rather developers of applications. It is crucial that you provide an excellent developer experience when designing APIs.
In the previous example scenario, the web services tended to lean more toward a remote procedure call (RPC)–style web service rather than a RESTful service. Having an RPC-style web service is not wrong, but it is important to not confuse the characteristics of REST and RPC.
In an RPC world, endpoints are mere functions that get triggered remotely, whereas in a RESTful world endpoints are entities, also known as resources. Properly designing an API is hard because requirements tend to change and we need to adapt to the business needs of the day. Implementing patterns like REST will improve the experience of our web services by making them less redundant, more scalable, and more maintainable.
Some of the most important concerns that a RESTful architecture affects include performance, scalability, simplicity, interoperability, communication visibility, component portability, and reliability. These properties are encapsulated by six principles, which are defined by Fielding as the constraints guiding a RESTful system design.
The Client-Server constraint enforces the proper separation of concerns between the UI/consumer and the back-end, which mostly contains the business-logic and data-storage implementations.
Often combined with the Client-Server constraint, the Layered System constraint dictates that layers should be organized hierarchically, restricting the use of a service to the layers directly beneath and above it. Orchestrating components in layers drastically improves reusability, making them more modular.
Building on the Client-Server style is the Stateless constraint. Communication between the client and the server needs to be stateless, meaning that a request should contain all the information necessary for the server to understand and to create context.
The key feature that associates a system with REST is a Uniform Interface. This constraint consists of four essential parts, which are resource identification, resource manipulation, self-describing responses, and state management. These architectural elements are implemented directly through URIs, HTTP verbs, media types, and Hypermedia as the Engine of Application State (HATEOAS), respectively.
The Cache constraint derives from the Stateless constraint and requires that responses coming from the server are explicitly labeled as cacheable or non-cacheable, regardless if they are explicitly or implicitly defined. Responses that are cached allow clients to reuse them later when making similar requests, thus improving speed and latency.
The final and optional constraint is Code on Demand, which allows a client to access specific resources from the server without knowledge of how to process them.
Apply these six constraints to your API services, then and only then will they become truly RESTful.