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:
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:
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
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:
Import the transaction module into your code:
from django.db import transaction
Decorate your function with the
@transaction.atomic
decorator. Here’s how the newupdate_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:
Import the
transaction
module into your code:from django.db import transaction
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
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.