Artículos

Evita el acceso directo a los modelos en aplicaciones Django

Un programa informático, en su forma más simple, es un conjunto de datos y una colección de transformaciones sobre ellos. Un programa puede ser tan sencillo como recibir dos números enteros y sumarlos, o tan complejo como un motor de búsqueda para la web.

Cuando escribimos programas, pensamos en términos más abstractos. Podemos tener un objeto persona con nombre y edad, no solo una cadena de texto y un número entero. Nos gusta escribir programas de manera que el código fuente muestre qué queremos lograr en lugar de cómo lograrlo. Esto nos permite olvidarnos, en gran medida, de dónde están nuestros datos y cómo mantenerlos consistentes.

Supongamos que queremos escribir una API web con Django. Comenzaremos creando una aplicación Django, definiendo algunos modelos y escribiendo unas vistas para manejar las solicitudes entrantes. Esto funciona bastante bien hasta que agregamos más aplicaciones que usan los modelos unas de otras y, de repente, nos damos cuenta de que nuestro software dedica más tiempo a hablar de cómo hacer las cosas que de qué cosas hacer.

Quizás estemos considerando almacenar algunas partes de nuestros datos en caché para acceder más rápido, lo que significa que ya no deberíamos usar nuestros modelos Django directamente. Ahora tendremos que refactorizar el código para evitar ese uso directo.

Creo que la causa de estos problemas es que la forma predeterminada de desarrollar aplicaciones Django nos lleva por un camino donde la base de datos ocupa el centro de nuestro código, en lugar de ser un detalle más de implementación. Además, tendemos a escribir la lógica en las clases de vista y, a medida que aumenta la cantidad de vistas, el uso de nuestros modelos se dispersa por el código.

Me gustaría proponer un enfoque diferente para desarrollar aplicaciones Django. En lugar de depender de los propios modelos para crear, actualizar y obtener datos, puede ser útil escribir módulos controladores que implementen acciones de mayor nivel sobre los datos. Esto significa que debemos planificar con más cuidado qué hacen nuestras aplicaciones Django y asegurarnos de acceder a los datos únicamente mediante nuestros módulos controladores.

Nuestras clases de vista deberían encargarse del lado HTTP de la comunicación. Llega una solicitud para realizar una acción sobre un conjunto de recursos; la función de vista se comunica con el módulo controlador para realizarla y luego prepara los resultados recibidos del controlador en el formato que espera el cliente.

Por ejemplo, creemos un proyecto Django nuevo y una aplicación llamada myapp. Creemos un modelo llamado Resource con un único campo booleano llamado homepage.

from django.db import models

class Resource(models.Model):
    homepage = models.BooleanField()

Ahora crearemos una función de vista que muestre los identificadores de todos los objetos Resource con homepage=True.

from django.http import HttpResponse
from .models import Resource


def homepage(request):
    resources = Resource.objects.filter(homepage=True)
    return HttpResponse(
        '<br>'.join([f'Resource: {x.id}' for x in resources])
    )

La función anterior es muy básica, pero aun así podemos ver que realiza dos acciones diferentes: primero obtiene objetos Resource de la base de datos y luego les aplica reglas de formato antes de devolverlos al cliente.

Movamos la parte de la lógica, es decir, la obtención de los objetos Resource, a otra clase.

Creamos un archivo controller.py dentro del directorio myapp, así:

from .models import Resource

class MyAppController:
    def get_homepage_resources(self):
        return Resource.objects.filter(homepage=True).values()

Aquí usamos .values() para devolver una lista de diccionarios en lugar de un QuerySet y así evitar que las instancias del modelo se filtren al código que llama al método.

Luego modificamos el archivo views.py para que use el controlador.

from django.http import HttpResponse
from .controller import MyAppController

ctrl = MyAppController()

def homepage(request):
    resources = ctrl.get_homepage_resources()
    return HttpResponse(
        '<br>'.join([f'Resource: {x["id"]}' for x in resources])
    )

Ahora que la vista homepage no necesita saber de dónde vienen los objetos Resource, la clase controladora incluso podría ampliarse para admitir múltiples bases de datos, caché con Redis y otras opciones sin cambiar la función de vista.

Creo que esto mejorará mucho la integridad de nuestros datos, nos ayudará a comprender y mantener mejor nuestras invariantes y facilitará el mantenimiento y la mejora de nuestros sistemas de almacenamiento de datos. Por ejemplo, debería ser más fácil agregar un sistema de caché cuando el módulo controlador es el único que accede a esos datos.

“Evita el acceso directo a los modelos en aplicaciones Django” de Juan Cabrera está bajo la licencia CC BY SA. Los ejemplos de código fuente están bajo la licencia MIT.

Foto de sophilabs.

Clasificado en Investigación y aprendizaje.

Lecturas relacionadas