Modelos espessos vs. Lógica de negócios, onde você faz a distinção?

16

Hoje, entrei em um debate acalorado com outro desenvolvedor da minha organização sobre onde e como adicionar métodos às classes mapeadas do banco de dados. Utilizamos sqlalchemye grande parte da base de código existente em nossos modelos de banco de dados é pouco mais que um conjunto de propriedades mapeadas com um nome de classe, uma tradução quase mecânica de tabelas de banco de dados em objetos python.

No argumento, minha posição era de que o principal valor do uso de um ORM era o fato de você poder anexar comportamentos e algoritmos de baixo nível às classes mapeadas. Os modelos são primeiro as classes e secundariamente persistentes (eles podem ser persistentes usando xml em um sistema de arquivos, você não precisa se preocupar). Sua opinião era de que qualquer comportamento é "lógica de negócios" e necessariamente pertence a qualquer lugar, exceto no modelo persistente, que deve ser usado apenas para persistência do banco de dados.

Certamente acho que existe uma distinção entre o que é lógica de negócios e deve ser separado, pois ele tem algum isolamento do nível mais baixo de como isso é implementado e lógica de domínio, que acredito ser a abstração fornecida pelas classes de modelo discuti no parágrafo anterior, mas estou tendo dificuldade em entender o que é isso. Tenho uma noção melhor do que pode ser a API (que, no nosso caso, é HTTP "ReSTful"), pois os usuários invocam a API com o que desejam fazer , diferente do que têm permissão para fazer e como ela é feito.


tl; dr: Que tipos de coisas podem ou devem seguir um método em uma classe mapeada ao usar um ORM e o que deve ser deixado de fora para viver em outra camada de abstração?

SingleNegationElimination
fonte
Parece-me que foram duas questões discutidas ao mesmo tempo, a questão dos modelos de persistência são as classes primeiro e secundariamente persistentes (elas podem ser persistentes usando xml em um sistema de arquivos, você não precisa se preocupar). e o motivo do código. Normalmente, as coisas ficam claras, pelo menos para mim, quando me pergunto por que o código está escrito, que requisito força esse código. É o requisito do cliente sobre como o programa funcionará ou é de que maneira escolhemos implementá-lo. Para mim, a primeira é a lógica de negócios e a segunda o que você chama de lógica de domínio. Como isso ajuda.

Respostas:

10

Eu estou principalmente com você; seu colega parece estar argumentando pelo antipadrão do modelo de domínio anêmico ou pela duplicação do modelo em um "modelo de persistência" sem nenhum benefício óbvio (estou trabalhando em um projeto Java onde isso foi feito, e é uma dor de cabeça maciça de manutenção, como significa três vezes o trabalho sempre que algo muda no modelo).

Que tipos de coisas podem ou devem seguir um método em uma classe mapeada ao usar um ORM e o que deve ser deixado de lado para viver em outra camada de abstração?

Regra geral: a classe deve conter lógica que descreva fatos básicos sobre os dados que são verdadeiros em todas as circunstâncias. A lógica específica de um caso de uso deve estar em outro lugar. Um exemplo é a validação, há um artigo interessante de Martin Fowler, onde ele afirma que deve ser considerado dependente do contexto.

Michael Borgwardt
fonte
+1, para a segunda parte da sua resposta. Quanto à primeira parte, tenho certeza de por que você diz que o modelo de domínio anêmico exige muito trabalho "extra" em caso de mudança. Meu entendimento é que a mudança ocorrerá de qualquer maneira, exceto em várias classes diferentes (o que é meio ruim), mas é quase a mesma quantidade de mudança; Eu acho?
NoChance
1
@Emmad Kareem: O comentário foi sobre ter persistência spearate e modelos de domínio. Adicionar uma propriedade ao modelo exige adicioná-la a duas classes de modelo (em vez de uma), bem como a quaisquer mapas entre eles (que poderiam, em teoria, ser automáticos, mas geralmente o asshat que achou uma boa idéia ter um separado "modelo de persistência" decide justificar essa separação, tornando-as diferentes, por exemplo, tipos de dados que mais se assemelham ao modelo de tipo de banco de dados), de modo que é 2 + X vezes a quantidade de alteração, com X variando entre 0 e horas de produtividade perdida devido à obscuridade problemas de mapeamento.
Michael Borgwardt
3

Essa é uma decisão que realmente depende do tamanho e da escala previstos do que você está desenvolvendo. A abordagem mais rígida é limitar os tipos ORM a um componente de acesso a dados e usar POCOs em uma biblioteca comum como tipos referenciados e usados ​​por todas as camadas. Isso permitiria a separação física futura, bem como a separação lógica. Você também pode decidir que uma camada adicional deve existir entre a interface do usuário e a camada de lógica de negócios. Isso geralmente é chamado de camada Fachada ou Interface de negócios. Essa camada adicional é onde mora o seu "código de casos de uso". O código individual fracamente acoplado é chamado pela camada Facade / BI (por exemplo, Facade possui uma função ProcessOrder () que chama a lógica de negócios 1: M vezes para executar todas as etapas necessárias para realmente processar o pedido).

No entanto, tudo isso foi dito: muitas vezes essa quantidade de arquitetura é simplesmente um exagero desnecessário. Por exemplo, codifique especificamente para um site simples em que você não tem a intenção de empacotar seus componentes para reutilização. É perfeitamente válido criar um site MVC e usar objetos EF para esse tipo de solução. Se o site precisar ser expandido posteriormente, você poderá analisar o agrupamento ou um processo frequentemente perdido chamado "refatoração".

Sean Chase
fonte
3

Lembre ao seu colega que você não precisa arquitetar demais os modelos como se fosse um projeto Java. Quero dizer, comparar dois objetos persistentes é comportamento, mas não é especificado pela camada de persistência. Portanto, a questão das 6 cervejas é: por que classes completamente independentes descrevem algo sobre a mesma coisa? Certamente, a persistência é um aspecto grande o suficiente de um modelo para ser tratado separadamente, mas não o suficiente para garantir que ele seja tratado de maneira distinta de todo o resto. Se você dirige seu carro, lava ou rebenta, você manipula seu carro o tempo todo.

Então, por que não apenas compor todos esses aspectos diferentes em uma única classe de modelo? Você precisa de vários métodos de classe que lidam com objetos persistentes - coloque-os em uma classe; você tem vários métodos de instância que lidam com validação - coloque-os em outro. Finalmente, misture os dois e pronto! Você conseguiu uma representação de modelo inteligente, autoconsciente e totalmente contida.

Filip Dupanović
fonte
1

Além de outras respostas, preste atenção aos cavehats ocultos ao usar modelos de domínio avançados com um ORM.

Tive problemas ao injetar serviços polimórficos nas classes de modelo persistentes ao tentar obter algo como o seguinte pseudocódigo:

Person john = new Person('John Doe')
Organisation company = organisation_repository.find('some id')
Employee our_collegue_john = company.hire(john)

Nesse caso, uma organização pode exigir uma HRServicedependência de construtor (por exemplo). Você geralmente não pode controlar facilmente a instanciação de suas classes de modelo ao usar um ORM.

Eu estava usando o Doctrine ORM e o contêiner de serviço do Symfony. Eu tive que monitorar o ORM de maneira não tão elegante e não tive escolha a não ser separar persistência e modelos de negócios. Ainda não tentei com sqlachemy, pensei. O Python pode ser mais flexível que o PHP para essas coisas.

abstrus
fonte