Understanding atomic transactions in Django: Ensuring data consistency

Django is a high-level web framework that supports building robust applications quickly and efficiently. One of its notable features is its support for data integrity and consistency through built-in mechanisms that developers can seamlessly integrate into their projects.

Django follows the ACID (Atomicity, Consistency, Isolation, Durability) principle for database transactions and one of the ways it does this is through atomic transactions. In this article, you will learn how atomic transactions work in Django.

What is an atomic transaction?

An atomic transaction describes a group of database transactions or operations regarded as a single unit. These operations can be saving something to your database or updating an existing record based on some condition. In an atomic transaction, if anything goes wrong while you're running your operation, the entire transaction fails.

How atomic transactions work in Django

Django’s default behavior is to commit each transaction or operation to the database immediately after it gets executed. This means you can have a view function where some database operations get executed and some do not. Here’s an example:


def my_view(request):
    my_object = MyModel.objects.create(first_name="Eren")
    my_object.save()

    print(saved) # error occurs to break the code 

    my_object.last_name = "Yeager"
    my_object.save()

In the above code example, whenever you call the view function, it’ll return an error message, as you might expect. However, if you inspect your database, you’ll find an object with Eren as the first name. This object will also not have a last name.

This behavior can allow your database to be updated unknowingly if an error occurs while your code is executing.

Atomic transactions will ensure your database is not updated until all operations are successfully executed. So, if you get an error in the middle of your code, your database will not be affected. This image shows the difference between Django’s default approach to database transactions and how atomic transactions work:

Analogy to understand atomic transactions in Django

This analogy will show how your data can be compromised if you don’t use atomic transactions. Imagine the following scenarios:

  1. You have a model that takes an employee's first name, last name, and role. However, the last name is not a required field:

    
     from django.db import models
     class Employee(models.Model):
         first_name = models.CharField(max_length=50)
         last_name = models.CharField(max_length=50, null=True, blank=True)
         role = models.CharField(max_length=225)
    

    After you create your model, go to the admin panel to create an employee without a last name:

  2. In your view, you have a function that updates an employee's last name and makes them a manager. Here’s an example:

    
     from .models import Employee
     def update_employee(emp_id, last_name):
         employee = Employee.objects.get(id=emp_id)
         employee.last_name = last_name
         employee.save()
         employee.role = Manager
         employee.save()
    

    If you observe closely, you will realize that the function above has a slight error. This line of code is supposed to assign a string, but it is not:

     employee.role = Manager
    
  3. Now, try using the update_employee() to update the employee you created earlier. To do this, you can open your shell by typing the following command:

     python manage.py shell
    

    After that, you should import your function and call it as done in the image below:

    Since your code ran into an error during execution, you should not expect any updates in your database. However, if you open your Django admin panel to view the employee object, you will see this:

    Despite your code encountering an error, you can see that it updated the employee's last name and maintained the previous role. This is bad because the expectation is to update both the employee's last name and role, hence, the integrity of your data can be questioned.

    If the code above was written as an atomic transaction, the entire operation would have failed and your database will receive no updates.

How to use atomic transactions in Django

Django provides two ways to use atomic transactions in your project. Each method has its own advantage and use case. The two methods are listed here:

  • function decorators

  • context managers

Regardless of the method you use, you need to import transactions into your code.

Writing atomic transactions with function Decorators

You can learn how to write atomic transactions with function decorators by following these steps:

  1. Import the transaction module into your code:

     from django.db import transaction
    
  2. Decorate your function with the @transaction.atomic decorator. Here’s how the new update_employee() function will look like:

    
     @transaction.atomic #new
     def update_employee(emp_id, last_name):
         employee = Employee.objects.get(id=emp_id)
         employee.last_name = last_name
         employee.save()
         employee.role = Manager
         employee.save()
    

    If you call the function again, you’ll still get an error as expected. However, no changes will be committed to your database and your data will remain consistent.

Writing atomic transactions using context managers

You can write atomic transactions as context managers by using the with keyword. You should use this method if you only want to make a specific part of your code atomic. These steps will show you how to do it:

  1. Import the transaction module into your code:

     from django.db import transaction
    
  2. Wrap your transaction statement inside a with code block. Here’s an example:

    
     def update_employee(emp_id, last_name):
         employee = Employee.objects.get(id=emp_id)
         with transaction.atomic:
             employee.last_name = last_name
             employee.save()
             employee.role = Manager
             employee.save()
    

    In the code above, the entire function isn’t atomic. Only the code block related to modifying and saving the employee to the database is atomic.

What method should you use for atomic transactions?

The choice of what method to use ultimately depends on your desired code output. Sometimes, making your entire view atomic (with decorators) might be less advantageous, and the same goes for using atomic transactions as context managers.

Always consider the final desired outcome of your code before deciding what method to use for atomic transactions or whether it’s okay to use atomic transactions.

Strive to ensure data integrity for your projects

Data integrity is an essential part of backend development and in this article, you have learned one way to ensure the integrity of your application's data.

Apart from atomic transactions, there are principles you should consider when building your app. These principles are the ACID principles mentioned earlier. Read about them to learn their use cases and how to apply them.

If you enjoyed this post, consider following me on Twitter, Hashnode, and dev.to.

Resource

Database transactions in Django

Cover image by D koi on Unsplash

0
Subscribe to my newsletter

Read articles from Ademola Thompson directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ademola Thompson
Ademola Thompson

Ademola is a backend developer with experience with Django, a Python web framework. He enjoys teaching others what he knows about the web; hence he decided to become a technical content writer. Once in a while, he explores the possibilities of Solidity and Web 3.0. He is popularly known as SuperheroJT.