Pular para o conteúdo principal

Data Mapper

Descrição​

O Data Mapper é uma camada de acesso a dados que transfere dados entre objetos em memória e o banco, mantendo ambos independentes entre si e do próprio mapper. Os objetos de domínio não sabem que o banco existe: não contêm SQL nem conhecem o esquema.

Modelos de objetos têm coleções, herança e associações que raramente casam com tabelas relacionais. O mapper cuida da tradução: carrega linhas e monta objetos, e percorre objetos para gravar de volta, resolvendo diferenças de estrutura. Para o domínio, o mapper fica oculto atrás de uma interface.

É a base de ORMs como Hibernate, Entity Framework e SQLAlchemy, e costuma vir acompanhado de Unit of Work, Identity Map e Lazy Load.

Problemas que resolve​

  • Domínio acoplado ao esquema do banco e a SQL.
  • Impedância objeto-relacional: herança, coleções e associações sem equivalente direto em tabelas.
  • Dificuldade de evoluir modelo de objetos e esquema de forma independente.
  • Testes de domínio que precisam do banco.

Quando usar​

  • Com um Domain Model rico, cuja estrutura diverge do esquema.
  • Quando modelo e banco evoluem em ritmos diferentes.
  • Quando se quer testar regras de negócio sem banco.
  • Quando o esquema é herdado e não pode ser alterado.

Quando evitar​

  • Em aplicações CRUD simples, em que Active Record resolve com muito menos código.
  • Quando a equipe não tem experiência nem ferramenta de ORM e o custo de construir um mapper não compensa.
  • Quando a lógica é procedural e baseada em tabelas.

Trade-offs​

GanhoCusto / Risco
Domínio independente do bancoMais camadas e mais código de mapeamento
Modelo e esquema evoluem separadamenteCurva de aprendizado e configuração
Facilita testes unitários do domínioPode ocultar consultas ineficientes (N+1)
Suporta heranças e associações complexasConstruir o próprio mapper é trabalhoso; usa-se um ORM
  • Acoplamento: baixo entre domínio e persistência; o acoplamento fica concentrado no mapper.
  • Consistência: costuma ser combinado com Unit of Work e Identity Map para gerenciar mudanças e identidade.
  • Complexidade: alta; compensa em domínios complexos.

Diagrama​

O Person não conhece o banco nem o PersonMapper. O mapper lê e grava no Database e converte linhas em objetos Person, e vice-versa.

Exemplo​

Uma aplicação de RH tem a entidade Employee com regras de negócio, e a tabela employees tem colunas diferentes. O mapper faz a tradução e o domínio permanece puro.

class Employee { // domínio puro
private final String name;
private final LocalDate hiredAt;
Employee(String name, LocalDate hiredAt) { this.name = name; this.hiredAt = hiredAt; }
int yearsOfService() { return Period.between(hiredAt, LocalDate.now()).getYears(); }
}

class EmployeeMapper {
private final JdbcTemplate jdbc;
EmployeeMapper(JdbcTemplate jdbc) { this.jdbc = jdbc; }

Employee find(long id) {
return jdbc.queryForObject(
"SELECT full_name, hire_date FROM employees WHERE id = ?",
(rs, n) -> new Employee(rs.getString("full_name"),
rs.getDate("hire_date").toLocalDate()),
id);
}

void insert(Employee e) { /* INSERT a partir dos campos de Employee */ }
}

No Hibernate e no Entity Framework, o mapeamento é configurado por metadados em vez de escrito à mão.

Padrões relacionados​

  • Active Record — alternativa mais simples em que o objeto sabe se persistir.
  • Row Data Gateway — gateway por linha, sem lógica de domínio.
  • Table Data Gateway — gateway por tabela, usado por lógicas tabulares.
  • Domain Model — o modelo que o Data Mapper mantém independente do banco.
  • Unit of Work — controla quais objetos mudaram e coordena a gravação.
  • Identity Map — garante um único objeto por linha carregada.
  • Lazy Load — adia o carregamento de associações.
  • Repository — oferece ao domínio uma visão de coleção sobre o mapper.
  • Mapper — padrão base do qual este é uma especialização.
  • Metadata Mapping — mapeamento descrito por metadados em ORMs.

Referência​