Home » Idempotence in HTTP Methods – Defined with CRUD Examples

Idempotence in HTTP Methods – Defined with CRUD Examples

by Icecream
0 comment

Idempotence refers to a program’s means to take care of a specific consequence even after repeated actions.

For instance, as an instance you have got a button that solely opens a door when pressed. This button doesn’t have the flexibility to shut the door, so it stays open even when it is pressed repeatedly. It merely stays within the state it was modified to by the primary press.

This identical logic applies to HTTP strategies which are idempotent. Operating on idempotent HTTP strategies repeatedly will not have any extra impact past the preliminary execution.

Understanding idempotence is essential for sustaining the consistency of HTTP strategies and API design. Idempotence has a big impression on API design, because it influences how API endpoints ought to behave when processing requests from shoppers.

In this tutorial, I’ll clarify the idea of idempotence and the position it performs in constructing sturdy and useful APIs. You’ll additionally find out about what secure strategies are, how they relate to idempotence, and find out how to implement idempotency in non-idempotent strategies.

Prerequisites

Before understanding and implementing idempotence in API design, it is important to have a strong basis within the following areas:

  • RESTful Principles
  • Fundamentals of HTTP strategies
  • API Development
  • HTTP Status codes
  • Basics of Web improvement.

Idempotence Example  

Let’s begin off with an instance of idempotence in motion. We’ll create a operate that makes use of the DELETE technique to delete information from an internet web page:


from flask import Flask, jsonify, abort

app = Flask(__name__)

web_page_data = [
   {"id": 1, "content": "Row 1 data"},
   {"id": 2, "content": "Row 2 data"},
   # Add more rows as needed
]

@app.route('/delete_row/<int:row_id>', strategies=['DELETE'])
def delete_row(row_id):
   # Find the row to delete
   row_to_delete = subsequent((row for row in web_page_data if row["id"] == row_id), None)
   
   if row_to_delete:
       # Simulate deletion
       web_page_data.take away(row_to_delete)
       return jsonify({"message": f"Row {row_id} deleted efficiently."}), 200
   else:
       abort(404, description=f"Row {row_id} not discovered.")

if __name__ == '__main__':
   app.run(debug=True)

This operate is predicted to delete the rows chosen by the person. Now due to the idempotent nature of the DELETE technique, the information shall be deleted as soon as, even when referred to as repeatedly. But subsequent calls will return a 404 error for the reason that information has already been deleted by the primary name.  

Let’s take a look at one other instance with the GET technique. The GET technique is used to retrieve information from a useful resource. Let’s create a operate that makes use of the GET technique to retrieve a username:

import requests

def get_username():
    url="https://api.instance.com/get_username"
    strive:
        response = requests.get(url)
        if response.status_code == 200:
            return response.json()['username']
        else:
            return None
    besides requests.RequestException as e:
        print(f"Error occurred: {e}")
        return None

# Usage
username = get_username()
if username:
    print(f"The username is: {username}")
else:
    print("Failed to retrieve the username.")

In this instance, we outline the get_username() operate, which sends a GET request to the API endpoint to retrieve the username. If the request is profitable, we extract the username from the JSON response and return it. But if any error happens through the request, we deal with it and return None.

Now the idempotent nature of the GET technique ensures that even when you name get_username() a number of instances, the identical username shall be fetched from the API every time. The consequence will all the time be the identical which is to fetch the username from the useful resource.

Idempotent vs. Non-Idempotent HTTP Methods:

HTTP strategies play essential roles in figuring out how information is fetched, modified, or created when interacting with APIs. And Idempotency is likely one of the essential ideas that influences information consistency and reliability within the strategies used .

Here’s a breakdown of the completely different strategies primarily based on their idempotency.

Idempotent strategies:

  • GET
  • HEAD
  • PUT
  • DELETE
  • OPTIONS
  • TRACE

Non-idempotent strategies:

Safe Methods

In our earlier instance, we used the GET technique to retrieve a username and this had no facet impact on the server. This is as a result of it’s a secure technique.

A secure technique is a sort of technique that doesn’t modify the server’s state or the useful resource being accessed. In different phrases, they carry out read-only operations used to retrieve information or for useful resource illustration.

When you make a request utilizing a secure technique, the server doesn’t carry out any operations that modify the useful resource’s state. Like in our earlier instance, we retrieved the username from the webpage which is the useful resource with out altering something within the server.

All secure strategies are mechanically idempotent, however not all idempotent strategies are secure. This is as a result of whereas idempotent strategies produce constant outcomes when referred to as repeatedly, a few of them should still modify the server’s state or the useful resource being accessed.

Like in our first instance, the DELETE technique is idempotent, as a result of deleting a useful resource a number of instances can have the identical impact. But it is not secure, because it modifications the server’s state by eradicating the useful resource.

Here’s a classification of HTTP strategies primarily based on their secure standing:

Safe strategies:

Unsafe strategies:

Why is POST not idempotent?

POST is an HTTP technique that sends info to a server. When you make a POST request, you sometimes submit information to create a brand new useful resource or set off a server-side motion. Therefore, making the identical request a number of instances can lead to completely different outcomes and negative effects on the server. This can result in duplicated information, beginning server sources, and decreasing efficiency due to the repeated motion.

Unlike idempotent strategies like GET, PUT, and DELETE, which have constant outcomes no matter repetition, POST requests could cause modifications to the server’s state with every invocation.

POST requests typically create new sources on the server. Repeating the identical POST request will generate a number of an identical sources, doubtlessly resulting in duplication.

This is much like DELETE which is an idempotent technique however not a secure technique. Deleting the final entry in a group utilizing a single DELETE request could be thought of idempotent. But if a developer creates a operate that deletes the final entry, that will set off DELETE a number of instances. Subsequent DELETE calls would have completely different results, as every one removes a singular entry. This could be thought of non-idempotent.

How to Achieve Idempotency with Non-Idempotent Methods

Idempotency is not solely a property inherent to sure strategies – it may also be carried out as a characteristic of a non-idempotent technique.  

Here are some methods to realize idempotency even with non-idempotent strategies.

Unique Identifiers

Adding distinctive identifiers to each request is likely one of the commonest methods used to implement idempotency. It works by monitoring whether or not the operation has already been carried out or not. If it is a duplicate (a repeat request), the server is aware of it is already handled that request and easily ignores it, guaranteeing that no negative effects happen.

Here’s an instance of the way it works:

from uuid import uuid4
 
def process_order(unique_id, order_data):
    if Order.objects.filter(unique_id=unique_id).exists():
        return HttpResponse(standing=409)  # Conflict
    order = Order.objects.create(unique_id=unique_id, **order_data)
    return HttpResponse(standing=201, content_type="software/json")

# Example utilization
post_data = {"merchandise": [...]}
headers = {"X-Unique-ID": str(uuid4())}
requests.publish("https://api.instance.com/orders", information=post_data, headers=headers)

In this code snippet, we outline a operate referred to as process_order that creates orders in an API, utilizing distinctive identifiers to implement idempotency.

Here’s a breakdown of the code:

Importing the Unique Identifier Generator:

from uuid import uuid4: The code snippet begins by importing the uuid4 operate from the uuid module. This operate generates distinctive identifiers, that are used to realize idempotency on this code.

Defining the process_order Function:

def process_order(unique_id, order_data): This line defines a operate named process_order that takes two arguments:

  • unique_id: This is a string representing a singular identifier for the request. This ensures no duplicate orders are created with the identical identifier.
  • order_data: This is a dictionary containing the precise order information, like product info and buyer particulars.

Checking for Existing Orders:

if Order.objects.filter(unique_id=unique_id).exists(): This line checks if an order with the identical unique_id already exists within the database.

Order.objects.filter(unique_id=unique_id).exists() queries the Order mannequin for orders with the matching unique_id and checks if any orders had been discovered within the question consequence. If an order is discovered, it means the identical request was already processed.

Handling current orders:

return HttpResponse(standing=409): If an order with the identical unique_id already exists, the operate instantly returns an HTTP response with standing code 409 indicating a battle. This prevents duplicate orders from being created.

Creating a brand new order (if distinctive):

order = Order.objects.create(unique_id=unique_id, **order_data ): This line solely runs if no current order is discovered.

Order.objects.create: creates a brand new object within the Order mannequin.

unique_id=unique_id units the unique_id attribute of the brand new order to the supplied unique_id.

order_data: spreads the dictionary order_data as key phrase arguments to the order mannequin’s constructor, setting different related attributes like merchandise and buyer info.

Sending a hit response:

return HttpResponse(standing=201, content_type="software/json"): If the order creation is profitable, the operate will return an HTTP response with standing code 201 which reveals a profitable creation. It additionally specifies the response content material sort as JSON, assuming the order information could be returned in JSON format.

post_data = {"merchandise": [...]}: an instance request, defines a dictionary containing the precise order information, like a listing of merchandise.

headers = {"X-Unique-ID": str(uuid4())}: This line creates a dictionary containing a customized header named X-Unique-ID. It generates a singular identifier string utilizing uuid4() and provides it to the header.

requests.publish("https://api.instance.com/orders", information=post_data, headers=headers): This line sends a POST request to the API endpoint https://api.instance.com/orders  with the supplied post_data and headers.

How does this implement idempotence?

It does so by utilizing a singular identifier (unique_id) for every order.

It checks if an order with the identical identifier already exists within the database. If it returns true, it returns a 409 Conflict standing. Otherwise, it creates a brand new order and responds with a 201 Created standing. The distinctive identifier prevents duplicate orders, making the system idempotent.

Token-based Authorization

Token-based authorization is a type of authorization that assigns momentary tokens for every non-idempotent motion. Once the motion is accomplished, the token is invalidated. If the identical request comes once more with the identical token, the server acknowledges it as invalid and refuses the request, thereby stopping duplicate actions.

// Generate a singular token for this motion
const token = generateToken();

fetch("https://api.instance.com/create-user", {
    technique: "POST",
    physique: JSON.stringify({ username, password }),
    headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "software/json",
    },
})
    .then(response => {
        // Handle profitable response
        if (response.okay) {
            // Do one thing with the profitable response
        } else {
            // Handle non-successful response
        }
    })
    .catch(error => {
        // Handle error
        console.error("Error occurred:", error);
    })
    .lastly(() => {
        // Invalidate token after profitable motion or in case of an error
        invalidateToken(token);
    });

// Simple implementation for producing a token
operate generateToken() {
    return Math.random().toString(36).substr(2);
}

// Simple implementation for invalidating a token
operate invalidateToken(token) {
    // Add your logic to invalidate the token, e.g., take away it from storage
}

Here’s a breakdown of the code:

Generating a singular token:

const token = generateToken(): This line calls a operate named generateToken() (which is assumed to be outlined elsewhere) that generates a singular token string. This token shall be used for authorization and idempotency.

Sending the POST request:

fetch("https://api.instance.com/create-user", { ... }): This line makes use of the fetch API to ship a POST request to the API endpoint https://api.instance.com/create-user.

technique: "POST": This specifies the HTTP technique as POST, indicating the intention to create a brand new person.

physique: JSON.stringify({ username, password }): This defines the request physique with person particulars like username and password. The information is transformed to JSON format earlier than sending.

headers: { Authorization:Bearer ${token}}: This units the Authorization header within the request. The header worth consists of the generated token prefixed with “Bearer “.

Handling the Response:

.then(response => { ... }): This block defines the code to execute if the request is profitable. You would deal with issues like storing person info or redirecting the person upon profitable person creation.

.catch(error => { ... }): This block defines the code to execute if the request encounters an error. You would deal with any error messages or deal with particular error situations right here.

Invalidating the Token:

invalidateToken(token): This line calls a operate named invalidateToken(token) ( which is assumed to be outlined elsewhere) which might probably mark the used token as invalid. This ensures the identical token can’t be used for subsequent requests, including to the idempotency assure.

How does this implement Idempotence?

This code snippet makes use of token-based authorization to implement idempotency in a POST request to create a person on an API. If a person creation request is by chance despatched a number of instances, a brand new distinctive token is generated every time and used within the Authorization header.

The API server can acknowledge and confirm the distinctive token, and for the reason that person creation motion has already been carried out (assuming it is profitable the primary time), it will not create duplicate customers as a consequence of subsequent an identical requests.

An ETag header (Entity Tag) is an HTTP header used for internet cache validation and conditional requests. It is especially used for  PUT requests, that solely replace sources in the event that they have not modified for the reason that final examine.

When you need to replace a useful resource, the server sends you its ETag which is then included in your PUT request together with the up to date information. If the ETag hasn’t modified (which means the useful resource stays the identical), the server accepts the replace. But if the ETag has modified, the server rejects the replace, stopping it from overwriting another person’s modifications.

def update_article(article_id, content material):
    # Get current article and its ETag
    article = Article.objects.get(pk=article_id)
    etag = article.etag
    
    # Check if ETag matches with request header
    if request.headers.get("If-Match") != etag:
        return HttpResponse(standing=409)  # Conflict
    
    # Update article content material and generate new ETag
    article.content material = content material
    article.save()
    new_etag = article.etag
    
    # Return success response with up to date ETag
    return HttpResponse(standing=200, content_type="textual content/plain", content material=new_etag)

In this code snippet, we outline a operate referred to as update_article that lets you replace the content material of an current article primarily based on its ID and new content material. It implements idempotency utilizing the ETag header approach.

Here’s a step-by-step rationalization of the way it works;

Getting the Existing Article and its ETag:

article = Article.objects.get(pk=article_id): This line fetches the article with the supplied article_id from the database utilizing the Article mannequin.

etag = article.etag: This line extracts the ETag worth from the retrieved article object. The ETag serves as a singular identifier for the article’s present state.

Checking for a Match:

if request.headers.get("If-Match") != etag: This line checks if the ETag header supplied within the request matches the ETag of the retrieved article.

return HttpResponse(standing=409): If the ETag would not match, it signifies that the article might need been up to date by one other request for the reason that shopper retrieved its info. The operate returns a 409 Conflict response, which prevents unintentional information corruption.

Updating the Article Content and producing a brand new ETag:

article.content material = content material: This line updates the article’s content material with the brand new content material acquired within the request.

article.save(): This line saves the up to date article again to the database.

new_etag = article.etag: This line retrieves the brand new ETag generated for the up to date article after saving it.

Returning the Success Response with the brand new ETag:

return HttpResponse(standing=200, content_type="textual content/plain", content material=new_etag): returns a profitable 200 OK response, together with the content material sort (“textual content/plain”) and the up to date ETag of the article within the response physique.

How does this implement idempotence?

This code ensures that if the identical replace request is shipped a number of instances with the identical ETag, the replace will solely be carried out as soon as, stopping duplicate updates and sustaining information consistency. The new ETag is then supplied within the response to assist the shopper hold monitor of the article’s state for future interactions.

Conclusion

In this tutorial, we highlighted the distinction between secure strategies like GET, which retrieves information with out negative effects, and non-idempotent strategies like POST, which may have completely different outcomes with every repetition.

We additionally explored methods you’ll be able to apply to realize idempotence in non-idempotent strategies, emphasizing the significance of designing APIs that prioritize consistency and reliability.

You may also like

Leave a Comment