Arquitectura Hexagonal en el Frontend: Escalabilidad Real
Descubre cómo desacoplar la lógica de negocio de la interfaz de usuario para crear aplicaciones web mantenibles, testables y duraderas.
En el vertiginoso mundo del desarrollo frontend, los frameworks nacen y mueren con una velocidad pasmosa. React, Vue, Svelte, Solid… todos compiten por nuestra atención. Sin embargo, hay algo que permanece constante: la necesidad de escribir código mantenible.
Aquí es donde entra la Arquitectura Hexagonal (o Puertos y Adaptadores). Originalmente concebida para el backend, sus principios son vitales para aplicaciones frontend modernas que aspiran a sobrevivir más allá del hype del momento.
El Problema del Acoplamiento
La mayoría de las aplicaciones frontend sufren de un acoplamiento excesivo entre la vista (Componentes React, por ejemplo) y la lógica de negocio.
// ❌ Mal: Lógica de negocio mezclada con la UI
const UserProfile = () => {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user').then(res => res.json()).then(data => {
// Lógica de transformación de datos aquí mismo
const formattedUser = { ...data, fullName: `${data.name} ${data.surname}` };
setUser(formattedUser);
});
}, []);
if (!user) return <div>Cargando...</div>;
return <h1>{user.fullName}</h1>;
};
Este enfoque tiene problemas graves:
- Difícil de testear: Necesitas montar el componente y mockear
fetchpara probar una simple transformación de datos. - Difícil de cambiar: Si cambias de API REST a GraphQL, tienes que tocar tus componentes UI.
- Difícil de reutilizar: La lógica está atrapada dentro del componente.
La Solución Hexagonal
La Arquitectura Hexagonal propone dividir la aplicación en capas concéntricas:
- Dominio (Núcleo): Las entidades y reglas de negocio puras. No sabe nada de React, ni de HTTP, ni del navegador.
- Aplicación (Casos de Uso): Orquesta el flujo de datos. “ObtenerUsuario”, “ActualizarPerfil”.
- Infraestructura (Adaptadores): La implementación concreta. Llamadas a API, LocalStorage, Componentes de UI.
Implementación Práctica
Definimos nuestro Dominio:
// domain/user.ts
export interface User {
id: string;
name: string;
email: string;
}
export interface UserRepository {
getUser(): Promise<User>;
}
Creamos un Caso de Uso:
// application/get-user.ts
import { UserRepository } from '../domain/user';
export class GetUserUseCase {
constructor(private readonly repository: UserRepository) {}
async execute(): Promise<User> {
return await this.repository.getUser();
}
}
Implementamos la Infraestructura (El repositorio):
// infrastructure/api-user-repository.ts
import { User, UserRepository } from '../domain/user';
export class ApiUserRepository implements UserRepository {
async getUser(): Promise<User> {
const response = await fetch('/api/user');
const data = await response.json();
return {
id: data.uuid,
name: data.first_name,
email: data.email
}; // Adaptamos la respuesta de la API a nuestro Dominio
}
}
Y finalmente, la Vista (React):
// ui/UserProfile.tsx
const UserProfile = ({ getUserUseCase }: { getUserUseCase: GetUserUseCase }) => {
const { data: user } = useQuery(['user'], () => getUserUseCase.execute());
if (!user) return <div>Cargando...</div>;
return <h1>{user.name}</h1>;
};
Conclusión
Al aplicar estos principios, logramos:
- Independencia del Framework: Podrías migrar de React a Vue llevándote toda tu lógica de dominio y aplicación intacta.
- Testabilidad: Puedes testear tus casos de uso con tests unitarios rápidos, sin renderizar componentes.
- Mantenibilidad: Los cambios en la API externa solo afectan al adaptador de infraestructura, no a toda la app.
En Plano Binario, aplicamos esta rigurosidad en cada proyecto, asegurando que tu inversión tecnológica sea duradera y robusta.