Si hay algo seguro en el desarrollo de software, además de los bugs, es encontrarte con código o datos que no controlamos. Llámalo librerías desactualizadas, APIs externas, o servicios de terceros que parecen diseñados para ponernos a prueba.
Para no perder la cordura, tenemos un superhéroe estructural: el patrón Adaptador. Este nos permite meter una capa intermedia entre nuestro código bonito (o no tan bonito, pero es nuestro) y ese caos externo del que no tenemos control absoluto.
Adapter Pattern
Implementar este patrón es cosa sencilla. Me gusta verlo de esta forma: necesitas saber qué es lo que vas a adaptar (el Adaptee), definir claramente cómo quieres integrar eso con tu código (el Target), y con base en eso, diseñar la clase Adapter.
Vamos a verlo con un ejemplo. Imagina que tienes una librería que te devuelve una película random, pero resulta que te manda la data como tuplas: el título de la película y el año.
Nosotros necesitamos trabajar con objetos literales de toda la vida, algo así:
¡Listo! Ya tenemos un problema perfecto para resolver con un adaptador.
En este caso, nuestra clase Adaptee puede ser directamente RandomMovie
, o puedes crear una clase propia que extienda de RandomMovie
. Esto depende de qué tan creativo te sientas ese día.
Primero, definamos algunas interfaces para establecer qué necesitamos para construir el adaptador:
Aquí definimos Movie
, que es el tipo de objetos con los que nuestra aplicación va a trabajar, y una interfaz para dejar claro qué funcionalidades debe tener nuestro adaptador.
El adaptador quedaría así:
De esta forma, en nuestro código ya no usamos directamente la clase de la librería. Ahora trabajamos con ella a través del adaptador, y todo fluye más bonito:
Caso de uso en aplicaciones Front-End
Ahora que ya definimos el patrón, mi objetivo con este post es mostrar el uso más común que me he encontrado al meterle mano a aplicaciones frontend. Esto es completamente agnóstico de tecnologías: aplica igual para Angular, React, Vue o cualquiera de los 450 frameworks de frontend que seguro saldrán el mes que viene.
En la mayoría de los casos, las aplicaciones frontend consumen información de servicios de terceros o incluso de servicios propios que, para variar, pueden cambiar de un día para otro o devolver datos que no son 100% compatibles con la app.
Supongamos que obtenemos información de usuarios desde una API externa, y al hacer un request GET
nos retorna un usuario que luce algo así:
Trabajar directamente con los objetos tal como llegan puede traer varios dolores de cabeza:
- Fechas: Las APIs suelen devolver fechas en formatos incómodos; necesitarás convertirlas constantemente.
- Convenciones de nombres: Si la API usa
snake_case
o formatos raros, tu código sufrirá para mantenerse consistente concamelCase
. - Datos anidados: Las respuestas pueden ser un laberinto de datos difíciles de manejar y propensos a errores si los usas directamente.
- Datos innecesarios: Las APIs devuelven mucha información irrelevante, desperdiciando recursos si no la filtras.
- Cambios en la API: Si la API cambia su estructura, sin un adaptador tendrás que modificar el código en muchos lugares.
- Formatos regionales: Fechas, monedas o números podrían no ajustarse a las preferencias de los usuarios; mejor centralizar las conversiones.
Ahora, en nuestra aplicación web, necesitamos adaptar esos objetos entrantes para que estén validados y formateados según nuestras reglas. Empecemos definiendo las interfaces necesarias:
Estas son las interfaces que definen las entidades con las que vamos a trabajar. Cualquier objeto User
que usemos en nuestra app tiene que lucir exactamente así, sí o sí.
Sabemos cómo deberían verse los objetos que vienen de la API... en teoría. Pero como mencioné antes, no podemos confiar ciegamente en algo que no controlamos. Aquí es donde entra el adaptador, nuestro escudo protector, para asegurarnos de que todo lo que recibimos sea como esperamos.
Para este caso, vamos a usar Zod y definir unos esquemas que reflejen la estructura esperada de la respuesta de la API:
¡Genial! Estos esquemas validan los objetos de usuario que nos llegan de la API. Puedes agregar tantas validaciones como creas necesarias, porque en este juego, entre más validado el dato, menos lloramos después. Además, exportamos los tipos generados por los esquemas para reutilizarlos más adelante.
Con eso listo, creamos nuestra clase adapter. Primero definimos la interfaz que deberá cumplir:
Ahora sí, implementemos la clase para adaptar la entidad User
:
El método adaptFromApi()
hace el trabajo sucio: valida la entrada, la convierte y nos regresa un objeto User
limpio, validado y listo para usarse, como un MVP pero en formato JSON.
El último paso es usar este adaptador en tu aplicación. Mi recomendación siempre es manejar las peticiones HTTP en una capa de servicios. Aquí un ejemplo:
En este caso, asumo que la API devuelve un objeto de tipo UserApi
, por lo que lo específico en el genérico del método get()
. Pero, como siempre, puede ser cualquier cosa, desde un objeto válido hasta un desastre digno de llorar.
Cuando tengas la respuesta, pásala directamente al adaptador. Él hará su magia y, si algo falla, puedes lanzar excepciones detalladas desde el adaptador para que tu servicio (en este caso, UserService
) las maneje como mejor te convenga.
Así, en tus componentes, sin importar el framework o librería que uses, puedes trabajar con objetos seguros y correctamente tipados gracias a nuestro héroe, TypeScript. ¡Usa TypeScript! Porque nadie debería sufrir bugs por falta de tipos.
Conclusión
El patrón Adaptador no solo es útil, es prácticamente indispensable cuando trabajamos con datos o servicios que no controlamos. Nos permite mantener nuestro código limpio, seguro y adaptable a cambios futuros, evitando convertir nuestras aplicaciones en un caos.
En el caso de aplicaciones frontend, usar adaptadores para manejar datos de APIs externas puede marcar la diferencia entre un proyecto sostenible y uno lleno de dolores de cabeza. Validar, transformar y controlar los datos en un solo punto central nos ahorra problemas de compatibilidad, errores de formato y horas de debugging.
¿La lección de todo esto? Si algo no está bajo tu control, usa un adaptador. Y por favor, usa TypeScript. El futuro tú te lo agradecerá cuando no tenga que descifrar un error causado por un undefined
.