Con el patrón Adapter, aislamos la regla de negocio de la dependencia concreta.
Escenario inicial
Tenemos un backend con CRUD simple de usuario: crear, editar, recuperar y eliminar por endpoints en la API. Los datos van a una base cualquiera — digamos MySQL — y la estructura nace como Controller → Service → Database. Hasta aquí, el equipo está cómodo.
Hasta que alguien decide: la semana que viene migramos de MySQL a PostgreSQL. A partir de ahí la casa se cae para tecnología. Además de reestructurar la base, el equipo necesita cazar referencias a MySQL: inserciones, conexión, queries esparcidas.
La mayoría de las veces esto retrasa la entrega, reduce la calidad, salta pruebas e introduce bugs. Sería mejor disminuir la dependencia entre la regla de negocio y quien ejecuta la operación específica — en este caso, la base. El Adapter ayuda a construir el sistema así.
Patrón Adapter
Tenemos un plugin, library, module o servicio de terceros que hace algo que queremos en la regla de negocio — aquí, persistir datos. El camino:
- Definir una interfaz con el contrato de lo que necesitamos.
- Exponer solo métodos que tengan sentido en el contexto (SOLID).
- Implementar la interfaz en clases que adaptan el código de terceros.
Creamos CreateDatabaseCustomerProtocol con un método create que recibe CustomerInputEntity y 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;
}
En lugar de Controller → Service → Database, pasamos a Controller → Service → Protocols → Plugin.
El servicio pierde el conocimiento de cómo el CRUD llega a la base. Está compuesto por protocolos; la implementación concreta entra en tiempo de ejecución — por inyección de dependencias. Mientras usamos PostgreSQL, implementamos los protocolos en los adapters (o connectors).
CreateDatabaseCustomerProtocol puede ser implementado por CreateDatabaseCustomerPostgresqlAdapter, CreateDatabaseCustomerMysqlAdapter, CreateDatabaseCustomerMongoDBAdapter o CreateDatabaseCustomerMockedAdapter. El servicio queda así:
class CustomerService {
constructor(
private readonly createCustomer: CreateDatabaseCustomerProtocol,
) {}
public register(customer: CustomerInputEntity): SuccessfulEntityCreation {
return this.createCustomer.createCustomerOnDatabase(customer);
}
}
Para el servicio, da igual si la base devuelve JSON, XML u otro formato — el adapter traduce al contrato que la regla de negocio espera.
Ventajas
- Mantenimiento: cualquier plugin puede sustituirse sin reescribir el servicio.
- Pruebas: para probar solo la regla de negocio, inyecta un adapter mock que implemente el mismo protocolo.
- Código limpio: responsabilidades separadas; la regla de negocio no carga detalles del driver.
Próximo paso
Toma un frontend con decenas de bibliotecas e identifica lo que realmente usas. Elige una funcionalidad — convertir reales a dólares, por ejemplo. Describe el contrato (entrada y salida) e implementa un adapter sobre la biblioteca que hoy lo hace. Repite donde la dependencia moleste.
Relación con otros patrones
En el artículo Design Patterns: Strategy, el foco es intercambiar algoritmos tras un contrato. El Adapter aísla dependencias externas tras una interfaz propia. Ambos se complementan: Strategy varía comportamiento; Adapter traduce el mundo exterior.
Publicado originalmente en LinkedIn el 26 de abril de 2022.