Estou escrevendo um projeto no Django e vejo que 80% do código está no arquivo models.py
. Esse código é confuso e, depois de um certo tempo, deixo de entender o que realmente está acontecendo.
Aqui está o que me incomoda:
- Acho feio que o nível do meu modelo (que deveria ser responsável apenas pelo trabalho com dados de um banco de dados) também esteja enviando e-mails, andando na API para outros serviços, etc.
- Além disso, acho inaceitável colocar a lógica de negócios na exibição, pois dessa maneira fica difícil de controlar. Por exemplo, no meu aplicativo, há pelo menos três maneiras de criar novas instâncias de
User
, mas tecnicamente ele deve criá-las de maneira uniforme. - Nem sempre percebo quando os métodos e propriedades dos meus modelos se tornam não determinísticos e quando desenvolvem efeitos colaterais.
Aqui está um exemplo simples. A princípio, o User
modelo era assim:
class User(db.Models):
def get_present_name(self):
return self.name or 'Anonymous'
def activate(self):
self.status = 'activated'
self.save()
Com o tempo, tornou-se isso:
class User(db.Models):
def get_present_name(self):
# property became non-deterministic in terms of database
# data is taken from another service by api
return remote_api.request_user_name(self.uid) or 'Anonymous'
def activate(self):
# method now has a side effect (send message to user)
self.status = 'activated'
self.save()
send_mail('Your account is activated!', '…', [self.email])
O que eu quero é separar entidades no meu código:
- Entidades do meu banco de dados, nível do banco de dados: O que contém meu aplicativo?
- Entidades do meu aplicativo, nível da lógica de negócios: O que pode fazer meu aplicativo?
Quais são as boas práticas para implementar essa abordagem que pode ser aplicada no Django?
Respostas:
Parece que você está perguntando sobre a diferença entre o modelo de dados e o modelo de domínio - o último é onde você pode encontrar a lógica de negócios e as entidades percebidas pelo usuário final; o primeiro é onde você realmente armazena seus dados.
Além disso, interpretei a 3ª parte da sua pergunta como: como perceber a falha em manter esses modelos separados.
Esses são dois conceitos muito diferentes e é sempre difícil mantê-los separados. No entanto, existem alguns padrões e ferramentas comuns que podem ser usados para esse fim.
Sobre o modelo de domínio
A primeira coisa que você precisa reconhecer é que seu modelo de domínio não é realmente sobre dados; trata-se de ações e perguntas como "ativar este usuário", "desativar esse usuário", "quais usuários estão atualmente ativados?" e "qual é o nome desse usuário?". Em termos clássicos: trata-se de consultas e comandos .
Pensando em Comandos
Vamos começar examinando os comandos no seu exemplo: "ativar este usuário" e "desativar este usuário". O bom dos comandos é que eles podem ser facilmente expressados por pequenos cenários de quando e quando:
Esse cenário é útil para ver como diferentes partes da sua infraestrutura podem ser afetadas por um único comando - nesse caso, seu banco de dados (algum tipo de sinalizador 'ativo'), seu servidor de email, seu log do sistema etc.
Esse cenário também ajuda muito na configuração de um ambiente de desenvolvimento orientado a testes.
E, finalmente, pensar em comandos realmente ajuda a criar um aplicativo orientado a tarefas. Seus usuários irão apreciar isso :-)
Expressando comandos
O Django fornece duas maneiras fáceis de expressar comandos; ambas são opções válidas e não é incomum misturar as duas abordagens.
A camada de serviço
O módulo de serviço já foi descrito por @Hedde . Aqui você define um módulo separado e cada comando é representado como uma função.
services.py
Usando formulários
A outra maneira é usar um Django Form para cada comando. Eu prefiro essa abordagem, porque combina vários aspectos intimamente relacionados:
forms.py
Pensando em consultas
Seu exemplo não continha nenhuma consulta, então tomei a liberdade de fazer algumas consultas úteis. Eu prefiro usar o termo "pergunta", mas consultas é a terminologia clássica. As consultas interessantes são: "Qual é o nome deste usuário?", "Esse usuário pode efetuar login?", "Mostre-me uma lista de usuários desativados" e "Qual é a distribuição geográfica dos usuários desativados?"
Antes de começar a responder a essas perguntas, você deve sempre fazer duas perguntas: é uma consulta de apresentação apenas para meus modelos e / ou uma lógica de negócios vinculada à execução de meus comandos e / ou uma consulta de relatório .
As consultas de apresentação são feitas apenas para melhorar a interface do usuário. As respostas às consultas da lógica de negócios afetam diretamente a execução de seus comandos. As consultas de relatório são apenas para fins analíticos e têm restrições de tempo mais fracas. Essas categorias não são mutuamente exclusivas.
A outra pergunta é: "eu tenho controle completo sobre as respostas?" Por exemplo, ao consultar o nome do usuário (neste contexto), não temos controle sobre o resultado, porque contamos com uma API externa.
Fazendo consultas
A consulta mais básica no Django é o uso do objeto Manager:
Obviamente, isso só funciona se os dados estiverem realmente representados no seu modelo de dados. Isso não é sempre o caso. Nesses casos, você pode considerar as opções abaixo.
Tags e filtros personalizados
A primeira alternativa é útil para consultas que são meramente de apresentação: tags personalizadas e filtros de modelo.
template.html
template_tags.py
Métodos de consulta
Se sua consulta não for meramente de apresentação, você poderá adicionar consultas ao services.py (se estiver usando isso) ou introduzir um módulo queries.py :
queries.py
Modelos de proxy
Modelos de proxy são muito úteis no contexto da lógica de negócios e dos relatórios. Você basicamente define um subconjunto aprimorado do seu modelo. Você pode substituir o QuerySet básico de um gerente substituindo o
Manager.get_queryset()
métodomodels.py
Modelos de consulta
Para consultas que são inerentemente complexas, mas são executadas com bastante frequência, existe a possibilidade de modelos de consulta. Um modelo de consulta é uma forma de desnormalização em que dados relevantes para uma única consulta são armazenados em um modelo separado. O truque, é claro, é manter o modelo desnormalizado em sincronia com o modelo primário. Os modelos de consulta podem ser usados apenas se as alterações estiverem totalmente sob seu controle.
models.py
A primeira opção é atualizar esses modelos em seus comandos. Isso é muito útil se esses modelos forem alterados apenas por um ou dois comandos.
forms.py
Uma opção melhor seria usar sinais personalizados. É claro que esses sinais são emitidos por seus comandos. Os sinais têm a vantagem de poder manter vários modelos de consulta sincronizados com o modelo original. Além disso, o processamento do sinal pode ser transferido para tarefas em segundo plano, usando o Aipo ou estruturas semelhantes.
signs.py
forms.py
models.py
Mantendo-o limpo
Ao usar essa abordagem, torna-se ridiculamente fácil determinar se seu código permanece limpo. Basta seguir estas diretrizes:
O mesmo vale para as visualizações (porque as visualizações geralmente sofrem do mesmo problema).
Algumas referências
Documentação do Django: modelos de proxy
Documentação do Django: sinais
Arquitetura: Design Orientado a Domínio
fonte
User.objects.inactive_users()
. Mas o exemplo do modelo de proxy aqui da IMO leva à semântica incorreta:u = InactiveUser.objects.all()[0]; u.active = True; u.save()
e aindaisinstance(u, InactiveUser) == True
. Também mencionaria que uma maneira eficaz de manter um modelo de consulta em muitos casos é com uma exibição de banco de dados.Normalmente, implico uma camada de serviço entre visualizações e modelos. Isso funciona como a API do seu projeto e oferece uma boa visão de helicóptero do que está acontecendo. Eu herdei essa prática de um colega meu que usa muito essa técnica de camadas com projetos Java (JSF), por exemplo:
models.py
services.py
views.py
fonte
Primeiro de tudo, não se repita .
Então, tenha cuidado para não fazer o excesso de engenharia, às vezes é apenas uma perda de tempo e faz com que alguém perca o foco no que é importante. Revise o zen do python de tempos em tempos.
Dê uma olhada em projetos ativos
o repositório de malha também é bom de se olhar.
yourapp/models/logicalgroup.py
User
,Group
e modelos relacionados podem ficaryourapp/models/users.py
Poll
,Question
,Answer
... poderia ir ao abrigoyourapp/models/polls.py
__all__
dentro deyourapp/models/__init__.py
Mais sobre MVC
request.GET
/request.POST
... etctastypie
oupiston
Aproveite o middleware / templatetags
Aproveite os gerenciadores de modelos
User
pode entrar em umUserManager(models.Manager)
.models.Model
.queryset
poderiam entrar em ummodels.Manager
.User
um de cada vez, para poder pensar que ele deve permanecer no próprio modelo, mas ao criar o objeto, você provavelmente não possui todos os detalhes:Exemplo:
Faça uso de formulários sempre que possível
Um monte de código padrão pode ser eliminado se você tiver formulários mapeados para um modelo. O
ModelForm documentation
é muito bom. Separar o código para formulários do código do modelo pode ser bom se você tiver muita customização (ou às vezes evite erros de importação cíclicos para usos mais avançados).Use comandos de gerenciamento quando possível
yourapp/management/commands/createsuperuser.py
yourapp/management/commands/activateinbulk.py
se você tiver lógica de negócios, poderá separá-la
django.contrib.auth
usa back- end, assim como o db tem um back-end ... etc.setting
para a sua lógica de negócios (por exemploAUTHENTICATION_BACKENDS
)django.contrib.auth.backends.RemoteUserBackend
yourapp.backends.remote_api.RemoteUserBackend
yourapp.backends.memcached.RemoteUserBackend
exemplo de back-end:
poderia se tornar:
mais sobre padrões de design
mais sobre os limites da interface
yourapp.models
yourapp.vendor
yourapp.libs
yourapp.libs.vendor
ouyourapp.vendor.libs
Em suma, você poderia ter
yourapp/core/backends.py
yourapp/core/models/__init__.py
yourapp/core/models/users.py
yourapp/core/models/questions.py
yourapp/core/backends.py
yourapp/core/forms.py
yourapp/core/handlers.py
yourapp/core/management/commands/__init__.py
yourapp/core/management/commands/closepolls.py
yourapp/core/management/commands/removeduplicates.py
yourapp/core/middleware.py
yourapp/core/signals.py
yourapp/core/templatetags/__init__.py
yourapp/core/templatetags/polls_extras.py
yourapp/core/views/__init__.py
yourapp/core/views/users.py
yourapp/core/views/questions.py
yourapp/core/signals.py
yourapp/lib/utils.py
yourapp/lib/textanalysis.py
yourapp/lib/ratings.py
yourapp/vendor/backends.py
yourapp/vendor/morebusinesslogic.py
yourapp/vendor/handlers.py
yourapp/vendor/middleware.py
yourapp/vendor/signals.py
yourapp/tests/test_polls.py
yourapp/tests/test_questions.py
yourapp/tests/test_duplicates.py
yourapp/tests/test_ratings.py
ou qualquer outra coisa que ajude você; encontrar as interfaces necessárias e os limites o ajudarão.
fonte
O Django emprega um tipo ligeiramente modificado de MVC. Não há conceito de "controlador" no Django. O proxy mais próximo é uma "view", que tende a causar confusão com os convertidos do MVC, porque no MVC uma view é mais parecida com o "template" do Django.
No Django, um "modelo" não é meramente uma abstração de banco de dados. Em alguns aspectos, ele compartilha o dever com a "visão" do Django como o controlador do MVC. Ele contém todo o comportamento associado a uma instância. Se essa instância precisar interagir com uma API externa como parte de seu comportamento, ainda será o código do modelo. De fato, não é necessário que os modelos interajam com o banco de dados; portanto, é possível conceber modelos que existam inteiramente como uma camada interativa para uma API externa. É um conceito muito mais gratuito de "modelo".
fonte
No Django, a estrutura MVC é como Chris Pratt disse, diferente do modelo MVC clássico usado em outras estruturas, acho que a principal razão para fazer isso é evitar uma estrutura de aplicação muito rigorosa, como acontece em outras estruturas MVC como CakePHP.
No Django, o MVC foi implementado da seguinte maneira:
A camada de visualização é dividida em duas. As visualizações devem ser usadas apenas para gerenciar solicitações HTTP, elas são chamadas e respondem a elas. As visualizações se comunicam com o restante do seu aplicativo (formulários, modelos, classes personalizadas, em casos simples, diretamente com os modelos). Para criar a interface, usamos modelos. Os modelos são do tipo string para o Django, ele mapeia um contexto para eles, e esse contexto foi comunicado à view pelo aplicativo (quando a view pede).
A camada de modelo fornece encapsulamento, abstração, validação, inteligência e torna seus dados orientados a objetos (eles dizem que um dia o DBMS também fará). Isso não significa que você deve criar arquivos models.py enormes (na verdade, um conselho muito bom é dividir seus modelos em arquivos diferentes, colocá-los em uma pasta chamada 'models', criar um arquivo '__init__.py' neste pasta onde você importa todos os seus modelos e, finalmente, usa o atributo 'app_label' da classe models.Model). O modelo deve abstraí-lo da operação com dados, isso simplificará seu aplicativo. Você também deve, se necessário, criar classes externas, como "ferramentas" para seus modelos. Você também pode usar a herança nos modelos, configurando o atributo 'abstract' da classe Meta do seu modelo para 'True'.
Onde está o resto? Bem, pequenas aplicações web geralmente são uma espécie de interface para dados; em alguns casos de programas pequenos, usar visualizações para consultar ou inserir dados seria suficiente. Casos mais comuns usarão Forms ou ModelForms, que na verdade são "controladores". Isso não é senão uma solução prática para um problema comum e muito rápido. É o que um site costuma fazer.
Se o Forms não é adequado para você, você deve criar suas próprias classes para fazer a mágica, um exemplo muito bom disso é o aplicativo admin: você pode ler o código ModelAmin, na verdade ele funciona como um controlador. Não existe uma estrutura padrão, sugiro que você examine os aplicativos Django existentes, isso depende de cada caso. É isso que os desenvolvedores do Django pretendem, você pode adicionar a classe analisador xml, uma classe de conector API, adicionar o Celery para executar tarefas, torcer para um aplicativo baseado em reator, usar apenas o ORM, criar apenas um ORM, criar um serviço da Web, modificar o aplicativo de administração e muito mais. .. É sua responsabilidade criar código de boa qualidade, respeitar a filosofia MVC ou não, torná-lo baseado em módulo e criar suas próprias camadas de abstração. É muito flexível.
Meu conselho: leia o máximo de código possível, existem muitos aplicativos de django por aí, mas não os leve tão a sério. Cada caso é diferente, os padrões e a teoria ajudam, mas nem sempre, isso é uma ciência imprecisa, o django apenas fornece boas ferramentas que você pode usar para aliviar algumas dores (como interface de administrador, validação de formulário da web, i18n, implementação de padrões de observadores, tudo o mencionado anteriormente e outros), mas os bons designs vêm de designers experientes.
PS .: use a classe 'Usuário' do aplicativo auth (do django padrão), você pode criar, por exemplo, perfis de usuário ou, pelo menos, ler seu código, será útil para o seu caso.
fonte
Uma pergunta antiga, mas eu gostaria de oferecer minha solução de qualquer maneira. É baseado na aceitação de que os objetos de modelo também exigem alguma funcionalidade adicional, enquanto é difícil colocá-lo dentro de models.py . Lógica comercial pesada pode ser escrita separadamente, dependendo do gosto pessoal, mas eu pelo menos gosto do modelo para fazer tudo relacionado a si mesmo. Essa solução também suporta aqueles que gostam de ter toda a lógica colocada nos próprios modelos.
Como tal, criei um hack que me permite separar a lógica das definições de modelo e ainda obter todas as dicas do meu IDE.
As vantagens devem ser óbvias, mas isso lista algumas que observei:
Eu tenho usado isso com Python 3.4 e superior e Django 1.8 e superior.
app / models.py
app / logic / user.py
A única coisa que não consigo descobrir é como fazer meu IDE (PyCharm neste caso) reconhecer que o UserLogic é na verdade um modelo de usuário. Mas, como isso é obviamente um truque, fico feliz em aceitar o pequeno incômodo de sempre especificar o tipo de
self
parâmetro.fonte
Eu teria que concordar com você. Existem muitas possibilidades no django, mas o melhor lugar para começar é revisar a filosofia de design do Django .
Chamar uma API a partir de uma propriedade de modelo não seria o ideal, parece que faria mais sentido fazer algo assim na visualização e possivelmente criar uma camada de serviço para manter as coisas secas. Se a chamada para a API for sem bloqueio e a chamada for cara, o envio da solicitação a um trabalhador de serviço (um trabalhador que consome de uma fila) pode fazer sentido.
De acordo com a filosofia de design do Django, os modelos englobam todos os aspectos de um "objeto". Portanto, toda a lógica de negócios relacionada a esse objeto deve residir lá:
Os efeitos colaterais que você descreve são aparentes, a lógica aqui poderia ser melhor dividida em Querysets e gerentes. Aqui está um exemplo:
models.py
admin.py
fonte
Concordo principalmente com a resposta escolhida ( https://stackoverflow.com/a/12857584/871392 ), mas quero adicionar a opção na seção Fazendo consultas.
Pode-se definir classes QuerySet para modelos para fazer consultas de filtro e assim por diante. Depois disso, você pode fazer proxy dessa classe de conjunto de consultas para o gerente do modelo, como fazem as classes Manager e QuerySet integradas.
Embora, se você tivesse que consultar vários modelos de dados para obter um modelo de domínio, me parecesse mais razoável colocar isso em um módulo separado, como sugerido anteriormente.
fonte
Artigo mais abrangente sobre as diferentes opções com prós e contras:
Fonte: https://sunscrapers.com/blog/where-to-put-business-logic-django/
fonte
O Django foi projetado para ser facilmente usado para entregar páginas da web. Se você não estiver confortável com isso, talvez use outra solução.
Estou escrevendo as operações raiz ou comuns no modelo (para ter a mesma interface) e as outras no controlador do modelo. Se eu precisar de uma operação de outro modelo, importo seu controlador.
Essa abordagem é suficiente para mim e para a complexidade dos meus aplicativos.
A resposta de Hedde é um exemplo que mostra a flexibilidade do django e do próprio python.
Pergunta muito interessante de qualquer maneira!
fonte