Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter

Migrar de MySQL para PostgreSQL não precisa reescrever o serviço. O Adapter isola o plugin atrás de um contrato que a regra de negócio entende.

Com o padrão Adapter, isolamos a regra de negócio da dependência concreta.

Cenário inicial

Temos um backend com CRUD simples de usuário: criar, editar, recuperar e deletar por endpoints na API. Os dados vão para um banco qualquer — digamos MySQL — e a estrutura nasce como Controller → Service → Database. Até aqui, o time está confortável.

Até alguém decidir: na semana que vem migramos de MySQL para PostgreSQL. A partir daí a casa cai para tecnologia. Além de reestruturar o banco, o time precisa caçar referências ao MySQL: inserções, conexão, queries espalhadas.

Na maioria das vezes isso atrasa entrega, reduz qualidade, pula testes e introduz bugs. Seria melhor diminuir a dependência entre a regra de negócio e quem executa a operação específica — no caso, o banco. O Adapter ajuda a construir o sistema assim.

Padrão Adapter

Temos um plugin, library, module ou serviço de terceiros que faz algo que queremos na regra de negócio — aqui, persistir dados. O caminho:

  1. Definir uma interface com o contrato do que precisamos.
  2. Expor só métodos que fazem sentido no contexto (SOLID).
  3. Implementar a interface em classes que adaptam o código de terceiros.

Criamos CreateDatabaseCustomerProtocol com um método create que recebe CustomerInputEntity e retorna SuccessfulEntityCreation:

interface CustomerInputEntity {
  name: string;
  email: string;
  birthDate: Date;
}

interface SuccessfulEntityCreation {
  readonly id: number;
  readonly name: string;
  readonly email: string;
  readonly birthDate: Date;
}

interface CreateDatabaseCustomerProtocol {
  createCustomerOnDatabase(
    customer: CustomerInputEntity,
  ): SuccessfulEntityCreation;
}

Em vez de Controller → Service → Database, passamos a Controller → Service → Protocols → Plugin.

O serviço perde o conhecimento de como o CRUD chega ao banco. Ele é composto pelos protocolos; a implementação concreta entra em tempo de execução — por injeção de dependência. Enquanto usamos PostgreSQL, implementamos os protocolos nos adapters (ou connectors).

CreateDatabaseCustomerProtocol pode ser implementado por CreateDatabaseCustomerPostgresqlAdapter, CreateDatabaseCustomerMysqlAdapter, CreateDatabaseCustomerMongoDBAdapter ou CreateDatabaseCustomerMockedAdapter. O serviço fica assim:

class CustomerService {
  constructor(
    private readonly createCustomer: CreateDatabaseCustomerProtocol,
  ) {}

  public register(customer: CustomerInputEntity): SuccessfulEntityCreation {
    return this.createCustomer.createCustomerOnDatabase(customer);
  }
}

Para o serviço, tanto faz se o banco devolve JSON, XML ou outro formato — o adapter traduz para o contrato que a regra de negócio espera.

Vantagens

  • Manutenção: qualquer plugin pode ser substituído sem reescrever o serviço.
  • Testes: para testar só a regra de negócio, injete um adapter mock que implementa o mesmo protocolo.
  • Código limpo: responsabilidades separadas; a regra de negócio não carrega detalhes do driver.

Próximo passo

Pegue um frontend com dezenas de bibliotecas e identifique o que você realmente usa. Escolha uma funcionalidade — converter real em dólar, por exemplo. Descreva o contrato (entrada e saída) e implemente um adapter em cima da biblioteca que hoje faz isso. Repita onde a dependência incomoda.

Relação com outros padrões

No artigo Design Patterns: Strategy, o foco é trocar algoritmos atrás de um contrato. O Adapter isola dependências externas atrás de uma interface própria. Os dois se complementam: Strategy varia comportamento; Adapter traduz o mundo de fora.


Publicado originalmente no LinkedIn em 26 de abril de 2022.