Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter

Migrar de MySQL a PostgreSQL no exige reescribir el servicio. El Adapter aísla el plugin tras un contrato que la regla de negocio entiende.

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:

  1. Definir una interfaz con el contrato de lo que necesitamos.
  2. Exponer solo métodos que tengan sentido en el contexto (SOLID).
  3. 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.