Os ORMs permitem a criação de modelos de domínio avançados?


21

Depois de usar o Hibernate na maioria dos meus projetos há cerca de 8 anos, entrei em uma empresa que desencoraja seu uso e deseja que os aplicativos interajam apenas com o banco de dados através de procedimentos armazenados.

Depois de fazer isso por algumas semanas, não consegui criar um modelo de domínio avançado do aplicativo que estou começando a criar, e o aplicativo parece um script transacional (horrível).

Alguns dos problemas que encontrei são:

  • Não é possível navegar no gráfico de objetos, pois os procedimentos armazenados apenas carregam a quantidade mínima de dados, o que significa que, às vezes, temos objetos semelhantes com campos diferentes. Um exemplo é: temos um procedimento armazenado para recuperar todos os dados de um cliente e outro para recuperar informações da conta, além de alguns campos do cliente.
  • Muita lógica acaba nas classes auxiliares, portanto o código se torna mais estruturado (com entidades usadas como estruturas C antigas).
  • Código de andaime mais chato, pois não existe uma estrutura que extraia conjuntos de resultados de um procedimento armazenado e o coloque em uma entidade.

Minhas perguntas são:

  • alguém já passou por uma situação semelhante e não concordou com a abordagem do procedimento de armazenamento? o que você fez?
  • Existe um benefício real do uso de procedimentos armazenados? além do ponto bobo de "ninguém pode emitir uma tabela suspensa".
  • Existe uma maneira de criar um domínio rico usando procedimentos armazenados? Eu sei que existe a possibilidade de usar o AOP para injetar DAOs / Repositórios em entidades para poder navegar no gráfico de objetos. Não gosto dessa opção, pois é muito próxima do vodu.

Conclusão

Primeiro, obrigado a todos por suas respostas. A conclusão que cheguei é que os ORMs não permitem a criação de modelos Rich Domain (como algumas pessoas mencionaram), mas simplificam a quantidade de trabalho (geralmente repetitivo). A seguir, é apresentada uma explicação mais detalhada da conclusão, mas não é baseada em dados concretos.

A maioria dos aplicativos solicita e envia informações para outros sistemas. Para fazer isso, criamos uma abstração nos termos do modelo (por exemplo, um evento de negócios) e o modelo de domínio envia ou recebe o evento. O evento geralmente precisa de um pequeno subconjunto de informações do modelo, mas não de todo o modelo. Por exemplo, em uma loja online, um gateway de pagamento solicita algumas informações e o total para cobrar um usuário, mas não exige o histórico de compras, os produtos disponíveis e toda a base de clientes. Portanto, o evento possui um conjunto pequeno e específico de dados.

Se considerarmos o banco de dados de um aplicativo como um sistema externo, precisamos criar uma abstração que permita mapear as entidades do Modelo de Domínio para o banco de dados ( como NimChimpsky mencionou , usando um mapeador de dados). A diferença óbvia é que agora precisamos criar um mapeamento para cada entidade de modelo no banco de dados (um esquema herdado ou procedimentos armazenados), com a dor extra de que, como os dois não estão sincronizados, uma entidade de domínio pode mapear parcialmente para uma entidade de banco de dados (por exemplo, uma classe UserCredentials que contém apenas nome de usuário e senha é mapeada para uma tabela Usuários que possui outras colunas) ou uma entidade de modelo de domínio pode mapear para mais de uma entidade de banco de dados (por exemplo, se houver uma um mapeamento na tabela, mas queremos todos os dados em apenas uma classe).

Em um aplicativo com poucas entidades, a quantidade de trabalho extra pode ser pequena se não houver necessidade de transversalidade das entidades, mas aumenta quando há uma necessidade condicional de transversalidade das entidades (e, portanto, podemos implementar algum tipo de 'preguiçoso'). Carregando'). À medida que um aplicativo cresce para ter mais entidades, esse trabalho apenas aumenta (e eu tenho a sensação de que aumenta não linearmente). Minha suposição aqui é que não tentamos reinventar um ORM.

Um benefício de tratar o banco de dados como um sistema externo é que podemos codificar situações nas quais queremos 2 versões diferentes de um aplicativo em execução, nas quais cada aplicativo possui um mapeamento diferente. Isso se torna mais interessante no cenário de entregas contínuas para produção ... mas acho que isso também é possível com ORMs em menor grau.

Vou descartar o aspecto de segurança, com base em que um desenvolvedor, mesmo que ele não tenha acesso ao banco de dados, pode obter a maioria, senão todas as informações armazenadas em um sistema, apenas injetando código malicioso (por exemplo, Não acredito que esqueci de remover a linha que registra os detalhes do cartão de crédito dos clientes, prezado senhor! ).


Pequena atualização (6/6/2012)

Os procedimentos armazenados (pelo menos no Oracle) impedem fazer algo como entrega contínua com tempo de inatividade zero, pois qualquer alteração na estrutura das tabelas invalidará os procedimentos e acionadores. Portanto, durante o tempo em que o banco de dados está sendo atualizado, o aplicativo também ficará inativo. A Oracle fornece uma solução para essa redefinição baseada em edição , mas os poucos DBAs que perguntei sobre esse recurso mencionaram que ele estava mal implementado e não o colocaram em um banco de dados de produção.


Bem, obviamente você pode fazer o que o Hibernate faz e usar herança para gerar um objeto proxy dinâmico, o que permite recuperar o gráfico do objeto. Isso é extremamente hacky no SP: D
Max

Então, eu acabaria reinventando metade do hibernate, sem os mais de 10 anos de experiência que a equipe do hibernate tem :).
Augusto

1
Qualquer DBA deve impedir a exclusão de tabelas específicas por determinados usuários. Não importa como você tenta fazer isso.
21412 JeffO

1
Você pode dar uma olhada no Mybatis - ele pode fornecer o recurso que você precisa. É menos um ORM do que uma estrutura de mapeamento. Você pode escrever SQL da maneira que desejar e dizer ao Mybatis onde colocá-lo no seu modelo de objeto. Ele manipulará gráficos de objetos grandes com várias consultas, que se parecem com a situação que você tem (muitos procedimentos armazenados finos).
Michael K

1
@ August: Eu estive em uma situação semelhante, não devido ao uso de SPs, mas devido ao uso de uma estrutura de mapeamento proprietário que não suportava relacionamentos com objetos. Passamos dias escrevendo código que poderia ser escrito em minutos usando um ORM adequado. Eu nunca resolvi esse problema.
precisa

Respostas:


16

Seu aplicativo ainda deve ser modelado a partir dos princípios de design orientados a domínio. Se você usa um ORM, JDBC direto, chamar SPs (ou qualquer outra coisa) não deve importar . Espero que uma camada fina abstraia seu modelo dos SPs faça o truque neste caso. Como outro cartaz afirmou , você deve exibir os SPs e seus resultados como um serviço e mapear os resultados para o seu modelo de domínio.


Martijn, eu concordo que o aplicativo deve ser modelado usando os princípios DDD, mas o problema que estou enfrentando (e me diga se há uma solução !!) é que alguns procs armazenados retornam muito pouca informação para instanciar uma entidade DDD. Por favor, veja este comentário, onde expliquei um pouco mais sobre as informações que os procedimentos armazenados retornam. Eu poderia contornar isso, invocando mais de um proc armazenado e, por exemplo, recuperando todos os detalhes do usuário e depois invocando outro para recuperar todas as informações da conta, mas parece errado :).
Augusto

1
@ Augustus Bem ... você é desenvolvedor de aplicativos, então precisa decidir se faz sentido que um determinado objeto exista com determinados campos definidos como NULL. Se faz sentido (para determinada tarefa, por exemplo), deixe estar. Caso contrário, peça ao autor do SP para fornecer mais dados, para que você possa criar seus objetos.
Jacek Prucia

E, adicionando ao comentário de Jacek - é perfeitamente aceitável chamar 2 + procs armazenados, pense novamente neles como dois serviços remotos que você precisa chamar para criar seu modelo de domínio, nada de errado com isso :-).
Martijn Verburg

@ Martijn: Na minha experiência, uma camada fina não é suficiente. O código de mapeamento pode ser consideravelmente maior que a lógica de negócios subjacente.
kevin Cline

@ Kevin Cline - Bom ponto, ter colocado 'espero' na resposta :-)
Martijn Verburg

5

Existe um benefício real do uso de procedimentos armazenados?

No mundo financeiro (e nos locais onde a conformidade com Sarbanes-Oxley é necessária), você deve poder auditar os sistemas para garantir que eles façam o que deveriam. Nesses casos, é muito mais fácil garantir a conformidade quando todo o acesso a dados é realizado através de procedimentos armazenados. E quando todo o SQL ad-hoc é removido, é muito mais difícil ocultar as coisas. Para um exemplo de por que isso seria uma "coisa boa", refiro-o ao artigo clássico de Ken Thompson, Reflections on Trusting Trust .


sim um milhão de vezes sim! Você também precisa garantir que os usuários não possam fazer nada que não devam, incluindo não ter direitos diretos para tabelas e procs armazenados, o que ajuda tremendamente.
HLGEM 21/03/12

1
Eu trabalho para uma empresa pública e somos SOX. Pode ser que eu tenha pouco conhecimento em auditoria, mas não vejo a diferença entre fazer a auditoria no nível do banco de dados (via procs armazenados) ou no nível do aplicativo. Cada aplicativo deve ter seu próprio esquema de banco de dados e esse esquema só pode ser acessado pelo aplicativo, em vez de compartilhado entre aplicativos diferentes.
22312 Augusto

link quebrado ...
Alex R

@AlexR, link fixo
Tangurena 23/04

2

Os procedimentos armazenados são muito mais eficientes que o código SQL do lado do cliente. Eles pré-compilam o SQL no banco de dados, o que também permite realizar algumas otimizações.

Arquitetonicamente, um SP retornará os dados mínimos necessários para uma tarefa, o que é bom, pois significa que menos dados estão sendo transferidos. Se você tem essa arquitetura, precisa pensar no DB como um serviço (pense nele como um serviço da Web e cada SP é um método para chamar). Não deve ser um problema trabalhar com isso dessa maneira, enquanto um ORM o orienta a trabalhar com dados remotos como se fossem locais, o que o leva a introduzir problemas de desempenho se você não for cuidadoso.

Já estive em situações em que usamos SPs completamente, o banco de dados forneceu uma API de dados e a usamos. Esse aplicativo em particular era de escala muito grande e apresentava um desempenho incrível. Depois disso, não terei nada de ruim sobre SPs!

Há outra vantagem - os DBAs gravam todas as suas consultas SQL para você e lidam com toda a hierarquia relacional no banco de dados, para que você não precise.


3
gbjbaanb, a maior parte do que você disse é verdadeira para bancos de dados antigos. A maioria dos bancos de dados mais recentes recompila as consultas com frequência para decidir quais novas otimizações usar (mesmo que sejam produzidas armazenadas). Concordo com o que você disse sobre o uso do banco de dados como um sistema externo, mas também vejo muito trabalho, pois o aplicativo possui o banco de dados e ambos devem estar sincronizados o máximo possível. Por exemplo, com a nomeação de tabelas / classes e campos / colunas. Além disso, a abordagem de deixar os DBAs escreverem os procedimentos cheira a silos de desenvolvimento, em vez de ter uma equipe multidisciplinar.
Augusto

7
Os SPs nem sempre são mais eficientes e acho que entregar o SQL aos DBAs é um caminho ruim. Como especialista em domínio, o desenvolvedor deve saber quais dados eles querem obter e como obtê-los.
Martijn Verburg

1
Essa é uma boa resposta, mas, na minha experiência, a maioria dos clientes não precisa realmente dos ganhos de desempenho para controlar o acesso aos dados por meio de procedimentos armazenados, contra a inconveniência de fazer pleno uso de suas ferramentas ORM na camada de aplicativo. Na maioria das vezes, vejo essas decisões arquiteturais tomadas em lojas de software, onde eles precisam justificar os salários inchados de programadores de procedimentos armazenados de "barba grisalha" que não têm outras habilidades.
Maple_shaft

1
@Augusto the approach of letting the DBAs write the procedures smells like development silos+100 internets para você por essa jóia da verdade. Eu sempre vi isso como o caso em que o acesso aos dados era controlado por meio de procedimentos armazenados.
Maple_shaft

1
@maple_shaft: por que os DBAs que escrevem SPs não são considerados parte da equipe de desenvolvedores? Onde funciona, eles são codificadores especializados que conhecem muito bem esse aspecto do sistema, muito melhor do que a maioria dos desenvolvedores de uso geral. Este pode ser o problema que deu origem à popularidade das ORMs. Quero dizer, ninguém pensaria duas vezes antes de um designer fazer a GUI, então por que o ódio por um arquiteto de dados está fazendo o esquema?
precisa

2

O que acontece com frequência é que os desenvolvedores usam incorretamente seus objetos ORM como modelos de domínio.

Isso está incorreto e vincula seu domínio diretamente ao seu esquema de banco de dados.

O que realmente deveria ter são modelos de domínio separados, tão ricos quanto você quiser e usar a camada ORM separadamente.

Isso significa que você precisará mapear entre cada conjunto de objetos.


1
É uma boa idéia, mas para projetos menores, realmente começa a parecer um exagero. Essa abordagem também requer uma camada de conversão entre a camada de persistência do ORM e o modelo de domínio.
Maple_shaft

@maple_shaft concordou, e é isso que eu quis dizer com "mapeamento" :-)
ozz

@ Oz, a maneira como trabalhei é exatamente isso, as classes de entidade SÃO o modelo de domínio (e devo acrescentar com bastante sucesso). Concordo que ele vincula o modelo de domínio ao esquema, mas é exatamente isso que eu quero, pois uso convenção sobre configuração, e o bom efeito colateral é que, se eu vir um campo em uma entidade, não preciso pensar muito sobre o nome da tabela e coluna em que essas informações são armazenadas.
Augusto

@ August Eu também fiz isso! e como maple_shaft diz, é bom para aplicativos pequenos no estilo CRUD, mas há muitos problemas que o OP está descobrindo. Um exemplo pode ser o local em que você tem uma tabela de mapeamento de muitos para muitos, por exemplo: StudentClasses, que mapeia os alunos para suas classes e apenas contém StudentID e classID, você não necessariamente deseja mapear isso em seu domínio. Isso é apenas um exemplo rápido da minha cabeça.
ozz

2
@ Oz: Seu comentário parece contradizer a própria idéia de um ORM. Um ORM não "vincula seu domínio diretamente ao seu esquema de banco de dados". O ORM mapeia seu domínio para um esquema de banco de dados, sem a necessidade de uma camada DAO separada. Esse é o objetivo de um ORM. E a maioria dos ORMs lida com mapeamentos muitos para muitos, sem necessidade de modelo de domínio para a tabela de mapeamento.
precisa

1

Os objetos do seu domínio podem ser preenchidos da maneira que desejar, não é necessário usar o Hibernate. Eu acho que o termo apropriado é mapeador de dados . É bem possível que seus dados persistentes sejam uma estrutura completamente diferente dos objetos do seu domínio.


No momento, estamos usando mapeadores de dados, mas o problema é que eles armazenaram procs retornam um conjunto mínimo de dados, que às vezes não é suficiente para preencher um objeto (talvez devamos permitir que os procedimentos armazenados retornem mais informações). Por exemplo, um processo de armazenamento pode retornar um email de usuário, nome, sobrenome; enquanto outro adiciona o ID do usuário e um endereço. Como os dados são diferentes, estamos usando objetos diferentes para armazená-los, o que significa que temos diferentes classes de 'Usuário'. Estou tentando evitar o uso de herança aqui, porque acho que é um uso errado.
Augusto

@Augusto: Interfaces?
Kramii Restabelecer Monica 21/03

@ Karmii, não acho que as interfaces ajudem aqui, pois precisaríamos duplicar a lógica em diferentes classes. Ou podemos usar interfaces e, em seguida, delegar o processamento para uma classe auxiliar, mas isso não é realmente OO :(.
Augusto

1
@ August: Eu não entendo o problema: "procs armazenados retornam um conjunto mínimo de dados, o que às vezes não é suficiente para preencher um objeto". Assim, você altera o sproc ou cria outro e deixa o mapeador de dados fazer o mapeamento
NimChimpsky
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.