Arquitectura hexagonal en 2026: puertos, adaptadores y Clean

Profile picture of Equipo Arvucore

Equipo Arvucore

September 22, 2025 · Actualizado el August 26, 2026

17 min read

La arquitectura hexagonal (también llamada puertos y adaptadores) pone la lógica de negocio en el centro de la aplicación y obliga a toda preocupación externa — bases de datos, HTTP, colas, APIs de terceros — a hablar con ella a través de interfaces que el núcleo controla. Clean Architecture y Onion Architecture son formulaciones posteriores de la misma idea, con capas más prescriptivas. Si vas a recordar una sola regla, es esta: las dependencias del código fuente apuntan hacia dentro, y el núcleo nunca importa infraestructura.

Esta guía explica puertos y adaptadores con precisión, compara los tres estilos lado a lado, recorre un ejemplo Orders en TypeScript con adaptadores en memoria y Postgres, y enumera los errores que convierten el patrón en ceremonia.

Qué dice realmente la arquitectura hexagonal

Alistair Cockburn describió el patrón en 2005 con un objetivo simple: una aplicación debería poder accionarse por igual desde usuarios, programas, tests automatizados y scripts por lotes, y debería poder desarrollarse y probarse aislada de los dispositivos y bases de datos de su entorno de ejecución. La forma de hexágono no tiene un significado especial; solo da espacio para dibujar varios lados, uno por cada tipo de actor externo.

El patrón tiene tres partes:

  • El núcleo de la aplicación. Modelo de dominio más lógica de aplicación (casos de uso). No sabe nada de frameworks, transporte ni almacenamiento.
  • Puertos. Interfaces que definen cómo se usa el núcleo y qué necesita. Los puertos pertenecen al núcleo y se escriben en el lenguaje del núcleo (placeOrder, OrderRepository), nunca en el lenguaje de la infraestructura (insertRow, POST /orders).
  • Adaptadores. Código en el borde que traduce entre un puerto y una tecnología concreta. Los adaptadores son reemplazables; el núcleo no debería notar cuando se cambia uno.
                         LADO DRIVING                       LADO DRIVEN
              (quién llama a la aplicación)           (qué llama la aplicación)

   +-----------+                                                  +----------------+
   | REST ctrl |--+                                            +--| Postgres repo  |
   +-----------+  |                                            |  +----------------+
   +-----------+  |     +--------------------------------+     |  +----------------+
   | CLI       |--+---->| puerto driving|  NÚCLEO DE LA  |     +--| In-memory repo |
   +-----------+  |     | PlaceOrder    |  APLICACIÓN    |     |  +----------------+
   +-----------+  |     |               |  ------------  |     |  +----------------+
   | Test      |--+     |               |  Order         |---->+--| SMTP mailer    |
   +-----------+        |               |  OrderLine     |     |  +----------------+
   +-----------+  |     |               |  Money         |     |  +----------------+
   | Cola      |--+     |               |  puertos driven|     +--| Payment gateway|
   +-----------+        +--------------------------------+        +----------------+

   Los adaptadores IMPLEMENTAN puertos driven.   Los adaptadores LLAMAN a puertos driving.
   Todas las flechas del código fuente apuntan HACIA DENTRO del núcleo.

Puertos driving vs puertos driven

La distinción que la mayoría de tutoriales difumina es la dirección de la llamada.

Puertos driving (primarios, de entrada). El exterior llama al núcleo a través de ellos. Un puerto driving es la API del caso de uso: PlaceOrder.execute(command). El adaptador de este lado es un llamador — un controller HTTP, un resolver GraphQL, un consumidor de mensajes, un cron job, un test. El núcleo expone el puerto; el adaptador depende de él.

Puertos driven (secundarios, de salida). El núcleo llama al exterior a través de ellos. Un puerto driven es una necesidad del núcleo: OrderRepository, PaymentGateway, Clock, EventPublisher. El adaptador de este lado es un implementador — un repositorio Postgres, un cliente de Stripe, un fake para tests. El núcleo declara la interfaz; el adaptador la implementa y se inyecta al arrancar.

Esto es inversión de dependencias aplicada en la frontera de la aplicación. El núcleo define ambos tipos de puerto, así que en el código fuente no depende de nada fuera de sí mismo. La llamada en tiempo de ejecución en la dirección driven va hacia fuera, pero la dependencia en tiempo de compilación sigue apuntando hacia dentro, porque la interfaz vive en el núcleo.

Dos consecuencias:

  1. Los puertos se definen en términos de tipos de dominio (Order, OrderId, Money), nunca en términos de una entidad de ORM, un DTO de un framework HTTP o una fila de base de datos.
  2. Cualquier cosa que el núcleo necesite del entorno — tiempo, aleatoriedad, configuración — también es un puerto driven. Eso es lo que lo hace determinista en los tests.

Hexagonal vs Clean vs Onion Architecture

Las tres son variaciones de la misma regla. Las diferencias están en el vocabulario, en cuántas capas prescriben y en su origen.

Criterio Hexagonal (Puertos y Adaptadores) Onion Architecture Clean Architecture
Origen Alistair Cockburn, 2005 Jeffrey Palermo, 2008 Robert C. Martin, 2012
Idea central El núcleo habla con el exterior solo por puertos; los adaptadores traducen Anillos concéntricos alrededor de un modelo de dominio Círculos concéntricos con una regla de dependencia explícita
Capas nombradas Dos: dentro (núcleo) y fuera (adaptadores). No prescribe capas internas Modelo de dominio, servicios de dominio, servicios de aplicación, infraestructura/UI Entidades, casos de uso, adaptadores de interfaz, frameworks y drivers
Regla de dependencia Implícita: los adaptadores dependen de los puertos, el núcleo no depende de nada Explícita: los anillos externos dependen solo de los internos Explícita y con nombre: las dependencias apuntan hacia dentro; los círculos internos no saben nada de los externos
Terminología Puerto, adaptador, driving/driven, aplicación Modelo de dominio, servicio de dominio, servicio de aplicación, infraestructura Entidad, caso de uso (interactor), gateway, presenter, controller
Dónde viven las interfaces En el núcleo, bajo control del núcleo En los anillos internos (dominio o aplicación) En el círculo de casos de uso (input/output boundaries, gateways)
¿Prescribe casos de uso? No, pero la "aplicación" cumple ese papel Servicios de aplicación Sí: un interactor por caso de uso, con puertos de entrada/salida
Estructura de carpetas típica domain/, application/, ports/, adapters/ Domain/, Application/, Infrastructure/, Web/ entities/, usecases/, adapters/, frameworks/
Punto más fuerte Explicar la frontera y la testabilidad Enfatizar el modelo de dominio Estandarizar las capas internas y la nomenclatura

Lee la tabla y el patrón aparece: la hexagonal responde dónde está la frontera y quién la implementa; Onion y Clean responden cómo organizo lo que hay dentro de la frontera. La mayoría de bases de código en producción que se dicen "hexagonales" usan en realidad capas internas al estilo Clean (dominio, aplicación/casos de uso) con vocabulario hexagonal en el borde (puertos, adaptadores). Es una combinación sensata, no una contradicción.

Ejemplo práctico: un módulo Orders en TypeScript

El ejemplo de abajo es lo bastante pequeño para leerlo de una sentada y lo bastante completo para mostrar cada parte: dominio, un puerto driven, dos adaptadores para él, un caso de uso (puerto driving) y un adaptador HTTP.

Estructura de carpetas sugerida

src/
  orders/
    domain/
      Order.ts               # agregado con reglas de negocio
      OrderLine.ts
      Money.ts
      errors.ts
    application/
      ports/
        driving/
          PlaceOrder.ts      # interfaz del caso de uso (puerto driving)
        driven/
          OrderRepository.ts # interfaz de persistencia (puerto driven)
          Clock.ts
      use-cases/
        PlaceOrderService.ts # implementa PlaceOrder, depende de los puertos driven
    adapters/
      driving/
        http/
          OrdersController.ts
      driven/
        persistence/
          InMemoryOrderRepository.ts
          PostgresOrderRepository.ts
        SystemClock.ts
  composition/
    container.ts             # conecta adaptadores con puertos (composition root)
  main.ts

Regla práctica: domain/ y application/ deben compilar sin adapters/. Si puedes borrar la carpeta adapters/ y el núcleo sigue pasando el type-check, la frontera está intacta.

Dominio

// src/orders/domain/Order.ts
import { Money } from './Money';
import { OrderLine } from './OrderLine';
import { EmptyOrderError } from './errors';

export type OrderId = string;

export class Order {
  private constructor(
    readonly id: OrderId,
    readonly customerId: string,
    private readonly lines: OrderLine[],
    readonly placedAt: Date,
  ) {}

  static place(id: OrderId, customerId: string, lines: OrderLine[], now: Date): Order {
    if (lines.length === 0) throw new EmptyOrderError(id);
    return new Order(id, customerId, [...lines], now);
  }

  total(): Money {
    return this.lines.reduce((sum, l) => sum.add(l.subtotal()), Money.zero('EUR'));
  }

  getLines(): readonly OrderLine[] {
    return this.lines;
  }
}

El dominio tiene comportamiento (place, total) e impone una invariante (nada de pedidos vacíos). Fíjate en que recibe now en lugar de llamar a new Date() — el tiempo es una dependencia.

Puertos driven

// src/orders/application/ports/driven/OrderRepository.ts
import { Order, OrderId } from '../../../domain/Order';

export interface OrderRepository {
  save(order: Order): Promise<void>;
  findById(id: OrderId): Promise<Order | null>;
}

// src/orders/application/ports/driven/Clock.ts
export interface Clock {
  now(): Date;
}

El repositorio habla en Order, no en filas. Tiene exactamente los métodos que necesitan los casos de uso, no una superficie CRUD genérica.

Puerto driving y caso de uso

// src/orders/application/ports/driving/PlaceOrder.ts
export interface PlaceOrderCommand {
  orderId: string;
  customerId: string;
  lines: { sku: string; quantity: number; unitPriceCents: number }[];
}

export interface PlaceOrder {
  execute(command: PlaceOrderCommand): Promise<{ orderId: string; totalCents: number }>;
}

// src/orders/application/use-cases/PlaceOrderService.ts
import { PlaceOrder, PlaceOrderCommand } from '../ports/driving/PlaceOrder';
import { OrderRepository } from '../ports/driven/OrderRepository';
import { Clock } from '../ports/driven/Clock';
import { Order } from '../../domain/Order';
import { OrderLine } from '../../domain/OrderLine';
import { Money } from '../../domain/Money';

export class PlaceOrderService implements PlaceOrder {
  constructor(
    private readonly orders: OrderRepository,
    private readonly clock: Clock,
  ) {}

  async execute(cmd: PlaceOrderCommand) {
    const lines = cmd.lines.map(
      (l) => new OrderLine(l.sku, l.quantity, Money.of(l.unitPriceCents, 'EUR')),
    );
    const order = Order.place(cmd.orderId, cmd.customerId, lines, this.clock.now());
    await this.orders.save(order);
    return { orderId: order.id, totalCents: order.total().cents };
  }
}

El caso de uso orquesta: construye objetos de dominio, aplica reglas, persiste a través de un puerto, devuelve un resultado simple. Solo importa de domain/ y ports/.

Dos adaptadores para el mismo puerto

// src/orders/adapters/driven/persistence/InMemoryOrderRepository.ts
import { OrderRepository } from '../../../application/ports/driven/OrderRepository';
import { Order, OrderId } from '../../../domain/Order';

export class InMemoryOrderRepository implements OrderRepository {
  private readonly store = new Map<OrderId, Order>();
  async save(order: Order) { this.store.set(order.id, order); }
  async findById(id: OrderId) { return this.store.get(id) ?? null; }
}
// src/orders/adapters/driven/persistence/PostgresOrderRepository.ts
import { Pool } from 'pg';
import { OrderRepository } from '../../../application/ports/driven/OrderRepository';
import { Order, OrderId } from '../../../domain/Order';
import { toRows, fromRows } from './OrderMapper';

export class PostgresOrderRepository implements OrderRepository {
  constructor(private readonly pool: Pool) {}

  async save(order: Order) {
    const { header, lines } = toRows(order);
    const client = await this.pool.connect();
    try {
      await client.query('BEGIN');
      await client.query(
        'INSERT INTO orders (id, customer_id, placed_at) VALUES ($1, $2, $3)',
        [header.id, header.customer_id, header.placed_at],
      );
      for (const l of lines) {
        await client.query(
          'INSERT INTO order_lines (order_id, sku, quantity, unit_price_cents) VALUES ($1, $2, $3, $4)',
          [l.order_id, l.sku, l.quantity, l.unit_price_cents],
        );
      }
      await client.query('COMMIT');
    } catch (e) {
      await client.query('ROLLBACK');
      throw e;
    } finally {
      client.release();
    }
  }

  async findById(id: OrderId) {
    const header = await this.pool.query('SELECT * FROM orders WHERE id = $1', [id]);
    if (header.rowCount === 0) return null;
    const lines = await this.pool.query('SELECT * FROM order_lines WHERE order_id = $1', [id]);
    return fromRows(header.rows[0], lines.rows);
  }
}

El mapeo entre Order y las filas de las tablas vive en el adaptador (OrderMapper). El dominio nunca ve pg, y el SQL nunca se filtra hacia arriba. Cambiar Postgres por DynamoDB o por un ORM es un archivo nuevo en adapters/, no un cambio en application/.

Adaptador HTTP (driving)

// src/orders/adapters/driving/http/OrdersController.ts
import { FastifyInstance } from 'fastify';
import { PlaceOrder } from '../../../application/ports/driving/PlaceOrder';
import { EmptyOrderError } from '../../../domain/errors';

export function registerOrderRoutes(app: FastifyInstance, placeOrder: PlaceOrder) {
  app.post('/orders', async (req, reply) => {
    try {
      const result = await placeOrder.execute(req.body as any); // en código real, valida con un schema
      return reply.code(201).send(result);
    } catch (e) {
      if (e instanceof EmptyOrderError) return reply.code(422).send({ error: e.message });
      throw e;
    }
  });
}

El controller traduce HTTP a un comando y los errores de dominio a códigos de estado. Depende de la interfaz PlaceOrder, no de PlaceOrderService, así que las mismas rutas funcionan con cualquier implementación.

Composition root

// src/composition/container.ts
import { Pool } from 'pg';
import { PlaceOrderService } from '../orders/application/use-cases/PlaceOrderService';
import { PostgresOrderRepository } from '../orders/adapters/driven/persistence/PostgresOrderRepository';
import { SystemClock } from '../orders/adapters/driven/SystemClock';

export function buildContainer(env: { DATABASE_URL: string }) {
  const pool = new Pool({ connectionString: env.DATABASE_URL });
  const placeOrder = new PlaceOrderService(new PostgresOrderRepository(pool), new SystemClock());
  return { placeOrder };
}

Este es el único sitio que conoce ambos lados. Los frameworks con contenedor de DI (NestJS, tsyringe, Spring, .NET) hacen el mismo trabajo; una función simple basta para la mayoría de módulos.

Tests: la razón por la que la mayoría de equipos lo adopta

Con la frontera en su sitio, los tests se dividen en tres niveles, cada uno con un papel claro.

Tests del núcleo, sin I/O. Conecta el caso de uso al repositorio en memoria y a un reloj fijo. Corren en milisegundos, no necesitan base de datos ni contenedor, y son los que más vas a escribir.

// PlaceOrderService.test.ts
const orders = new InMemoryOrderRepository();
const clock = { now: () => new Date('2026-08-26T10:00:00Z') };
const service = new PlaceOrderService(orders, clock);

it('rechaza un pedido sin líneas', async () => {
  await expect(service.execute({ orderId: 'o1', customerId: 'c1', lines: [] }))
    .rejects.toBeInstanceOf(EmptyOrderError);
});

it('persiste el pedido y devuelve el total', async () => {
  const result = await service.execute({
    orderId: 'o2', customerId: 'c1',
    lines: [{ sku: 'A', quantity: 2, unitPriceCents: 1500 }],
  });
  expect(result.totalCents).toBe(3000);
  expect(await orders.findById('o2')).not.toBeNull();
});

Tests de contrato de los adaptadores. Ejecuta la misma suite contra todas las implementaciones de un puerto driven. Un describeOrderRepositoryContract(factory) compartido, ejecutado una vez para InMemoryOrderRepository y otra para PostgresOrderRepository (contra un Postgres real en un contenedor), garantiza que el fake se comporta como producción. Este es el paso que mantiene honestos a los fakes en memoria.

Tests end-to-end ligeros. Un puñado a través del adaptador HTTP para demostrar el cableado. No uno por regla de negocio; las reglas ya están cubiertas en el primer nivel.

El resultado práctico es un ciclo de feedback rápido en la CI y una suite de tests que sobrevive intacta a una migración de infraestructura. Para el argumento más amplio, consulta beneficios del TDD para el negocio.

Errores comunes

Dominio anémico. Entidades que son bolsas de getters y setters, con toda la lógica en "services". Entonces tienes un hexágono que no protege nada. Si Order no puede calcular su propio total ni rechazar una lista de líneas vacía, la frontera es decoración cara. Empuja las reglas hacia los objetos de dominio; deja los casos de uso como orquestación.

Filtrar entidades de ORM por los puertos. Un puerto cuya firma es save(entity: PrismaOrder) o findById(): Promise<TypeOrmOrderEntity> ha hecho que el núcleo dependa de la librería de base de datos. La solución es un mapper dentro del adaptador y tipos de dominio en el puerto, como en el ejemplo de arriba. Lo mismo aplica a los DTOs HTTP en el lado driving: el caso de uso recibe un comando, no un objeto de request.

Sobreabstraer una app CRUD. Un formulario que escribe una fila y la vuelve a leer no necesita un caso de uso, dos puertos y tres adaptadores. Obtienes cuatro archivos por campo y ninguna protección, porque no hay reglas que proteger. Reserva el patrón para módulos con lógica real.

Interfaces de repositorio genéricas. Repository<T> con findAll, findWhere, count invita a la capa de aplicación a escribir consultas. Los puertos deben listar las operaciones que los casos de uso necesitan de verdad, nombradas en términos de dominio.

Framework en el núcleo. Decoradores del framework web en casos de uso, @Injectable() en clases de dominio, excepciones del framework cruzando el puerto. Algunos equipos aceptan un decorador de DI en application/; ninguno debería aceptarlo en domain/.

Un hexágono para todo el sistema. Una única carpeta application/ con 200 casos de uso es un monolito en capas con nombres nuevos. Dibuja un hexágono por bounded context o módulo, como se comenta más abajo.

Cuándo no usar arquitectura hexagonal

Un checklist de decisión. Si la mayoría de respuestas son "no", una estructura en capas más simple te servirá mejor.

  • ¿El módulo contiene reglas de negocio más allá de la validación de entrada y la persistencia?
  • ¿Es plausible que una dependencia driven (base de datos, proveedor de pagos, mensajería) cambie durante la vida del producto?
  • ¿Necesitas tests rápidos que corran sin infraestructura?
  • ¿Más de un driver va a llamar a la misma lógica (HTTP y una cola, o HTTP y una CLI)?
  • ¿El código vivirá lo suficiente para que la mantenibilidad importe más que la velocidad inicial?
  • ¿El equipo entiende la inversión de dependencias, o hay tiempo para enseñarla?

Evita el patrón en prototipos con vida corta conocida, CRUD administrativo interno, proxies finos y servicios de pegamento, y scripts. Evítalo también si el equipo no va a hacer cumplir la frontera; un hexágono con import { Pool } from 'pg' dentro de domain/ es peor que una aplicación en capas honesta, porque promete un aislamiento que no existe. Las reglas de lint (por ejemplo, no-restricted-imports de ESLint por carpeta, o dependency-cruiser) son un seguro barato.

Cómo encaja con DDD, microservicios y monolitos modulares

Con Domain-Driven Design. DDD aporta el contenido del hexágono: agregados, value objects, eventos de dominio, un lenguaje ubicuo. La hexagonal aporta la cáscara que mantiene ese modelo libre de infraestructura. Los bounded contexts se mapean de forma natural a hexágonos; las anti-corruption layers son simplemente adaptadores entre dos contextos. Consulta Domain-Driven Design en la práctica para el lado del modelado.

Con monolitos modulares. Este es el punto óptimo en 2026 para la mayoría de productos medianos. Cada módulo (orders/, billing/, catalog/) es un hexágono con sus propios puertos y adaptadores, desplegado como un único proceso. Los módulos hablan entre sí solo a través de puertos driving (o eventos publicados), nunca a través de los repositorios de los demás. Como las fronteras existen en el código, extraer un módulo a un servicio más adelante es, sobre todo, un cambio en el composition root y en los adaptadores.

Con microservicios. Cada servicio es un hexágono. El patrón mantiene el núcleo del servicio independiente del transporte (REST hoy, gRPC o eventos mañana) y hace rápidos los tests in-process, lo que importa más en un sistema distribuido, donde los tests end-to-end son lentos e inestables. No resuelve las preocupaciones distribuidas — consistencia, contratos entre servicios, observabilidad — que se cubren en microservicios vs arquitectura monolítica y estrategias de testing para microservicios.

Con sistemas basados en eventos. Un consumidor de mensajes es un adaptador driving; un publicador de eventos es un puerto driven. El núcleo emite eventos de dominio; un adaptador los mapea a Kafka, SNS o una tabla de outbox. Ese mapeo es el sitio para gestionar la serialización y el versionado, no el dominio. Más sobre el patrón en arquitectura basada en eventos.

Recomendación

Usa la arquitectura hexagonal por módulo, no por sistema, y solo en módulos con reglas de negocio reales. Combínala con la división interna de Clean Architecture (dominio y aplicación/casos de uso) y mantén el vocabulario hexagonal en el borde (puertos driving y driven, adaptadores). Define los puertos en tipos de dominio, mapea a tipos de ORM o de transporte dentro de los adaptadores, y escribe un test de contrato que corra contra cada adaptador de un puerto, para que tus fakes sigan siendo fieles. Haz cumplir la dirección de los imports con herramientas desde el primer día.

Empieza con un monolito modular en el que cada módulo sea un hexágono; obtienes la mayor parte de la testabilidad y la reemplazabilidad de los microservicios sin el coste operativo, y la puerta a la extracción sigue abierta. En Arvucore solemos recomendar esta forma para productos que se espera que vivan más de un par de años, y una estructura en capas simple para todo lo demás.

¿Listo para Transformar tu Negocio?

Hablemos sobre cómo nuestras soluciones pueden ayudarte a alcanzar tus objetivos. Ponte en contacto con nuestros expertos hoy mismo.

Hablar con un Experto

Tags:

arquitectura hexagonalclean architecturepatrones de diseño de softwarepuertos y adaptadoresarquitectura oniondomain-driven design
Equipo Arvucore

Equipo Arvucore

El equipo editorial de Arvucore está formado por profesionales experimentados en desarrollo de software. Estamos dedicados a producir y mantener contenido de alta calidad que refleja las mejores prácticas de la industria e insights confiables.

Preguntas frecuentes

¿Cuál es la diferencia entre arquitectura hexagonal y clean architecture?
Comparten la misma idea central: lógica de negocio en el centro, dependencias apuntando hacia dentro, infraestructura en el borde. La hexagonal (2005) describe dos tipos de frontera, puertos y adaptadores, y no dice nada sobre capas internas. Clean Architecture (2012) prescribe capas concéntricas (entidades, casos de uso, adaptadores de interfaz, frameworks) y nombra la regla de dependencia de forma explícita. En la práctica, la mayoría de equipos implementa una mezcla.
¿Qué es un puerto y qué es un adaptador?
Un puerto es una interfaz que pertenece al núcleo de la aplicación y describe cómo el exterior habla con él (puerto driving) o cómo él habla con el exterior (puerto driven). Un adaptador es el código concreto que implementa o llama a esa interfaz: un controller HTTP, una CLI, un repositorio Postgres, un cliente SMTP.
¿Cuál es la diferencia entre puertos driving y driven?
Los puertos driving (primarios) los llama el exterior para accionar la aplicación, por ejemplo un caso de uso PlaceOrder invocado por un controller REST. Los puertos driven (secundarios) los llama la aplicación para llegar al exterior, por ejemplo un OrderRepository implementado por un adaptador de base de datos.
¿La arquitectura hexagonal es excesiva para una app CRUD?
Normalmente sí. Si la aplicación no tiene reglas de negocio más allá de validación y persistencia, puertos y adaptadores añaden indirección sin proteger nada. Úsala cuando la lógica de dominio merezca aislarse y cuando esperes que la infraestructura cambie o necesites tests rápidos sin I/O.
¿La arquitectura hexagonal exige DDD?
No. La arquitectura hexagonal trata de dónde está la frontera; DDD trata de qué va dentro de ella. Combinan bien, porque DDD aporta un modelo de dominio rico que merece protegerse, pero puedes usar puertos y adaptadores con un dominio simple.
¿La arquitectura hexagonal funciona con microservicios?
Sí, y también con monolitos modulares. Cada servicio o módulo tiene su propio hexágono. El patrón facilita mover un módulo a un servicio separado más adelante, porque la frontera ya existe en el código.

Artículos relacionados

Arquitectura de software: diseño basado en dominios en la práctica

Arquitectura de software: diseño basado en dominios en la práctica

El diseño orientado al dominio (DDD) ofrece un enfoque pragmático para modelar dominios empresariales complejos dentro de la arquitectura de software. Este artículo de Arvucore explica estrategias prácticas de implementación de DDD, patrones y compensaciones para equipos que enfrentan desafíos complejos de arquitectura de software. Guía a líderes técnicos y tomadores de decisiones a través de contextos delimitados, patrones tácticos y alineación organizacional para ofrecer sistemas sostenibles y alineados con el negocio hoy mismo.

Arquitectura basada en eventos: sistemas resilientes y escalables

Arquitectura basada en eventos: sistemas resilientes y escalables

En Arvucore, exploramos cómo la arquitectura basada en eventos transforma los sistemas distribuidos modernos, permitiendo aplicaciones ágiles, resilientes y escalables. Este artículo examina los principios fundamentales, los patrones de diseño prácticos y las consideraciones operativas para la implementación de sistemas basados en eventos en entornos empresariales. Los lectores encontrarán orientación sobre opciones de arquitectura, estrategias de integración y beneficios mensurables para impulsar la agilidad empresarial y la solidez técnica en las implementaciones de producción.

GraphQL vs REST en 2026: cuándo usar cada estilo de API

GraphQL vs REST en 2026: cuándo usar cada estilo de API

GraphQL vs REST comparados en fetching, caché, versionado, errores, N+1, seguridad y herramientas, con checklist de decisión por escenario y ejemplo lado a lado.

Micro Frontends: Arquitectura Escalable para Grandes Aplicaciones

Micro Frontends: Arquitectura Escalable para Grandes Aplicaciones

En Arvucore, exploramos cómo los micro frontends permiten una arquitectura frontend resiliente y modular para aplicaciones a gran escala. Al descomponer interfaces de usuario monolíticas en partes implementables de forma independiente, los equipos ganan autonomía, lanzamientos más rápidos y una propiedad más clara. Este artículo describe patrones de diseño, estrategias de integración, ventajas y desventajas en términos de rendimiento y enfoques de gobernanza para ayudar a los líderes empresariales y equipos técnicos europeos a evaluar los micro frontends para su próximo gran proyecto.