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:
- Definir uma interface com o contrato do que precisamos.
- Expor só métodos que fazem sentido no contexto (SOLID).
- 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.