A orientação a objetos é valiosa especificamente porque esses tipos de cenários surgem e fornece ferramentas para projetar razoavelmente abstrações que permitem encapsular a complexidade.
A verdadeira questão aqui é: onde você encapsula essa complexidade?
Então, deixe-me recuar um momento e falar a que "complexidade" estou me referindo aqui. Seu problema (como eu o entendo; corrija-me se estiver errado) é um modelo de persistência que não é um modelo efetivamente utilizável para as tarefas que você precisa concluir com os dados. Pode ser eficaz e utilizável para outras tarefas, mas não para suas tarefas.
Então, o que fazemos quando temos dados que não apresentam um bom modelo para nossos meios?
Traduzir. É a única coisa que você pode fazer. Essa tradução é a 'complexidade' a que me refiro acima. Então, agora que aceitamos que vamos traduzir o modelo, precisamos decidir sobre alguns fatores.
Precisamos traduzir as duas direções? As duas direções serão traduzidas da mesma forma, como em:
(Tbl A, Tbl B) -> Obj X (leitura)
Obj X -> (Tbl A, Tbl B) (gravação)
ou as atividades de inserção / atualização / exclusão representam um tipo diferente de objeto, como você lê os dados como Obj X, mas os dados são inseridos / atualizados no Obj Y? Qual dessas duas maneiras você deseja seguir ou se nenhuma atualização / inserção / exclusão é possível são fatores importantes para onde você deseja colocar a tradução.
Para onde você traduz
De volta à primeira declaração que fiz nesta resposta; OO permite que você encapsule a complexidade, e o que eu me refiro aqui é o fato de que você não apenas deve, mas deve encapsular essa complexidade se quiser garantir que ela não vaze e se infiltre em todo o seu código. Ao mesmo tempo, é importante reconhecer que você não pode ter uma abstração perfeita; portanto, se preocupe menos com isso do que com uma muito eficaz e utilizável.
Novamente agora; seu problema é: onde você coloca essa complexidade? Bem, você tem escolhas.
Você pode fazer isso no banco de dados usando procedimentos armazenados. Isso tem a desvantagem de não jogar muito bem com ORMs, mas isso nem sempre é verdade. Os procedimentos armazenados oferecem alguns benefícios, incluindo o desempenho frequentemente. No entanto, os procedimentos armazenados podem exigir muita manutenção, mas cabe a você analisar seu cenário específico e dizer se a manutenção será mais ou menos que outras opções. Pessoalmente, sou muito habilidoso com procedimentos armazenados e, como tal, esse fato de talento disponível reduz a sobrecarga; Nunca subestime o valor de tomar decisões com base no que você não sabe. Às vezes, a solução abaixo do ideal pode ser mais ideal do que a solução correta, porque você ou sua equipe sabem como criá-la e mantê-la melhor do que a solução ideal.
Outra opção no banco de dados são visualizações. Dependendo do servidor do banco de dados, eles podem ser altamente ideais ou sub-ideais ou nem mesmo eficazes, uma das desvantagens pode ser o tempo de consulta, dependendo das opções de indexação disponíveis no seu banco de dados. As visualizações se tornam uma opção ainda melhor se você nunca precisar fazer nenhuma modificação de dados (inserir / atualizar / excluir).
Ultrapassando o banco de dados, você tem o modo de espera antigo de usar o padrão de repositório. Esta é uma abordagem testada pelo tempo, que pode ser muito eficaz. As desvantagens tendem a incluir a placa da caldeira, mas os repositórios bem-fatorados podem evitar parte disso, e mesmo quando resultam em quantidades infelizes da placa da caldeira, os repositórios tendem a ser um código simples, fácil de entender e manter, além de apresentar uma boa API /abstração. Além disso, os repositórios podem ser bons para a unidade de testabilidade, que você perde com as opções no banco de dados.
Existem ferramentas como o mapeador automático por aí que podem tornar plausível o uso de um ORM, onde é possível fazer a conversão entre o modelo de banco de dados do orm e os modelos utilizáveis, mas algumas dessas ferramentas podem ser complicadas para manter / entender o comportamento mais como mágica; embora eles criem um mínimo de código adicional, resultando em menos custo adicional de manutenção quando bem compreendidos.
A seguir, você está se afastando cada vez mais do banco de dados , o que significa que haverá uma quantidade maior de código que lidará com o modelo de persistência não traduzido, o que será genuinamente desagradável. Nesses cenários, você fala sobre colocar a camada de tradução na interface do usuário, o que parece que você está fazendo agora. Geralmente, é uma péssima idéia e decai terrivelmente com o tempo.
Agora vamos começar a falar loucamente .
A Object
não é a única abstração completa que existe. Houve uma profundidade de abstrações desenvolvidas ao longo dos muitos anos em que a ciência da computação foi estudada e mesmo antes disso a partir do estudo da matemática. Se vamos começar a ser criativos, vamos começar a falar sobre abstrações conhecidas disponíveis que foram estudadas.
Existe o modelo do ator.Essa é uma abordagem interessante, pois diz que tudo o que você faz é enviar mensagens para outro código que efetivamente delega todo o trabalho para esse outro código, o que é muito eficaz para encapsular a complexidade de todo o seu código. Isso pode funcionar desde que você envie uma mensagem a um ator dizendo "Eu preciso que o Obj X seja enviado para Y" e você tenha um receptáculo aguardando uma resposta no local Y que processa o Obj X. Você pode até enviar uma mensagem que instrua "Preciso do Obj X e da computação Y, Z executados" e então você nem precisa esperar; a tradução ocorre do outro lado da mensagem e você pode seguir em frente se não precisar ler o resultado. Isso pode ser um pequeno abuso do modelo de ator para seus propósitos, mas tudo depende;
Outro limite de encapsulamento são os limites do processo. Eles podem ser usados para segregar a complexidade de maneira muito eficaz. Você pode criar o código de conversão como um serviço da Web em que a comunicação é HTTP simples, usando SOAP, REST ou se você realmente deseja seu próprio protocolo (não sugerido). O STOMP não é um protocolo totalmente novo e ruim. Ou use um serviço daemon normal com um canal de memória divulgada localmente no sistema para se comunicar muito rapidamente novamente usando o protocolo que você escolher. Na verdade, isso tem alguns benefícios muito bons:
- Você pode ter vários processos em execução que fazem a conversão para suporte a versões mais antigas e mais recentes ao mesmo tempo, permitindo atualizar o serviço de tradução para divulgar um modelo de objeto V2 e, em seguida, atualizar posteriormente o código de consumo para trabalhar com o novo objeto. modelo.
- Você pode fazer coisas interessantes, como fixar o processo em um núcleo para desempenho, além de obter uma certa segurança de segurança nessa abordagem, tornando esse o único processo em execução com os privilégios de segurança para tocar esses dados.
- Você terá um limite muito forte ao falar sobre os limites do processo que permanecerão fixos, garantindo um vazamento mínimo de sua abstração por um longo tempo, porque escrever código no espaço de tradução não poderá ser chamado fora do espaço de tradução, pois eles não compartilhará o escopo do processo, garantindo um conjunto fixo de cenários de uso por contrato.
- Capacidade de atualizações assíncronas / sem bloqueio sendo mais simples.
Obviamente, os inconvenientes são mais manutenção do que o normalmente necessário, a sobrecarga da comunicação afetando o desempenho e a manutenção.
Há uma grande variedade de maneiras de encapsular a complexidade que podem permitir que essa complexidade seja colocada em lugares cada vez mais estranhos e curiosos em seu sistema. Usando formas de funções de ordem superior (muitas vezes falsificadas usando padrão de estratégia ou várias outras formas estranhas de padrões de objetos), você pode fazer algumas coisas muito interessantes.
É isso mesmo, vamos começar a falar sobre uma mônada. Você pode criar essa camada de tradução de maneira muito independente de pequenas funções específicas que executam as traduções independentes necessárias, mas oculta todas essas funções de tradução que não são visíveis e, portanto, dificilmente são acessíveis ao código externo. Isso tem o benefício de reduzir a dependência deles, permitindo que eles mudem facilmente sem afetar muito o código externo. Em seguida, você cria uma classe que aceita funções de ordem superior (funções anônimas, funções lambda, objetos de estratégia, porém é necessário estruturá-las) que funcionam em qualquer um dos bons objetos do tipo de modelo OO. Você então deixa o código subjacente que aceita essas funções executar literalmente usando os métodos de conversão apropriados.
Isso cria um limite em que toda a tradução não existe apenas do outro lado do limite, longe de todo o seu código; ele é usado apenas nesse lado, permitindo que o restante do seu código nem saiba nada sobre ele, exceto onde está o ponto de entrada para esse limite.
Ok, sim, isso realmente está falando loucura, mas quem sabe; você pode ser tão louco (sério, não faça mônadas com uma classificação de loucura abaixo de 88%, existe um risco real de lesão corporal).