Quais são as desvantagens do padrão ActiveRecord?

30

Estou curioso para saber quais são as desvantagens de usar o padrão ActiveRecord para acessar dados / objetos de negócios. A única coisa em que consigo pensar é que viola o Princípio da Responsabilidade Única, mas o padrão de RA é comum o suficiente para que esse motivo por si só não pareça "bom o suficiente" para justificar não usá-lo (é claro, meu A visualização pode ser distorcida, pois muitas vezes nenhum código com o qual trabalho segue nenhum dos princípios do SOLID).

Pessoalmente, eu não sou fã do ActiveRecord (com exceção de escrever um aplicativo Ruby on Rails, onde o AR parece "natural") porque parece que a classe está fazendo muito e o acesso a dados não deve depender da própria classe lidar. Prefiro usar Repositórios que retornam objetos de negócios. A maior parte do código com o qual trabalho tende a usar uma variação do ActiveRecord, na forma de (não sei por que o método é booleano):

public class Foo
{
    // properties...

    public Foo(int fooID)
    {
        this.fooID = fooID;
    }

    public bool Load()
    {
        // DB stuff here...
        // map DataReader to properties...

        bool returnCode = false;
        if (dr.HasRows)
            returnCode = true;

        return returnCode;
    }
}

ou, às vezes, a maneira mais "tradicional" de ter um public static Foo FindFooByID(int fooID)método para os buscadores e algo semelhante ao de public void Save()salvar / atualizar.

Entendo que o ActiveRecord normalmente é muito mais simples de implementar e usar, mas parece um pouco simples demais para aplicativos complexos e você pode ter uma arquitetura mais robusta encapsulando sua lógica de acesso a dados em um Repositório (sem mencionar que é mais fácil trocar estratégias de acesso a dados, por exemplo, talvez você use Procs armazenados + DataSets e queira mudar para LINQ ou algo assim)

Então, quais são as outras desvantagens desse padrão que devem ser consideradas ao decidir se o ActiveRecord é o melhor candidato para o trabalho?

Wayne Molina
fonte

Respostas:

28

A principal desvantagem é que suas "entidades" estão cientes de sua própria persistência, o que leva a muitas outras decisões ruins de design.

O outro problema é que os kits de ferramentas de registro mais ativos basicamente mapeiam 1 para 1 para os campos da tabela com zero camadas de indireção. Isso funciona em pequenas escalas, mas desmorona quando você tem problemas mais difíceis de resolver.


Bem, informar seus objetos sobre a persistência deles significa que você precisa fazer coisas como:

  • facilmente ter conexões de banco de dados disponíveis em qualquer lugar. Isso normalmente leva a códigos desagradáveis ​​ou a algum tipo de conexão estática que é atingida de qualquer lugar.
  • seus objetos tendem a parecer mais com SQL do que objetos.
  • difícil fazer qualquer coisa no aplicativo desconectado porque o banco de dados é tão arraigado.

E acaba por haver uma série de outras más decisões.

Wyatt Barnett
fonte
2
você pode elaborar "outras decisões ruins de design"?
Kevin Cline
2
Obrigado. Não encontrei esses problemas no desenvolvimento do Ruby on Rails. Ainda é possível testar o comportamento e a persistência separadamente. A IMO que separa persistência e comportamento tem pouco valor prático.
kevin Cline
@ Kevin: essas coisas são menos uma desvantagem com recursos de rubi como mixins e digitação de pato. Com linguagens estáticas - como C #, que é o que o OP usou em sua pergunta - é um pouco mais difícil separar as duas.
Wyatt Barnett
@Wayne: para mim, as classes são apenas caixas para colocar métodos - eu posso separar a lógica de negócios da persistência colocando-as em classes separadas, ou posso separá-las conceitualmente, certificando-me de que os métodos de negócios não funcionem. O. Em idiomas com pouco suporte à delegação (por exemplo, Java), isso economiza uma quantidade enorme de código. OTOH, estou entrando no Hibernate para estar completamente errado.
Kevin cline
4
Eu acrescentaria duas coisas; 1. O acoplamento com o mecanismo de persistência torna o código difícil, se não impossível, para realizar o teste unitário adequadamente. 2. O Active Record cola seu código nos relacionamentos em um mecanismo de persistência centralizado, tornando muito difícil dividir seu monólito, se você decidir.
Istepaniuk
15

A maior desvantagem do registro ativo é que o seu domínio geralmente fica fortemente associado a um mecanismo de persistência específico. Caso esse mecanismo exija uma mudança global, talvez da persistência baseada em arquivo para baseada em banco de dados ou entre estruturas de acesso a dados, TODAS as classes que implementam esse padrão podem mudar. Dependendo do idioma, estrutura e design, mesmo algo tão simples quanto alterar onde o banco de dados está localizado ou quem é o "proprietário", pode ser necessário percorrer todos os objetos para atualizar os métodos de acesso a dados (isso é incomum na maioria dos idiomas que fornecem acesso fácil para configurar arquivos com cadeias de conexão).

Também geralmente requer que você se repita. A maioria dos mecanismos de persistência possui muito código comum para conectar-se a um banco de dados e iniciar uma transação. O DRY (não se repita) lhe diria como codificador para centralizar essa lógica.

Também torna as operações atômicas complicadas. Se um grupo de objetos precisar ser salvo de maneira tudo ou nada (como uma fatura e suas linhas de fatura e / ou entradas de cliente e / ou GL), um objeto deve conhecer todos esses outros objetos e controlar sua persistência ( que amplia o escopo do objeto de controle; grandes registros interconectados podem facilmente se tornar "objetos divinos" que sabem tudo sobre suas dependências) ou o controle sobre toda a transação deve ser tratado de fora do domínio (e, nesse caso, por que você está usando AR?)

Também está "errado" de uma perspectiva orientada a objetos. No mundo real, uma fatura não sabe como se arquivar. Por que um objeto de código de fatura saberia como se salvar no banco de dados? Obviamente, a adesão excessivamente religiosa a "objetos deve apenas modelar o que seus colegas do mundo real podem fazer" levaria a modelos de domínio anêmicos (uma fatura também não sabe como calcular seu próprio total, mas divide o cálculo desse total para outro objeto é geralmente considerado uma má idéia).

KeithS
fonte
No Ruby, onde a persistência pode ser definida ou abstraída por trás de uma palavra-chave ou comando muito simples, provavelmente é um problema menor. No .NET, que requer mais LoC para configurar vários mecanismos de persistência, geralmente é redundante ter uma conexão SQL para cada objeto, especialmente se vários objetos devem ser salvos em uma transação atômica. A conexão com o banco de dados tem a mesma aparência, esteja você salvando uma fatura ou um cliente. Você seria ou abstrato o material comum em um comum classe base para todos os ActiveRecord objetos em sua base de código, ou extraí-lo para sua própria classe (um repositório)
Keiths
você pode fornecer exemplos de uso do Ruby ActiveRecord que ilustram seus pontos? Não tive esses problemas, mas meu aplicativo era relativamente pequeno. Eu poderia escrever migrações no Ruby e implantá-las em diferentes implementações de banco de dados. Eu descobri que o ActiveRecord do Ruby era muito útil para eliminar a repetição, então não vejo por que você afirma que o uso do ActiveRecord teria o efeito oposto. Não tive problemas com o salvamento: apenas modifiquei o modelo de objeto e deixei o AR atualizar o banco de dados para refletir o modelo de objeto.
Kevin cline
2

A desvantagem básica é que isso torna seu modelo de domínio complexo, pois não apenas contém a lógica de negócios, mas também informações de persistência.

Portanto, a solução é fazer uso da implementação do ORM do Data Mapper . Isso separa a camada de persistência e agora estamos mais centrados na lógica de negócios da entidade. A doutrina é o ORM do Mapeador de Dados .

Mas essa abordagem também tem alguma complexidade. Para a consulta agora, você depende muito do Data Mapper, cria um ambiente orientado à consulta. Para simplificá-lo, outra camada é introduzida entre o Modelo de Domínio e o Mapeador de Dados, chamado Repositório .

Resumo do repositório da camada de persistência. Faz sentido da programação orientada a objetos no sentido, é uma coleção de todos os objetos do mesmo tipo (como todas as entidades armazenadas na tabela do banco de dados) e você pode executar operações nelas como operação de coleção, adicionar , remover . contém etc.

Por exemplo, para Entidade do usuário , haverá UserRepository que representa a coleção do mesmo tipo de objetos de usuário (armazenados na tabela de usuários) sobre os quais você pode executar a operação. Para consultar a tabela de usuários, ele usa o User Data Mapper, mas abstraiu para o modelo de domínio User .

O padrão do repositório é o tipo de Camada de Acesso a Dados , outro é o único objeto de acesso a dados. Diferenças O repositório possui o recurso Raiz Agregada

palash140
fonte
Os padrões de repositório e DAO diferem, DAO é acesso geral a dados, Repositório é para persistência da coleção de todos os objetos do mesmo tipo. Ou seja, todos os repositórios devem ter a mesma interface (como matriz ou lista). Outro rei dos métodos não pertence ao Repositório. DAO é de nível inferior, o Repositório pode usar DAO. Frequentemente, programadores (especialmente PHP) usam o Repositório como DAO, mas não está correto.
xmedeko 18/11