Each of these constraints enforce a design decision for the service that is to be followed. If it is not followed, the service can't be denoted as RESTful. Let's discuss these constraints one by one.
Client-server architecture
The client or the consumer of the service should not worry about how the server processes the data and stores it in the database. Similarly, the server does not need to depend on the client's implementation, especially the UI.
Think of an internet of things device or sensor that doesn't have much of a UI. However, it interacts with the server to store data using APIs, which are programmed to be fired on specific events. Suppose you are using an IoT device that alerts you when your car runs out of petrol. At the time of a petrol shortage detection by the sensor in the IoT device, it calls the configured API, which then finally sends an alert to the owner.
What that means is that the client and server are not one entity and each can live without the other. They can be designed and evolved independently. Now you might ask, How can a client work without knowing about the server's architecture, and vice versa?Well, that is what these constraints are meant for. The service, when interacted with by the clients, provides enough information about its nature: how to consume it, and what operations you can carry out using it.
As we go ahead in this section, you will realize that there is absolutely no relation between the client and the server, and they can be completely decoupled if they adhere to all these constraints perfectly.
The term stateless means that the state in which the application remains for a particular time may not persist to the next moment. A RESTful service does not maintain the application's state, and thus it is stateless.
A request in a RESTful service does not depend on a past request. The service treats each request independently. On the other hand, a stateful service needs to record the application's current state when the request is performed so that it can act as required for the next request.
Moreover, because of an absence of these complications, stateless services become very easy to host. As we need not worry about the state of the application, it becomes easy to implement, and maintenance becomes smooth.
To avoid generating the same data with every request, there is a technique called caching that is used to store the data either on the client's or the server's side. This cached data may be used for further reference whenever it is required.
When using caching, it is important that you manage it properly. The reason for this is simple. We are storing data that won't be replaced by fresh data from the server. While this is an advantage that increases the performance of the service, at the same time, if we are not careful as to what to cache and configure during its lifetime, we might end up seeing outdated data. For example, suppose we are showing the live price of gold on our website and we cached this figure. The next time the price changes, it won't be reflected unless we expire the cache that was previously stored.
Let's look at the different kinds of HTTP headers and how to configure caches:
The configuration of the preceding five headers depends upon the nature of the service. Take the example of the service that provides the live price of gold—ideally, it would have the cache age limit as low as possible, or even have caching turned off, because users should see the latest results every time they refer to the site.
However, a site that contains many images would hardly change or update them. In that case, the cache can be configured to store them for a longer duration.
These header values are consulted in accordance with the cache-control header to check whether the cached results are still valid or not.
The following are the most common values for the cache-control header:
When we encounter the word interface, the first thing that comes to our mind is decoupling. We create interfaces to have loosely coupled architecture, and the same type of architecture is seen in the case of RESTful.
While implementing REST, we use the same concept to decouple the client from the implementation of the REST service. However, to implement such a decoupling between the client and the service, standards are defined that every RESTful service supports.
Note the word standard in the preceding line. We have so many services in the world and, obviously, the consumers outnumber the services. As a result, we have to follow some rules while designing the services because every client should understand the service easily without any hassle.
REST is defined by four interface constraints:
- Identification of resources: A URI is used to identify a resource. The resource is a web document.
- Manipulation of resources through representations: When a client has a given resource—along with any metadata—they should have enough information to either modify or delete the resource. So, for example,
GET
means that you want to retrieve data about the URI-identified resource. You can describe an operation with an HTTP
method and a URI. - Self-descriptive messages: The messages passed should contain enough information about the data to be understood and processed for further operations. MIME types are used for this purpose.
- Hypermedia as the engine of the application state (HATEOAS): The representation returned from the service should contain all the future actions as links. It is the same as visiting a website in which you find different hyperlinks providing you with the different types of available operations.
HTTP 1.1 provides a set of methods, called verbs. Implementing these verbs in our services would mark them as standardized. The important verbs are as follows:
The preceding table is quite self-explanatory, except the Method Type column. Let me clarify this.
A safe operation when performed on the service does not have any effect on the original value of the resource. As the GET
, OPTIONS
, and HEAD
verbs only retrieve or read the resource-related stuff and does not update that, they are safe.
An idempotent (can be repeated) operation when performed gives the same result no matter how many times we perform it. For example, when you make a DELETE
or PUT
operation, you are actually operating on a particular resource, and the operation can be repeated with no issues.
Note
POST
versus PUT
: This is a very common topic of discussion on the internet, and one that is very easy to understand. Both POST
andPUT
can be used to insert or update a resource. However, POST
is nonidempotent, meaning that it isn't repeatable. The reason is that each time you call using POST
, it will create a new resource if you don't provide the exact URI of the resource. The next time you use POST
, it will again create a new resource. However, inPUT
, it will first validate the existence of the resource. If it exists, it will update it; otherwise, it will create it.
Among all the available methods, GET
is the most popular one, as it is used to fetch the resource.
The HEAD
method will only return the response headers with an empty body. This is mostly only required when we don't need the whole representation of the resource.
The OPTIONS
method is used to get a list of the allowed or available operations on the resource.
Consider the following request:
OPTIONS http://packtservice.com/Authors/1 HTTP/1.1 HOST: packtservice
If the request is authorized and authenticated, it might return something like the following:
200 OK Allow: HEAD, GET, PUT
The response is actually saying that the service can be called using only all these methods.
Make sure you use the HTTP methods according to their specification. If you design the service to allow GET
, but perform a delete operation inside that, then clients will get confused. As they try to GET
something, it will actually delete the resource, which is weird.
The following is a request that is made with GET
, but it actually deletes the resource inside the server (just imagine):
GET http://packtservice.com/DeleteAuthor/1 HTTP/1.1 HOST: packtservice
The preceding request might work and delete the resource, but this is not regarded as a RESTful design. The recommended operation would be to use DELETE
method to delete a resource like the following:
DELETE http://packtservice.com/Authors/1 HTTP/1.1 HOST: packtservice
POST versus PUT explained
The use of POST
and PUT
can be summarized in the following two points:
PUT
is idempotent—it can be repeated, and yields the same result every time. If the resource does not exist, it will create it; otherwise, it will update it.POST
is nonidempotent—multiple resources will be created if it is called more than once.
The preceding contrast between these verbs is just a general difference. However, there is a very important and significant difference. When using PUT
, specifying the complete URI of the resource is necessary. Otherwise, it won't work. For example, the following won't work as it does not specify the exact URI of the author, which can be done by specifying an ID:
PUT http://packtservice.com/Authors/
To fix this, you can send an ID with this URI using something like the following:
PUT http://packtservice.com/Authors/19
created/updated.
This means that the author with the ID 19
will be processed, but if that does not exist, it will be created first. Subsequent requests with this URI will be considered as requests to modify the author resource with an ID of 19
.
On the other hand, if we do the same with a POST
request like the following, it will create a new author resource with the posted data:
POST http://packtservice.com/Authors/
Interestingly, if you repeat this, you will be responsible for duplicate records with the same data. That is why it is nonidempotent in nature.
Note the following request with POST
with an ID. Unlike PUT
, POST
won't consider this for a new resource, if that is does not exist. It will always be treated as an update request:
POST http://packtservice.com/Authors/19
updated.
The following are the main points to focus on in this section:
PUT
creates or updates one resource, as long as you are calling the same URIPUT
and POST
behave the same, if the resource already existsPOST
, without an ID, will create a resource each time it is fired
Most modern applications are designed using multiple layers, and the same is expected from a RESTful service. In a layered system, each layer is restricted to only seeing or knowing the next layer in the hierarchy.
Having a layered architecture helps improve the code's readability, hides complexities, and improves the code's maintainability. Imagine that you have one layer and everything takes place in it, from authentication to database operations. This is absolutely not recommended, as the primary components, such as authentications, business logic, and database operations, are not separated out.
Thus, this constraint is expected from a RESTful service, and no client can actually say that it is connected to the final layer.