Três Olhares sobre Arquitetura de Software: Fowler, Evans e Uncle Bob: 2.1 - Active Record: simplicidade que pode custar caro

Na primeira parte desta série exploramos como o domínio pode ser modelado, protegido e orquestrado. Mas todo domínio, por mais elegante que seja, precisa encarar uma realidade inescapável: os dados precisam ser persistidos. Não basta calcular, validar ou expressar regras de negócio; é preciso garantir que esse estado sobreviva ao tempo. É nesse momento que a arquitetura encontra a infraestrutura, e que padrões se tornam decisivos para equilibrar clareza do modelo e eficiência técnica.
Martin Fowler dedicou uma boa parte do seu catálogo de Patterns of Enterprise Application Architecture justamente a esse tema: Active Record, Data Mapper, Unit of Work, Identity Map, Lazy Load. Cada um deles enfrenta dilemas recorrentes: como salvar e recuperar o estado do domínio sem deixá-lo refém do banco de dados? Como coordenar mudanças de forma transacional? Como manter performance sem sacrificar consistência?
Eric Evans, no Domain-Driven Design, sempre reforçou que a persistência deve ser invisível ao domínio. Para ele, o modelo precisa refletir a linguagem do negócio, não o schema do banco. Repositórios, Application Services e agregados só cumprem bem seu papel quando o banco fica em segundo plano, um detalhe de implementação.
Robert C. Martin, em Clean Architecture, é ainda mais radical: o banco de dados é um detalhe de infraestrutura, nunca o centro da arquitetura. Ele insiste que o domínio e os casos de uso não devem ter dependência alguma de frameworks ou tecnologias de armazenamento. O dado pode estar em SQL, NoSQL, arquivo em disco ou até mesmo em memória; o que importa é que o domínio continue protegido.
Nesta segunda parte da série, vamos olhar para os padrões de persistência com esses três óculos: Fowler, Evans e Uncle Bob. Vamos ver onde eles brilham, onde tropeçam e que escolhas arquiteturais nos forçam a enfrentar. E, como sempre, vamos usar exemplos em C# para tornar o debate mais concreto.
Índice da Série
Parte 1 – A lógica do domínio
- 1.1 Transaction Script: do procedural ao domínio rico
- 1.2 Table Module: tabelas falam mais alto que objetos?
- 1.3 Domain Model: o coração da arquitetura em três vozes
- 1.4 Service Layer: orquestrando casos de uso
Parte 2 – Persistência e Infraestrutura
- 2.1 Active Record: simplicidade que pode custar caro
- 2.2 Data Mapper: separando domínio e banco de dados
- 2.3 Unit of Work: coordenando mudanças no domínio
- 2.4 Identity Map & Lazy Load: truques de performance e consistência
Parte 3 – Apresentação e Integração
- 3.1 MVC e Front Controller: a porta de entrada
- 3.2 Template View e Page Controller: quando a UI dita o jogo
- 3.3 Gateways e Mappers: defendendo o domínio
- 3.4 Mensageria e integração: eventos em três perspectivas
Active Record: simplicidade que pode custar caro
Introdução
Entre todos os padrões de persistência descritos por Martin Fowler, nenhum desperta tanto amor e ódio quanto o Active Record. Sua proposta é de uma simplicidade quase ingênua: um objeto que carrega seus dados e sabe como se salvar ou se carregar do banco. Nada de camadas intermediárias, nada de mapeadores complexos — apenas um “tudo em um” que parece resolver o problema com poucas linhas de código.
É justamente essa simplicidade que o tornou tão popular em frameworks, tutoriais e sistemas pequenos. Mas, quando o domínio amadurece e ganha complexidade, essa conveniência se transforma em fardo: regras de negócio se misturam com infraestrutura, o acoplamento cresce, a testabilidade desaparece e a linguagem do negócio se perde. Fowler o reconhece como útil em contextos limitados, Evans o vê como um obstáculo para um Domain Model rico, e Uncle Bob o condena como uma violação direta da separação entre negócio e infraestrutura.
Um padrão de duas faces
Fowler descreve o Active Record como uma solução legítima para sistemas em que a lógica de negócio é essencialmente CRUD. Em sistemas administrativos pequenos ou protótipos, ter objetos que representam linhas de tabela e sabem se salvar reduz esforço, facilita onboarding de desenvolvedores e acelera entregas. “É simples e funciona” e, às vezes, isso basta.
Mas Eric Evans, em Domain-Driven Design, nos lembra que, quando o domínio é o coração do sistema, misturar persistência e regras de negócio é um erro conceitual. Uma entidade que sabe calcular descontos e, ao mesmo tempo, abrir uma conexão com o banco, está carregando responsabilidades de naturezas diferentes. Isso dissolve a linguagem ubíqua e empurra o sistema para o que Evans chamou de Anemic Domain Model: classes que representam dados, mas sem comportamento expressivo. O código passa a refletir mais a estrutura da base de dados do que a realidade do negócio.
Robert C. Martin vai além. Na Clean Architecture, ele defende que o domínio deve permanecer puro, protegido no núcleo, isolado de frameworks e detalhes externos. Os círculos internos não podem depender de nada que esteja fora deles. O Active Record fura essa barreira ao trazer SQL e lógica de acesso a dados para dentro das entidades. O resultado é acoplamento: o domínio não pode ser testado sem banco, não pode ser usado em outro contexto sem arrastar junto a infraestrutura e muda a cada alteração no schema. Para Uncle Bob, esse é o oposto de uma arquitetura duradoura.
Exemplo prático em C#
Um exemplo simples ajuda a visualizar:
using System;
using System.Data.SqlClient;
// Active Record style entity
public class Customer
{
public int Id { get; private set; }
public string Name { get; private set; }
public Customer(int id, string name)
{
Id = id;
Name = name;
}
// Persistence logic inside the entity
public void Save()
{
using var conn = new SqlConnection("connection-string");
conn.Open();
using var cmd = new SqlCommand("INSERT INTO Customers (Id, Name) VALUES (@Id, @Name)", conn);
cmd.Parameters.AddWithValue("@Id", Id);
cmd.Parameters.AddWithValue("@Name", Name);
cmd.ExecuteNonQuery();
}
public static Customer Load(int id)
{
using var conn = new SqlConnection("connection-string");
conn.Open();
using var cmd = new SqlCommand("SELECT Id, Name FROM Customers WHERE Id = @Id", conn);
cmd.Parameters.AddWithValue("@Id", id);
using var reader = cmd.ExecuteReader();
if (reader.Read())
{
return new Customer(reader.GetInt32(0), reader.GetString(1));
}
throw new InvalidOperationException("Customer not found.");
}
}
A classe é pequena e clara, mas seu cheiro é forte: Customer conhece detalhes do banco, mistura lógica de domínio com infraestrutura e é difícil de testar. Imagine que queremos validar regras de fidelidade do cliente. Onde colocamos essa lógica? No mesmo objeto que abre SqlConnection? Em pouco tempo, a entidade vira um Frankenstein, carregando responsabilidades incompatíveis.
Um simples teste unitário já mostra o problema:
// Em teoria queremos apenas validar lógica de negócio...
var customer = new Customer(1, "Alice");
// ... mas qualquer teste real acaba pedindo banco de dados.
customer.Save(); // side-effect pesado e indesejado
Aqui, o domínio não é testável sem banco. A infraestrutura se tornou parte inseparável da regra de negócio.
O preço da conveniência
Fowler é pragmático: em alguns cenários, Active Record é suficiente. O problema não está no padrão em si, mas no mismatch entre problema e solução. Se o problema é simples, Active Record brilha. Mas se o domínio precisa refletir regras complexas, o padrão se torna um fardo.
Evans mostraria que, nesse caso, não temos um Domain Model, mas apenas uma camada de dados disfarçada de domínio. É a volta ao procedural, ainda que com objetos. Uncle Bob lembraria que, ao permitir isso, estamos comprometendo o futuro do sistema: mais frágil, difícil de testar, altamente acoplado. É exatamente o oposto do que buscamos em uma arquitetura limpa.
Conclusão
O Active Record é como um remédio de efeito rápido: ótimo para sintomas leves, perigoso quando o problema é mais profundo. Fowler o vê como aceitável em contextos simples. Evans o rejeita quando queremos modelos ricos. Uncle Bob o condena por comprometer a independência do core.
Isso nos leva a uma reflexão prática: quando você adota Active Record, está escolhendo conscientemente abrir mão de expressividade e de independência em troca de simplicidade inicial. É uma escolha que pode fazer sentido em protótipos, CRUDs descartáveis e sistemas auxiliares. Mas se o software é estratégico e precisa durar, esse atalho vira uma armadilha.
No próximo artigo, exploraremos justamente o contraponto: o Data Mapper, que tenta preservar a pureza do modelo ao custo de mais disciplina e complexidade. Será que vale pagar esse preço? Esse será o tema da nossa próxima discussão.