Dos conceptos que operan en dimensiones distintas: cómo agrupar el código y cómo aislar la lógica de negocio de los detalles técnicos. Son dos conceptos que suelen compararse, pero en realidad operan en dimensiones distintas de la organización del software. Uno resuelve cómo agrupar el código; el otro, cómo aislar la lógica de negocio de los detalles técnicos (frameworks, APIs, almacenamiento). Entender la diferencia ayuda a combinarlos correctamente en lugar de tratarlos como alternativas excluyentes.
Este apunte lo explica desde el ecosistema Vue 3 + Composition API, porque es donde este patrón se aplica de forma más natural en frontend: los composables hacen de “dominio” y los componentes .vue de “adaptadores de UI”.
Arquitectura Feature-Based (organización por características)
Organiza el código según funcionalidades del producto, no según tipo de archivo.
src/
features/
pedidos/
components/
ListaPedidos.vue
DetallePedido.vue
composables/
usePedidos.ts
pedidos.types.ts
usuarios/
components/
PerfilUsuario.vue
composables/
useUsuarios.ts
pagos/
...
Características:
- Todo lo relacionado con una funcionalidad vive junto (componentes, composables, tipos, tests).
- Facilita encontrar y modificar código relacionado con una feature concreta.
- Reduce el acoplamiento entre features distintas.
- Es más una convención de organización de carpetas que una filosofía arquitectónica profunda.
Problema que resuelve: el caos de organizar por tipo de archivo (components/, composables/, views/ como carpetas globales), donde trabajar en una sola funcionalidad obliga a saltar constantemente entre carpetas dispersas por todo el proyecto.
Cuándo usarla: proyectos pequeños o medianos, con lógica de negocio relativamente simple, o equipos que priorizan velocidad de desarrollo y facilidad de navegación sobre el aislamiento estricto de capas.
Arquitectura Hexagonal (Ports & Adapters)
Su objetivo es aislar la lógica de negocio (dominio) de los detalles técnicos externos: en frontend, esto significa aislar tu lógica de estado y reglas de negocio de la API concreta, del almacenamiento local, o incluso del propio Vue.
src/
features/
pedidos/
domain/ ← lógica pura: entidades y reglas de negocio, sin Vue ni fetch
pedido.entity.ts
ports/ ← interfaces: qué necesita el dominio, sin decir cómo
pedido-repository.port.ts
adapters/
http/ ← adaptador que llama a la API REST/GraphQL
pedido-http.adapter.ts
local-storage/ ← adaptador alternativo (ej. modo offline o caché)
pedido-local.adapter.ts
composables/ ← conecta el dominio con Vue (el "puerto de entrada")
usePedidos.ts
components/
ListaPedidos.vue
Si mañana necesitas modo offline, creas un nuevo adaptador que implemente el mismo puerto y lo inyectas en el composable en vez del adaptador HTTP. El componente .vue no cambia una sola línea, y tampoco cambia la lógica de negocio en domain/.
Características:
- El dominio no sabe nada de Vue, de fetch, ni de dónde vienen los datos.
- El composable actúa de puerto de entrada: conecta el dominio con la reactividad de Vue.
- El componente
.vuees solo un adaptador de presentación, intercambiable. - Testear las reglas de negocio del dominio no requiere montar ningún componente ni mockear el DOM.
Problema que resuelve: la lógica de negocio mezclada dentro de componentes .vue, que hace difícil testear reglas de negocio sin levantar todo el árbol de componentes, y que acopla tu producto a una API o proveedor concretos.
Cuándo usarla: proyectos con reglas de negocio no triviales (cálculos, validaciones, estados complejos), equipos que necesitan alta testabilidad, o cuando se prevén cambios de fuente de datos (API, GraphQL, local-first, múltiples backends).
Diagrama conceptual
FEATURE-BASED HEXAGONAL (dentro de una feature)
(agrupación horizontal) (capas concéntricas)
┌─────────┐ ┌─────────┐ ┌──────────────────────────┐
│ Pedidos │ │Usuarios │ │ Adaptadores (.vue, │
│─────────│ │─────────│ │ http, local-storage) │
│Component│ │Component│ │ ┌──────────────────────┐ │
│Composab.│ │Composab.│ │ │ Composables (puerto) │ │
└─────────┘ └─────────┘ │ │ ┌─────────────────┐ │ │
│ │ │ Domain (puro) │ │ │
│ │ └─────────────────┘ │ │
│ └──────────────────────┘ │
└──────────────────────────┘
La diferencia clave
| Aspecto | Feature-Based | Hexagonal |
|---|---|---|
| Qué organiza | Cómo se agrupan los archivos/carpetas | Cómo se separan las responsabilidades (negocio vs Vue/API/storage) |
| Pregunta que responde | ”¿Dónde está el código de X funcionalidad?" | "¿Qué tan aislado está mi lógica de negocio de Vue y de la API?” |
| Nivel | Organizacional / estructura de proyecto | Arquitectónico / dependencias y flujo de datos |
| Foco principal | Cohesión por funcionalidad de producto | Independencia del dominio respecto al framework y la infraestructura |
| Origen | Convención práctica de organización | Acuñada por Alistair Cockburn (~2005) |
| En Vue, se traduce en | Carpetas features/pedidos/, features/usuarios/ | domain/ puro + ports/ + adapters/ + composables/ como puente hacia los componentes |
Combinándolas: lo mejor de ambos mundos
No son excluyentes — de hecho, es la combinación más natural en un proyecto Vue real: organizas por feature, y dentro de cada feature, aplicas la separación hexagonal.
src/
features/
pedidos/
domain/
ports/
adapters/
composables/
components/
usuarios/
domain/
ports/
adapters/
composables/
components/
pagos/
domain/
ports/
adapters/
composables/
components/
Con esta estructura obtienes:
- Navegabilidad: todo el código de “pedidos” está junto, fácil de localizar como design engineer que trabaja tanto en UI como en lógica.
- Aislamiento: dentro de “pedidos”, el dominio no depende de fetch, de axios ni de Vue.
- Composables reutilizables:
usePedidos()puede usarse en varios componentes distintos (lista, detalle, dashboard) sin duplicar lógica. - Testabilidad real: testeas
domain/con Vitest puro, sin@vue/test-utils, y testeas los composables mockeando el puerto — sin necesidad de red ni backend real.
Esta combinación es especialmente valiosa en proyectos que empiezan simples pero se espera que crezcan: puedes arrancar con componentes y composables planos por feature, e ir introduciendo domain/, ports/ y adapters/ feature por feature, a medida que la lógica de negocio se vuelve más compleja — sin necesidad de una reescritura completa.
Conclusión
- Feature-based responde a “¿cómo organizo mis carpetas para encontrar las cosas rápido?”
- Hexagonal responde a “¿cómo protejo mi lógica de negocio de Vue, de la API y del almacenamiento?”
En un proyecto Vue, la combinación natural es: carpetas por feature por fuera, y domain → ports → adapters → composables → componentes por dentro de cada una.