Pular para o conteúdo principal

Active Record

Descrição​

O Active Record é um objeto que envolve uma linha de uma tabela ou view, encapsula o acesso ao banco e adiciona lógica de domínio sobre esses dados. Os campos do objeto correspondem diretamente às colunas, e o próprio objeto sabe se carregar, salvar e excluir.

Em essência, é um Row Data Gateway com lógica de negócio. Métodos de busca costumam ser estáticos ou ficar numa classe auxiliar, e o objeto reúne dados e comportamento, o que dá uma impressão de modelo de objetos sem uma camada de mapeamento separada.

Funciona bem enquanto o modelo de domínio é parecido com o esquema do banco; quando os dois divergem, o acoplamento passa a ser um problema.

Problemas que resolve​

  • Excesso de código de infraestrutura para persistir objetos de domínio simples.
  • Necessidade de uma camada de acesso a dados leve, sem mapeamento separado.
  • Duplicação entre classes de dados e classes de lógica quando o modelo é quase igual às tabelas.

Quando usar​

  • Quando a lógica de domínio é moderada: criação, leitura, atualização, exclusão e validações simples.
  • Quando o modelo de objetos pode seguir de perto o esquema do banco.
  • Para entregar rapidamente aplicações CRUD, como em Rails e Laravel.

Quando evitar​

  • Quando a lógica de negócio é complexa e exige heranças, coleções e relacionamentos que divergem do esquema.
  • Quando se quer testar o domínio sem banco de dados.
  • Quando o esquema muda com frequência e não deve afetar o modelo de domínio.

Trade-offs​

GanhoCusto / Risco
Muito simples e rápido de começarObjeto de domínio acoplado ao esquema e ao banco
Dados e comportamento juntos, fácil de entenderDificulta testes unitários sem banco
Pouco código de mapeamentoRefatorar esquema e modelo de forma independente é difícil
Bom para CRUDMistura responsabilidades (persistência e regras)
  • Acoplamento: alto entre domínio e esquema; mudanças em um afetam o outro.
  • Testabilidade: limitada, a menos que se use banco em memória ou dublês.
  • Complexidade: baixa no início, mas pode crescer conforme o domínio se torna rico.

Diagrama​

O Client usa diretamente Person, que mantém os dados de uma linha, contém a lógica de domínio (calculateAge) e acessa o Database por meio de save, delete e find.

Exemplo​

Uma loja tem produtos com regras simples, como aplicar desconto. A classe Product representa a linha e sabe se salvar, como nos modelos do Rails ActiveRecord, do Django ORM ou do Eloquent.

class Product:
def __init__(self, conn, id, name, price):
self.conn, self.id, self.name, self.price = conn, id, name, price

@classmethod
def find(cls, conn, id):
r = conn.execute(
"SELECT id, name, price FROM products WHERE id = ?", (id,)).fetchone()
return cls(conn, *r)

def apply_discount(self, percent): # lógica de domínio
self.price = round(self.price * (1 - percent / 100), 2)

def save(self):
self.conn.execute(
"UPDATE products SET name = ?, price = ? WHERE id = ?",
(self.name, self.price, self.id))

product = Product.find(conn, 7)
product.apply_discount(10)
product.save()

Padrões relacionados​

Referência​