Evite o acesso direto aos modelos em aplicações Django

Um programa de computador, em sua forma mais simples, é um conjunto de dados e uma coleção de transformações sobre eles. Um programa pode ser tão simples quanto receber dois números inteiros e somá-los, ou tão complexo quanto um mecanismo de busca para a web.
Quando escrevemos programas, pensamos em termos mais abstratos. Podemos ter um objeto pessoa com nome e idade, não apenas uma string e um número inteiro. Gostamos de escrever programas de maneira que o código-fonte mostre o que realizar em vez de como realizar. Isso permite que esqueçamos, em grande parte, onde os dados estão e como mantê-los consistentes.
Suponha que queremos escrever uma API web com Django. Começaremos criando uma aplicação Django, definindo alguns modelos e escrevendo algumas views para tratar as solicitações recebidas. Isso funciona bem até começarmos a adicionar mais aplicações que usam os modelos umas das outras e, de repente, percebemos que nosso software dedica mais tempo a falar sobre como fazer as coisas do que sobre quais coisas fazer.
Talvez estejamos considerando armazenar algumas partes dos dados em cache para acessá-las mais rapidamente, o que significa que não deveríamos mais usar os modelos Django diretamente. Agora precisaremos refatorar o código para evitar esse uso direto.
Acredito que a causa desses problemas está no fato de que a forma padrão de desenvolver aplicações Django nos conduz por um caminho em que o banco de dados ocupa o centro do código, em vez de ser apenas mais um detalhe de implementação. Além disso, tendemos a escrever a lógica nas classes de view e, conforme o número de views aumenta, o uso dos modelos se espalha pelo código.
Gostaria de propor uma abordagem diferente para desenvolver aplicações Django. Em vez de depender dos próprios modelos para criar, atualizar e obter dados, podemos nos beneficiar de módulos controladores que implementem ações de nível mais alto sobre os dados. Isso significa planejar com mais cuidado o que nossas aplicações Django fazem e garantir que os dados sejam acessados apenas por meio dos módulos controladores.
Nossas classes de view devem ser responsáveis pelo lado HTTP da comunicação. Uma solicitação chega para realizar uma ação sobre um conjunto de recursos; a função de view se comunica com o módulo controlador para realizar a ação e depois prepara os resultados recebidos no formato esperado pelo cliente.
Por exemplo, vamos criar um projeto Django novo e uma aplicação chamada myapp. Vamos criar um modelo chamado Resource com um único campo booleano chamado homepage.
from django.db import models
class Resource(models.Model):
homepage = models.BooleanField()
Agora vamos criar uma função de view que exiba os identificadores de todos os objetos Resource com 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])
)
A função acima é muito básica, mas ainda podemos ver que realiza duas ações diferentes: primeiro busca objetos Resource no banco de dados e depois aplica regras de formatação antes de devolvê-los ao cliente.
Vamos mover a parte da lógica, ou seja, a busca dos objetos Resource, para outra classe.
Criamos um arquivo controller.py dentro do diretório myapp, assim:
from .models import Resource
class MyAppController:
def get_homepage_resources(self):
return Resource.objects.filter(homepage=True).values()
Aqui usamos .values() para retornar uma lista de dicionários em vez de um QuerySet, evitando que as instâncias do modelo vazem para o código que chama o método.
Depois, modificamos o arquivo views.py para usar o 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])
)
Agora que a view homepage não precisa saber de onde vêm os objetos Resource, a classe controladora pode até ser ampliada para oferecer suporte a vários bancos de dados, cache com Redis e outras opções sem alterar a função de view.
Acredito que isso melhorará muito a integridade dos dados, ajudará a entender e manter melhor nossas invariantes e facilitará a manutenção e a melhoria dos sistemas de armazenamento de dados. Por exemplo, deve ser mais fácil adicionar um sistema de cache quando o módulo controlador é o único a acessar esses dados.