¿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
CityyCountry, dondeCitycontiene elforeign keyaCountry - Un
ViewSetdonde declaramos en el atributoqueryset, la sentencia que nos buscará todos registros existentes deCountryy que los serializará conCountrySerializer - En
CountrySerializerpedimos 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 serializerque declaramos para el atributocities. Esto básicamente indica que todas lascitiesasociadas alCountryserá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
countrydeid=1 - Tres queries para obtener todos las
cityque 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








