¿Qué es un serializador?
Los serializadores son unos de los componentes más poderosos que tiene Django Rest Framework. Estos permiten que estructuras complejas y modelos de nuestro proyecto en Django sean convertidos a estructuras nativas de Python y puedan ser convertidas fácilmente en JSON
o XML
. El uso de ellos es muy extendido y uno de los temas claves que tocaremos en este post es la optimización de serializadores.
Lógica del proyecto
Imaginemos que tenemos una estructura básica de Country
y City
donde una ciudad pertenece a un país, así:
# models.py
from django.db import models
classCountry(models.Model):
name = models.CharField(max_length=55)
classCity(models.Model):
name = models.CharField(max_length=55)
country = models.ForeignKey(
Country, related_name='cities', on_delete=models.SET_NULL)
El viewsets
es el lugar donde declaramos la lógica de nuestra vista, con Django Rest Framework
en muy pocas líneas declararemos lo necesario para que funcione nuestra lógica
# viewsets.py
from rest_framework import viewsets
from .models import Country
from .serializers import CountrySerializer
classCountryViewSet(viewsets.ReadOnlyModelViewSet):
...
queryset = Country.objects.all()
serializer_class = CountrySerializer
model = Country
...
En los serializadores es donde escribiremos la lógica de la estructura final que será la respuesta al request que haremos a nuestro proyecto
# serializers.py
from rest_framework import serializers
from .models import Country, City
classCitySerializer(serializers.ModelSerializer):
classMeta:
model = City
fields = (
'id',
'name',
)
classCountrySerializer(serializers.ModelSerializer):
cities = CitySerializer(many=True)
classMeta:
model = Country
fields = (
'id',
'name',
'cities',
)
Antes de continuar analicemos más a fondo lo que tenemos escrito más arriba:
- Dos modelos que representan las entidades de
City
yCountry
, dondeCity
contiene elforeign key
aCountry
- Un
ViewSet
donde declaramos en el atributoqueryset
, la sentencia que nos buscará todos registros existentes deCountry
y que los serializará conCountrySerializer
- En
CountrySerializer
pedimos que nos muestre los datos según la estructura mostrada en el atributofields
. Lo importante acá, y nos servirá luego para hacer la optimización, es elnested serializer
que declaramos para el atributocities
. Esto básicamente indica que todas lascities
asociadas alCountry
serán serializadas conCitySerializer
Si por ejemplo hacemos una petición GET al endpoint country/1/
tendremos la siguiente estructura de respuesta:
{
"id": 1,
"name": "Colombia",
"cities": [
{
"id": 1,
"name": "Bogotá"
},
{
"id": 2,
"name": "Medellín"
},
{
"id": 3,
"name": "Cali"
}
]
}
Problema del N+1
La lógica que declaramos en los serializadores nos permite tener estructuras muy ordenadas como esta que obtuvimos, pero cuando nuestro proyecto es muy grande y procesa una cantidad de requests alta es necesario pensar en todos los queries
que se hacen a la base de datos.
En este caso y con esta respuesta tenemos los siguientes queries:
- Un query para obtener el
country
deid=1
- Tres queries para obtener todos las
city
que tienen comoforeign key=1
En total ejecutamos 4 queries a la base de datos para obtener la información. Este problema es muy común en programación y se llama el problema del N+1
, donde 1
representa al objeto base de la consulta (en este caso Country
) y N
representa a la N cantidad de atributos relacionados a él (en nuestro caso City
).
Eager Loading
Eager Loading
es un concepto muy utilizado para resolver el problema del N+1
, básicamente disminuye las consultas con un proceso más inteligente para hacer queries a la base de datos; en nuestro caso, en vez de hacer cuatro peticiones para solicitar la información de City
hace una sola consulta y precarga la información de estas.
Django
permite utilizar eager loading
a través del método prefetch_related
, este es el que implementaremos para optimizar las consultas. El cambio no es tan grande a nivel de código, solo tenemos que sobreescribir el método get_queryset
de CountryViewSet
, así:
# viewsets.py
from rest_framework import viewsets
from django.db.models import Prefetch
from .models import Country
from .serializers import CountrySerializer
classCountryViewSet(viewsets.ReadOnlyModelViewSet):
...
queryset = Country.objects.all()
serializer_class = CountrySerializer
...
defget_queryset(self):
queryset = super().get_queryset()
queryset = queryset.prefetch_related(
Prefetch('cities')
)
return queryset
Optimización del serializador
Sobreescribir el método get_queryset
nos permite ajustar el comportamiento y modificar la consulta conforme a lo que necesitemos, en este caso, que nos precargue los datos de cities
asociados al Country
que se consulta.
Si hacemos de nuevo el llamado al endpoint country/1/
no notaremos cambios en el JSON de la respuesta, pero sí habrá una disminución en la cantidad de queries a la base de datos, en lugar de las 4 que teníamos al inicio se disminuyó a 2, dado que la primera consulta es para traer la información de Country
y la segunda es para traer la información de las ciudades asociadas.
De esta forma y aplicando unos cambios muy pequeños en la lógica de nuestro código podemos mejorar el rendimiento y la velocidad de respuesta del proyecto.
En el Curso Avanzado de Django podrás aprender muchos conceptos como este y tendrás una aplicación tan rápida que tus usuarios amarán usarla.
Curso Avanzado de Django v2