Django
{.verbatim} es un gran framework para desarrollo web en
python. El django rest framework
{.verbatim}, lo especializa para crear APIs REST
{.verbatim} con mucha facilidad.
Django
{.verbatim} tiene clases como modelo
{.verbatim} y vista
{.verbatim}, que corresponden, no de manera exacta, con los correspondientes del patrón modelo-vista-controlador.1 El DRF agrega una clase nueva, los serializadores, encargados de transformar desde una representación, como json, a objetos de python y viceversa. Los serializadores son bastante flexibles y permiten incrustar en ellos lógica compleja de validación y de transformación de datos. No obstante, hacerlo es una mala idea, ya que podemos terminar con serializadores que implementan parte de la lógica del negocio o del dominio.
Este artículo no pretende hablar de ese problema, sino de otro que puede surgir con el abuso de los serializadores. Supongamos que tenemos una modelo llamado Company
en nuestra aplicación. Los campos de este modelo/tabla poco nos importan. A su vez, tenemos varias vistas que deben lidiar con \"compañías\". Aparte de las típicas acciones de crear, listar, actualizar, traer o eliminar, otras clases podrían tener que lidiar de alguna forma con la clase compañía. Por ejemplo, por conveniencia, podríamos querer incrustar en los datos de un usuario, su compañía:
{
"id": 1,
"name": "Joseph",
"company": {
"id": 10
}
}
Por tanto, crearemos uno o varios serializadores para lidiar con el
modelo Company
. Es un patrón que he visto en varios proyectos el crear una clase padre llamada, por ejemplo, CompanySerializer
, y de allí se desprenden clases específicas como CompanyUpdateSerializer
o CompanyCreateSerializer
en un intento de usar la herencia para
compartir funcionalidad aparentemente común.
No solo eso. Es muy probable que la vista de usuario que entrega la
compañía incrustada, decida compartir el mismo CompanySerializer
en un intento de seguir el malentendido principio de no te repitas, o
Don\'t Repeat Yourself (DRY). Voy a justificar por qué esto es una
mala idea por varias razones.
Comencemos por argumentar en contra de la herencia. Existe otro
principio de diseño que nos sugiere preferir la composición por sobre la herencia. Es demasiado fácil y tentador abusar de la herencia como mecanismo de reutilización de código, lo que puede resultar en una aplicación mucho más difícil de entender. Lo explícito es mejor que lo implícito, y pocas cosas hay más implícitas que la herencia. Mi experiencia con el DRF
{.verbatim} no ha hecho más que reafirmar en mi ese principio. Aunque parece intuitivo el que hayan diversos tipos de serializadores, lo cierto es que terminan siendo usados en contextos muy distintos. Especialmente cuando la aplicación crece, nos encontraremos con que habrá más lógica de negocio necesaria para crear, actualizar, e incluso para listar. Esa lógica es incompatible entre si, y termina siendo necesario hacer comprobaciones para saber en qué contexto se está llamando el serializador.
Dicho de otro modo, estamos violando el principio de única responsabilidad que dice que solo debe haber un motivo para que una clase cambie. Cuando una serializador es compartido en contextos tan diferentes, tiene múltiples razones para cambiar. Lo que sucede en un contexto, o caso de uso, puede afectar a otros contextos.
Desde otro punto de vista, nos damos cuenta que la clase CompanySerializer
es referenciada por muchas otras clases en contextos diferentes. Eso es otra señal de un problema de diseño. Cuando de una clase dependen muchas otras (en términos visuales, a una clase entran muchas líneas), un cambio en esta puede afectar a sus dependientes.
Y es que no tiene sentido compartir serializadores. La apariencia de
reutilizar no es más que eso; algo superficial.
Pensemos en que la vista de usuario que incrusta la compañía, aunque en apariencia accede a la misma fila de la base de datos, puede tener menos (o más) permisos que las demás vistas que también la usan. Quiero decir que puede necesitar retornar menos campos por cuestión de privacidad y seguridad. ¿Cómo manejamos esto con un serializador compartido? Se vuelve complejo y es proponso a errores. Por ejemplo, una nueva característica puede querer acceder a una compañía con un dato que se considera privado en otros contextos. Lo agrega al serializador y, por arte de magia, ahora es accesible en todas las vistas que dependen de él. Esto, claro está, tiene solución, pero facilita demasiado el equivocarse.
¿Cuál es la solución? Simple, no reutilizar serializadores. Podemos
también decir: a cada vista le corresponde un único serializador. No importa que accedan al mismo dato, en apariencia. El contexto tan diferente hace que no sea, en el fondo, lo mismo.
Dado que es una relación única, podemos usar una estrategia de ubicar un serializador al interior de su vista. Así:
class CompanyView(APIView):
class Serializer(ModelSerializer):
...
def get(self, request):
... # Podemos acceder al serializador haciendo: self.Serialzer
Es una opción, por supuesto, aunque es ideal para situaciones simples. ¿Qué pasa cuando el serializador posee serializadores anidados? Tal vez se haga complejo. Se puede resolver, pero, cuando menos, podemos siempre recurrir a la relación uno a uno ubicando los serializadores en el archivo serializer.py
como es costumbre.
Notas
-
En django, una vista no corresponde con el concepto de vista en MVC. La combinación entre una plantilla (template) y una vista de django corresponde de mejor manera con la V de MVC. ¿Y los controladores? No hay tal cosa en django. Por esta razón se promueve la idea de vistas delgadas y modelos obesos. ↩