Pular para o conteúdo principal

Row Data Gateway

Descrição​

O Row Data Gateway é um objeto que atua como gateway para um único registro de uma fonte de dados, com uma instância por linha. Sua interface espelha a estrutura do registro: há um campo para cada coluna, de modo que o código cliente o usa como um objeto comum.

Todo o acesso ao banco fica escondido dentro do gateway, que sabe se inserir, atualizar e excluir. Normalmente ele é acompanhado de uma classe de busca (finder) que carrega os gateways a partir de consultas. Ao contrário do Active Record, o gateway não contém lógica de domínio: apenas dados e acesso ao banco.

Isso mantém o código de persistência fora dos objetos de domínio em memória, simplificando a lógica de negócio e isolando variações de SQL entre bancos.

Problemas que resolve​

  • Lógica de negócio misturada a SQL e a detalhes de acesso a dados.
  • Testes lentos que dependem do banco para exercitar regras de negócio.
  • Necessidade de suportar variações de SQL entre diferentes bancos.
  • Código procedural que precisa manipular registros como objetos.

Quando usar​

  • Com Transaction Script, quando a lógica prefere manipular registros como objetos.
  • Quando o formato do registro é estável e quase igual à tabela.
  • Quando o código gerado automaticamente a partir do esquema pode produzir os gateways.

Quando evitar​

  • Quando há um Domain Model: o gateway vira uma camada redundante entre o modelo e o banco.
  • Quando se tem muitas tabelas e o esforço de manter um gateway e um finder por tabela não compensa.
  • Quando a lógica de domínio começa a migrar para o gateway, caracterizando um Active Record disfarçado.

Trade-offs​

GanhoCusto / Risco
Acesso ao banco isolado em um objeto por linhaUma classe por tabela, com muito código repetitivo
Lógica de negócio sem SQLPode ser redundante diante de um Domain Model
Facilita testes com dublês dos gatewaysExige um finder separado para consultas
Permite variar SQL entre bancosTentação de pôr lógica de domínio nele
  • Acoplamento: o cliente depende da interface do registro, não do SQL.
  • Testabilidade: boa, pois é fácil substituir o gateway por um objeto em memória.
  • Complexidade: moderada, por causa da quantidade de classes; geração de código ajuda.

Diagrama​

O TransactionScript usa o PersonFinder para obter instâncias de PersonGateway, cada uma representando uma linha, e as persiste chamando seus métodos insert, update e delete.

Exemplo​

Um script de cadastro de clientes manipula um registro por vez. Cada linha da tabela customers vira um CustomerGateway.

class CustomerGateway {
constructor(
private db: Db,
public id: number | null,
public name: string,
public email: string,
) {}

async insert(): Promise<void> {
this.id = await this.db.insert(
"INSERT INTO customers (name, email) VALUES (?, ?)",
[this.name, this.email]);
}

async update(): Promise<void> {
await this.db.exec(
"UPDATE customers SET name = ?, email = ? WHERE id = ?",
[this.name, this.email, this.id]);
}
}

class CustomerFinder {
constructor(private db: Db) {}
async find(id: number): Promise<CustomerGateway> {
const r = await this.db.one("SELECT * FROM customers WHERE id = ?", [id]);
return new CustomerGateway(this.db, r.id, r.name, r.email);
}
}

Padrões relacionados​

  • Table Data Gateway — um objeto por tabela em vez de um por linha.
  • Active Record — igual em estrutura, mas incorpora a lógica de domínio.
  • Data Mapper — separa totalmente objetos de domínio e persistência.
  • Transaction Script — uso mais comum do Row Data Gateway.
  • Gateway — padrão base do qual este é uma especialização.
  • Identity Map — evita carregar a mesma linha em gateways duplicados.

Referência​