The open blogging platform. Say no to algorithms and paywalls.

Data Transfer Object in Django, DRF

A guide to implementing the Data Transfer Objects (DTO) Pattern in Django

Django is a very popular tool for building websites with Python. It’s great for quickly making simple websites, but as projects get bigger, it can become tricky to manage and support them.

To deal with these challenges, developers use different ways of organizing their projects and try to apply ‘Clean Architecture’ approaches, patterns to Django, DRF projects.

To learn more about using ‘Clean Architecture’ with Django, you can read the article.

A key idea of most patterns are to keep different parts of the project decoupling, independent from each other to give them flexibility to scale and change.

One of these patterns is called the ‘Data Transfer Objects Pattern’.
This idea was first explained by Martin Fowler in his book. Basically, it means using special objects to move data between different parts of the project in order to reduce the number of methods calls.

When we use this approach in our project, we also get another benefit. It is the encapsulation of the serialization’s logic (the mechanism that translates the object structure and data to a specific format that can be stored and transferred). It provides a single point of change in the serialization nuances. It also decouples the domain models from the presentation layer, allowing both to change independently.

DTO pattern is often used with Repository pattern that isolates the data layer from the rest of the app.
The repository will be complete tie and depend from current ORM implementation but to make independent other layers it will return the DTO, which does not dependent from the framework implementation.

# dto.py  
  
@dataclass  
class InstanceDTO:  
  id: int  
  name: str
# repository.py  
  
def get_obj_by_id(self, instance_id: int) -> Union[CategoryDTO, None]:  
  django_orm_obj = get_object_or_None(Model, pk=instance_id)  
  
  instance_dto = InstanceDTO(  
    id=django_orm_obj.pk,   
    name=django_orm_obj.name  
  )  
  return instance_dto

Looks simple, but what if your django_orm_obj that returns from the database is more complex?
For example, has related models or recursive relation.

# repository.py  
  
def get_obj_by_id(self, instance_id: int) -> Union[CategoryDTO, None]:  
  django_orm_obj = get_object_or_None(  
    Model.objects.select_related('ModelA')  
      .prefetch_related('ModelB', 'ModelC__ModelD'), pk=instance_id  
  )  
...

To make DTO from this django_orm_obj, we are required to build a separate function to map it to DTO. And if there are a bunch of this queries their mapping turns into nightmare.

But I have a good news for you, there is a Python package that make all of this instead of you. The AutoDataclass is a simple package that helps easy to map data into DTO for transporting that data between system layers. The package uses specified Dataclass structure for retrieving data and creating DTO.

Now, I want to share with you of how to use this package in your project.

Installation

pip install auto_dataclass

Usage

  1. Simple models relations
# models.py  
from django.db import models  
  
class Product(models.Model):  
    name = models.CharField(max_length=128)  
    description = models.TextField(max_length=1000)  
      
class Photo(models.Model):  
    product = models.ForeignKey(Product, related_name="photos",  
                                on_delete=models.CASCADE)  
    image = models.ImageField(blank=True)

Define your Dataclasses that describe retrieved data structure from DB.

# dto.py  
  
@dataclass(frozen=True)  
class PhotoDataclass:  
    id: int  
    image: str  
  
@dataclass(frozen=True)  
class ProductDataclass:  
    id: int  
    name: str  
    description: str  
    photos: List[ProductDataclass] = field(default_factory=list)

Create Converter instance and call to_dto method with passed data from the query and previously defined Dataclass.

# repository.py  
  
from auto_dataclass.dj_model_to_dataclass import FromOrmToDataclass  
  
from dto import ProductDataclass  
from models import Product  
  
# Creating Converter instance  
converter = FromOrmToDataclass()  
  
def get_product(product_id: int) -> ProductDataclass:  
    product_model_instance = Product.objects \  
                    .prefetch_related('photos') \  
                    .get(pk=product_id)   
    # Converting Django model object from the query to DTO.   
    retrun converter.to_dto(product_model_instance, ProductDataclass)
  1. Recursive Django model relation

If your data has a recursive relation you can also map them with the same way.

# models.py  
  
from django.db import models  
  
class Category(models.Model):  
    name = models.CharField(max_length=128)  
    parent = models.ForeignKey(  
        "Category", related_name="sub_categories", null=True, blank=True, on_delete=models.CASCADE  
    )
# dto.py  
  
@dataclass  
class CategoriesDTO:  
    id: int  
    name: str  
    sub_categories: List['CategoriesDTO'] = field(default_factory=list)
# repository.py  
  
from itertools import repeat  
from auto_dataclass.dj_model_to_dataclass import FromOrmToDataclass  
  
from models import Category  
from dto import CategoriesDTO  
  
converter = FromOrmToDataclass()  
  
def get_categories(self) -> Iterable[CategoriesDTO]:  
    category_model_instances = Category.objects.filter(parent__isnull=True)  
    return map(converter.to_dto, category_model_instances, repeat(CategoriesDTO))

More example you can find in repo AutoDataclass.

Summary

DTO is a simple but powerfull pattern that allows to decoupling bussiness logic from the framework.
In order to quickly and easy implement it in your app, you may use AutoDataclass package, which will help to map Django ORM object to DTO.

Also, I would recommend to use djangorestframework-dataclasses.

This package helps you avoid duplicate the fields in DTO and Serializer definitions. Serializer will be constract automatically from DTO schema.




Continue Learning