Be Software Architecture Wise
Be Wise Apps

Read in English · Lire en français · Auf Deutsch lesen

Sección 5.6 de 5.8 · Intermedio · 25 min de lectura

Arquitectura hexagonal, de cebolla y limpia

Videolección

5 min · narrado en inglés, con subtítulos
Solo escuchar (audio)
Leer la transcripción

Esta lección trata una idea con tres nombres: arquitectura hexagonal, de cebolla y limpia. Mantiene tus reglas de negocio independientes de las bases de datos, los frameworks y los servicios externos, para que puedas probarlas rápidamente y sustituir la tecnología que las rodea.

Piensa en un adaptador de viaje. El cargador de tu portátil tiene una forma de enchufe, y cada país tiene su propia toma de corriente. No reconstruyes el portátil para cada país. Llevas pequeños adaptadores que encajan en la toma por un lado y en tu enchufe por el otro. Estos estilos construyen el software de la misma manera. Las reglas de negocio tienen sus propias tomas estándar, llamadas puertos. Las bases de datos, los sitios web, las empresas de pagos y las herramientas de prueba se conectan mediante adaptadores, así que cualquiera de ellos puede sustituirse sin tocar las reglas.

Estos estilos surgieron de la frustración con las capas, donde la capa de negocio depende de la persistencia, así que las reglas acaban moldeadas por las tablas, y probar una regla implica arrancar una base de datos. La respuesta de Alistair Cockburn, en 2005, fue la arquitectura hexagonal, también llamada de puertos y adaptadores. La arquitectura de cebolla de Jeffrey Palermo llegó en 2008. Y Robert Martin reunió estas ideas como arquitectura limpia, en una entrada de blog en 2012 y en un libro en 2017. Las tres invierten la dependencia. El código de negocio declara las interfaces que necesita, y la infraestructura las implementa.

Este es el hexágono. Dentro está la aplicación: el dominio y sus casos de uso. La forma en sí no tiene significado; lo que importa es dentro frente a fuera. Un puerto es una interfaz que pertenece a la aplicación. A la izquierda están los puertos conductores, como Hacer pedido y Seguir pedido: la API propia de la aplicación. Los adaptadores conductores los llaman: un controlador REST, la API de una app móvil, un arnés de pruebas o un script por lotes. Cada uno convierte su entrada en una llamada con valores del dominio. A la derecha están los puertos conducidos, como Pedidos y Pagos. La aplicación los llama cuando necesita algo del mundo exterior. Los adaptadores conducidos los implementan: un repositorio PostgreSQL o uno en memoria, un proveedor de tarjetas real o uno falso. En tiempo de ejecución la aplicación llama al adaptador, pero en el código, el adaptador depende de la aplicación.

La arquitectura limpia dibuja la misma idea como anillos. En el centro están las entidades: las reglas de toda la empresa, como Order y Money. A su alrededor, los casos de uso: las reglas específicas de la aplicación, como Hacer pedido. Después, los adaptadores de interfaz: controladores, presentadores e implementaciones de repositorios. Y en el borde, los frameworks y drivers: el framework web, la base de datos, la interfaz de usuario. La regla de dependencia lo une todo: las dependencias del código fuente apuntan solo hacia dentro. Nada de un anillo interior puede nombrar nada declarado en uno exterior.

Veamos por qué importa. La plataforma de reparto de comida debe pasarse a un nuevo proveedor de tarjetas en un plazo de seis semanas, por un cambio de precios. En una versión del sistema, el proceso de pago llama directamente al kit de desarrollo de software del proveedor antiguo, en veintitrés lugares. Sus tipos de error se capturan en once lugares, y sus objetos se guardan en la base de datos. Hay que cambiar todos los puntos de llamada, migrar los datos guardados y reescribir las pruebas de pagos. Eso son de cinco a seis semanas, con un cambio arriesgado de todo a la vez. En la otra versión, una clase adaptadora de unas doscientas líneas implementa un puerto de pasarela de pagos. El núcleo solo conoce charge, refund y su propio tipo de resultado. Así que el equipo escribe un segundo adaptador, ejecuta contra él las pruebas existentes del puerto y luego pasa el uno por ciento de los pagos, después el diez y después todos. Dos semanas, reversible en cada paso. El puerto costó unos tres días adicionales al principio. Su valor depende de lo probable y lo costoso que sea el cambio. Y debe hablar el lenguaje del dominio, no reflejar la API de un solo proveedor.

Usa esta estructura cuando la lógica de negocio sea considerable y vaya a sobrevivir a los frameworks actuales, y cuando importen las pruebas rápidas y aisladas de las reglas de negocio. También compensa cuando te integras con servicios externos que probablemente cambien. Ten cuidado con los prototipos, las herramientas de vida corta y las aplicaciones que sobre todo trasladan campos de formulario a tablas. Ahí, las interfaces adicionales y el código de mapeo añaden archivos sin añadir protección. Y, como con las capas, haz cumplir la regla automáticamente, con import-linter, con la regla de arquitectura de cebolla ya preparada de ArchUnit o con un módulo de dominio que ni siquiera compile si tiene una dependencia de un framework.

La sección completa construye en Python el caso de uso Hacer pedido, con un adaptador real y un doble de prueba, y compara los tres estilos uno al lado del otro. A continuación, en la Sección cinco punto siete, encontramos buenos límites con el diseño guiado por el dominio. Nos vemos allí.

Lo que necesitas saber

Una idea, tres nombres

La arquitectura hexagonal, la arquitectura de cebolla y la arquitectura limpia son tres formulaciones de una misma idea: poner la lógica de negocio en el centro, hacerla independiente de las interfaces de usuario, las bases de datos, los frameworks y los servicios externos, y hacer que todas las dependencias del código fuente apunten hacia ella. Describen la estructura interna de una aplicación, así que se combinan con cualquier estilo de despliegue: un único monolito, un monolito modular cuyos módulos se construyen cada uno de esta manera, o un microservicio.

Surgieron de la frustración con la clásica arquitectura en capas. En el estilo en capas de la Sección 5.2, la capa de negocio depende de la capa de persistencia, que depende de la base de datos, así que las reglas de negocio acaban moldeadas por las tablas y el mapeo objeto-relacional, y probar una regla implica arrancar una base de datos. La familia de estilos de esta sección invierte esa dependencia con el principio de inversión de dependencias: el código de negocio declara las interfaces que necesita, y el código de infraestructura las implementa. El principio en sí se trata en la Sección 3.5; esta sección muestra las arquitecturas construidas sobre él.

Arquitectura hexagonal: puertos y adaptadores

Alistair Cockburn describió la idea por primera vez en la wiki del Portland Pattern Repository y la desarrolló en un artículo en 2005 con el nombre, más descriptivo, de puertos y adaptadores. Su intención declarada era permitir que una aplicación sea dirigida por igual por usuarios, programas, pruebas automatizadas o scripts por lotes, y desarrollarla y probarla aislada de sus futuras bases de datos y dispositivos. En 2024, él y Juan Manuel Garrido de Paz publicaron un libro breve, Hexagonal Architecture Explained, que reformula el patrón.

Un puerto es una conversación con un propósito entre la aplicación y el mundo exterior, expresada como una interfaz: "hacer un pedido", "guardar pedidos", "cobrar una tarjeta". Un adaptador es el código que traduce entre un puerto y una tecnología concreta: un controlador HTTP, un repositorio SQL, el SDK de un proveedor de pagos, un script de línea de comandos, un fake en memoria. Un puerto puede tener muchos adaptadores. La aplicación dentro del hexágono solo conoce sus puertos.

El hexágono no tiene ningún significado como forma; Cockburn lo eligió para escapar de la imagen de capas de arriba abajo y para dejar espacio para dibujar varios puertos en cada lado. Lo que importa es el límite entre dentro y fuera y la asimetría entre dos tipos de puerto.

Puertos conductores y conducidos

Arquitectura hexagonal: los adaptadores conductores (izquierda) llaman a los puertos conductores; la aplicación llama a los puertos conducidos (derecha), que implementan los adaptadores conducidosLado conductor (primario)Lado conducido (secund.)Aplicacióndominio + c. de usoPlaceOrderTrackOrderp. conductoresPedidosPagosp. conducidosControlador RESTAPI de app móvilArnés pruebasScript batchRepo PostgreSQLRepo en memoriaProv. tarjetasPagos falsosFlechas continuas: el adaptador llama a un puerto. Discontinuas: lo implementa. Todas las dependencias apuntan hacia dentro.
Los adaptadores conductores llaman a la aplicación a través de los puertos conductores; la aplicación llama a los puertos conducidos, que implementan los adaptadores conducidos, de modo que la base de datos y el proveedor de pagos son plug-ins sustituibles.

Lado conductor y lado conducido

Los puertos y adaptadores conductores (primarios) se sitúan a la izquierda de la imagen habitual. Un adaptador conductor recibe una solicitud de un actor primario (el navegador de un usuario, una app para dispositivos móviles, otro sistema, una prueba, un trabajo programado) y llama a un puerto conductor, que es la API propia de la aplicación: en la práctica, un caso de uso como PlaceOrder. El adaptador convierte HTTP, un mensaje o un argumento de línea de comandos en una llamada con valores del dominio, y convierte el resultado de nuevo en una respuesta.

Los puertos y adaptadores conducidos (secundarios) se sitúan a la derecha. La aplicación los llama cuando necesita algo del mundo exterior: guardar un pedido, cobrar una tarjeta, enviar una notificación, leer la hora. El puerto es una interfaz que pertenece a la aplicación y está escrita en sus términos (OrderRepository.save(order)), y un adaptador conducido la implementa para una tecnología (PostgreSQL, la API de un proveedor de tarjetas, una pasarela de SMS). Aquí es donde se produce la inversión de dependencias: en tiempo de ejecución la aplicación llama al adaptador, pero en el código fuente el adaptador depende de la interfaz de la aplicación, y no al revés.

El texto original de Cockburn habla de actores primarios y secundarios; los términos conductor y conducido son hoy más comunes, y él mismo los usa. La distinción importa para las pruebas: los puertos conductores se ejercitan con arneses de pruebas que actúan como usuarios, y los puertos conducidos se sustituyen por dobles de prueba. También importa para el cambio: los canales nuevos (un asistente de voz, una API para socios) llegan como adaptadores conductores nuevos, y la infraestructura nueva llega como adaptadores conducidos nuevos.

Alguien tiene que crear los adaptadores y conectarlos a la aplicación. Eso ocurre en una única raíz de composición, normalmente el código de arranque del programa, que es la única parte del sistema que conoce todos los adaptadores. Un framework de inyección de dependencias puede hacer este trabajo, pero unas simples llamadas a constructores en main suelen ser más claras.

Arquitectura de cebolla

Jeffrey Palermo describió la arquitectura de cebolla en una serie de entradas de blog a partir de julio de 2008, pensada para aplicaciones de negocio de larga vida con un comportamiento considerable (la desaconsejaba para sitios web pequeños). Dibujó anillos concéntricos: el modelo de dominio en el centro, los servicios de dominio a su alrededor, los servicios de aplicación alrededor de estos, y un anillo exterior que contiene la interfaz de usuario, la infraestructura (incluida la base de datos) y las pruebas. Sus principios clave son que la aplicación se construye en torno a un modelo de objetos independiente, que las capas interiores definen interfaces que implementan las capas exteriores, que todo el acoplamiento va hacia el centro y que el núcleo de la aplicación puede compilarse y ejecutarse por separado de la infraestructura.

Frente a la arquitectura hexagonal, la de cebolla añade anillos con nombre dentro de la aplicación y hace explícito un punto que la imagen de Cockburn deja implícito: la base de datos no es el cimiento del sistema, sino un servicio externo en el borde. Las interfaces de repositorio viven en el núcleo; sus implementaciones viven en el anillo de infraestructura.

La arquitectura limpia y la regla de dependencia

Robert C. Martin publicó "The Clean Architecture" en su blog en agosto de 2012 como síntesis de la arquitectura hexagonal, la arquitectura de cebolla y otras propuestas similares, y la amplió en el libro Clean Architecture en 2017. Su imagen tiene cuatro anillos. Las entidades contienen las reglas de negocio de toda la empresa. Los casos de uso contienen las reglas específicas de la aplicación que orquestan las entidades. Los adaptadores de interfaz convierten los datos entre la forma que necesitan los casos de uso y la que necesitan los agentes externos: controladores, presentadores y gateways. Los frameworks y drivers forman el anillo exterior: el framework web, la base de datos, la interfaz de usuario.

La regla de dependencia lo une todo: las dependencias del código fuente apuntan solo hacia dentro, y nada de un anillo interior puede nombrar nada declarado en un anillo exterior, ya sea una clase, una función o un formato de datos. Cuando el control debe fluir hacia fuera (un caso de uso que guarda un pedido), el anillo interior declara una interfaz y el anillo exterior la implementa. Los datos que cruzan un límite deben ser estructuras simples en los términos del anillo interior, nunca una fila de base de datos ni un objeto de solicitud HTTP. Martin sostiene también que la estructura de nivel superior debe revelar el propósito del sistema (pedidos, pagos, entregas) y no su framework, una idea que llama arquitectura que grita (screaming architecture).

Los anillos de la arquitectura limpia

Arquitectura limpia: cuatro anillos concéntricos, con las entidades en el centro y luego casos de uso, adaptadores de interfaz, y frameworks y drivers; las dependencias del código apuntan solo hacia dentroEntidadesC. de usoAdapt. de interfazFrameworks y driversExterior: framework web, ORM,BD, broker de mensajes, UIAdapt. de interfaz: controladores,presentadores, implement. de repositoriosCasos de uso: reglas de aplicación,p. ej. PlaceOrderEntidades: reglas de la empresa,p. ej. Order, MoneyLa regla de dependencia: el códigofuente solo depende hacia dentro
Cada anillo solo puede depender de los anillos que tiene dentro; las entidades no dependen de nada, y los frameworks y drivers se sitúan en el borde, donde pueden sustituirse.

Arquitectura hexagonal, de cebolla y limpia comparadas

AspectoHexagonal (puertos y adaptadores)De cebollaLimpia
Creador y fechaAlistair Cockburn; desarrollada en un artículo en 2005Jeffrey Palermo; serie de entradas de blog desde julio de 2008Robert C. Martin; entrada de blog en agosto de 2012, libro en 2017
ImagenUn hexágono con un interior (la aplicación) y un exterior (los adaptadores), el lado izquierdo conductor y el derecho conducidoAnillos concéntricos: modelo de dominio, servicios de dominio, servicios de aplicación y, después, interfaz de usuario, infraestructura y pruebasAnillos concéntricos: entidades, casos de uso, adaptadores de interfaz, frameworks y drivers
Regla centralLa aplicación habla con el mundo solo a través de puertos; los adaptadores traducenTodo el acoplamiento va hacia el centro; el núcleo define interfaces que implementa el anillo exteriorLa regla de dependencia: las dependencias del código fuente apuntan solo hacia dentro
Estructura interna prescritaNinguna: solo dentro frente a fueraSí: anillos interiores con nombre para el dominioSí: entidades y casos de uso, más orientaciones sobre los presentadores y los datos que cruzan los límites
Énfasis principalSimetría: usuarios, pruebas, scripts y otros programas pueden dirigir la aplicación por igualPoner el modelo de dominio, y no la base de datos, en el centro de una aplicación de negocioIndependencia de los frameworks, la interfaz de usuario, la base de datos y los agentes externos; reglas de negocio que se pueden probar
Vocabulario típicoPuerto, adaptador, conductor (primario), conducido (secundario), actorModelo de dominio, servicios de dominio, servicios de aplicación, infraestructuraEntidad, caso de uso (interactor), gateway, presentador, controlador

Un caso de uso con un puerto y dos adaptadores

El siguiente ejemplo en Python implementa al estilo hexagonal el caso de uso PlaceOrder de la plataforma de reparto de comida. El primer fragmento es el interior: una pequeña clase de dominio, dos puertos conducidos escritos como clases typing.Protocol y el caso de uso, que actúa como puerto conductor. El segundo fragmento es el exterior: un adaptador de PostgreSQL y un doble de prueba en memoria que implementan el mismo puerto. El tercero muestra por qué importa: una prueba unitaria rápida y la raíz de composición donde se conectan los adaptadores de producción.

from dataclasses import dataclass
from decimal import Decimal
from typing import Protocol

@dataclass
class Order:
    id: str
    total: Decimal
    status: str = "new"

class OrderRepository(Protocol):        # driven port
    def save(self, order: Order) -> None: ...

class PaymentGateway(Protocol):         # driven port
    def charge(self, order_id: str, amount: Decimal) -> bool: ...

class PlaceOrder:                       # driving port + use case
    def __init__(self, orders: OrderRepository, payments: PaymentGateway):
        self.orders, self.payments = orders, payments

    def __call__(self, order_id: str, total: Decimal) -> Order:
        if total <= 0:
            raise ValueError("empty basket")
        order = Order(order_id, total)
        order.status = "paid" if self.payments.charge(order_id, total) else "payment-failed"
        self.orders.save(order)
        return order
Dentro del hexágono: una regla de dominio, dos puertos conducidos y el caso de uso PlaceOrder. Nada de esto importa un framework web, un driver de base de datos ni un SDK de pagos.
class PostgresOrderRepository:          # real driven adapter
    def __init__(self, conn):
        self.conn = conn
    def save(self, order):
        with self.conn.cursor() as cur:
            cur.execute(
                "INSERT INTO orders (id, total, status) VALUES (%s, %s, %s) "
                "ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status",
                (order.id, order.total, order.status))
        self.conn.commit()

class InMemoryOrderRepository:          # test double, same port
    def __init__(self):
        self.saved = {}
    def save(self, order):
        self.saved[order.id] = order
Fuera del hexágono: un adaptador real y un doble de prueba para el mismo puerto. El caso de uso no puede distinguirlos.
class DecliningPayments:
    def charge(self, order_id, amount): return False

def test_failed_payment_is_recorded():
    orders = InMemoryOrderRepository()
    place = PlaceOrder(orders, DecliningPayments())
    place("A-17", Decimal("23.50"))
    assert orders.saved["A-17"].status == "payment-failed"

# composition root (main.py): the only place that knows every adapter
# place_order = PlaceOrder(PostgresOrderRepository(conn), CardProviderGateway(key))
# app.add_route("/orders", make_rest_controller(place_order))   # driving adapter
Una prueba unitaria dirige el caso de uso directamente, sin servidor web, base de datos ni red; la conexión de producción se hace en un solo lugar al arrancar.

Fíjate en lo que el caso de uso no contiene: nada de SQL, ni códigos de estado HTTP, ni tipos de excepción específicos del proveedor, ni decoradores del framework. La prueba se ejecuta en milisegundos y cubre un caso (un pago rechazado) que sería incómodo de preparar contra el sandbox de un proveedor real. El adaptador de PostgreSQL necesita sus propias pruebas, pero son pruebas de integración centradas en una sola clase contra una base de datos real, no pruebas de reglas de negocio. Este es el beneficio para la capacidad de prueba descrito en la Sección 2.6: controlabilidad y observabilidad mediante dependencias inyectadas.

Ejemplo práctico

Ejemplo práctico: cambiar de proveedor de pagos

La plataforma de reparto de comida debe pasar de un proveedor de tarjetas a otro en un plazo de seis semanas por un cambio de precios. Compara el trabajo en dos versiones del sistema: una en la que el código del proceso de pago llama directamente al SDK del proveedor antiguo en 23 lugares, y otra construida con un puerto conducido PaymentGateway y un adaptador.

Llamadas directas: encontrar el impactoLa búsqueda encuentra el SDK en el proceso de pago, los reembolsos, las suscripciones y tres pantallas de administración. Los tipos de excepción propios del SDK se capturan en 11 lugares, y su objeto Charge se guarda en una columna de la base de datos.
Llamadas directas: el cambioCambian todos los puntos de llamada, más una migración de datos para los objetos guardados. Todas las pruebas que tocaban los pagos se escribieron contra el sandbox del SDK antiguo y hay que reescribirlas. Estimación: de 5 a 6 semanas, con un cambio arriesgado de todo a la vez.
Puertos y adaptadores: encontrar el impactoUna clase adaptadora (unas 200 líneas) implementa PaymentGateway. El núcleo solo conoce charge, refund y el tipo PaymentResult propio del núcleo.
Puertos y adaptadores: el cambioEscribir un segundo adaptador para el proveedor nuevo; ejecutar contra él las pruebas de contrato existentes del puerto; elegir el adaptador por pedido con un feature flag para que el 1%, luego el 10% y luego el 100% de los pagos usen el nuevo. Estimación: 2 semanas, reversible en cada paso.
Lo que costó el puertoDiseñar el puerto y el adaptador al principio llevó unos 3 días adicionales, y cada funcionalidad de pago nueva necesita un pequeño cambio en el puerto además de en el adaptador.

El valor de un puerto es proporcional a la probabilidad y al costo del cambio que aísla. Los proveedores de pago, los intermediarios de mensajes (message brokers) y las API de terceros cambian a menudo; el motor de tu propia base de datos principal puede no cambiar nunca, y envolverlo en puertos elaborados es más difícil de justificar.

El puerto debe hablar el lenguaje del dominio (charge, PaymentResult), no el del proveedor antiguo. Un puerto que refleja la API de un solo proveedor no protege nada.

Características de la familia de puertos y adaptadores

CaracterísticaValoraciónMotivo
Capacidad de prueba (testabilidad)AltoLas reglas de negocio pueden probarse en memoria, de forma rápida y determinista, conectando dobles de prueba a los puertos conducidos.
Modificabilidad de la infraestructuraAltoSustituir una base de datos, un proveedor de pagos o un framework web implica escribir un adaptador nuevo; el núcleo no cambia.
Modificabilidad de las reglas de negocioDe media a altaLas reglas viven en un solo lugar, libres de código del framework, pero una funcionalidad sigue atravesando puerto, caso de uso y adaptador.
SencillezDe baja a mediaMás tipos, interfaces y código de mapeo que una aplicación en capas sencilla; al principio cuesta más a los recién llegados.
Velocidad de desarrollo (al inicio)MediaLa estructura adicional ralentiza las primeras funcionalidades de una aplicación CRUD sencilla; se amortiza a medida que crece el dominio.
Costo totalDe barato a medioSin infraestructura adicional; el costo es el tiempo de los desarrolladores dedicado a escribir y mantener puertos, adaptadores y código de mapeo.
RendimientoDe media a altaLas llamadas a interfaces no cuestan casi nada; las verdaderas sobrecargas son el mapeo de objetos entre capas y la pérdida de algunas comodidades del ORM.
Escalabilidad, desplegabilidad, tolerancia a fallosNo los decide este estiloDependen del estilo de despliegue (monolito o servicios) en el que se sitúa el hexágono.

Costos, críticas y cuándo usarla

Los beneficios son reales, y también los costos. Cada puerto es una interfaz adicional; cada límite puede necesitar código de mapeo para convertir entre, por ejemplo, una entidad del ORM, un objeto de dominio y una respuesta de la API. Para una aplicación que sobre todo traslada campos de formulario a tablas con poca lógica entre medias, esta estructura añade archivos sin añadir protección, y las convenciones de un framework (un modelo active record, una interfaz de administración generada) pueden dar un resultado mejor y más rápido. Algunos profesionales con experiencia sostienen que las versiones estrictas de la arquitectura limpia producen demasiada indirección y que la mayoría de los equipos nunca cambian de base de datos; otros responden que el beneficio principal no es cambiar de tecnología, sino pruebas rápidas y fiables y un código de negocio que se puede leer sin conocer el framework. Ambos tienen razón en contextos distintos.

Úsala cuando la lógica de negocio es considerable y va a sobrevivir a los frameworks actuales; cuando el sistema se integra con servicios externos que probablemente cambien; cuando importan las pruebas rápidas y aisladas de las reglas de negocio; y como estructura interna de los módulos de un monolito modular o de servicios construidos con diseño guiado por el dominio (Sección 5.7). Ten cuidado con las aplicaciones CRUD sencillas, los prototipos y las herramientas de vida corta, donde la estructura predeterminada del framework suele bastar. Es común un término medio pragmático: puertos para los servicios externos y para todo lo que probablemente cambie, y las comodidades propias del framework en lo demás.

Hacer cumplir la regla de dependencia

Como ocurre con las capas, la regla se erosiona a menos que la compruebe una máquina. En Python, import-linter puede declarar los anillos como un contrato de tipo layers (adaptadores por encima de la aplicación, y esta por encima del dominio) y añadir contratos de tipo forbidden que impidan que los paquetes de dominio y de aplicación importen frameworks web, drivers de base de datos o SDK. En Java, ArchUnit tiene una regla ya preparada, onionArchitecture(), en la que nombras los paquetes del modelo de dominio, de los servicios de dominio, de los servicios de aplicación y de los adaptadores, y hace fallar una prueba si alguna dependencia apunta hacia fuera. Dividir el código en módulos de compilación separados (un módulo de dominio sin ninguna dependencia de frameworks) hace que las dependencias hacia fuera sean imposibles de compilar, que es la comprobación más fuerte de todas. Estas comprobaciones son ejemplos de las funciones de aptitud que se tratan en el Capítulo 11.

Una estructura de paquetes que hace visible la regla ayuda a quienes revisan: por ejemplo orders/domain, orders/application (casos de uso y puertos) y orders/adapters/{rest,postgres,payments}. Organizar primero por área de negocio y después por anillo mantiene la estructura alineada con los límites del dominio de las secciones 5.3 y 5.7.

Comprueba lo que has entendido

¿Un controlador REST es un adaptador conductor o conducido? ¿Y un repositorio SQL?

Un controlador REST es un adaptador conductor: recibe solicitudes y llama a la aplicación. Un repositorio SQL es un adaptador conducido: implementa una interfaz a la que llama la aplicación.

En la arquitectura limpia, ¿puede un caso de uso devolver una entidad del ORM a un controlador?

No. El ORM pertenece al anillo exterior, así que un caso de uso que lo nombra tiene una dependencia hacia fuera. Debe devolver una estructura simple o un objeto de dominio definido en un anillo interior.

¿Dónde se crean los adaptadores y se conectan a la aplicación?

En una raíz de composición, normalmente el código de arranque, que es el único lugar que conoce todos los adaptadores.

¿Qué añade la arquitectura de cebolla a la imagen hexagonal?

Anillos con nombre dentro de la aplicación (modelo de dominio, servicios de dominio, servicios de aplicación) y la afirmación explícita de que la base de datos es una inquietud externa situada en el borde, no el cimiento.

Siguiente · 5.7Diseño guiado por el dominioEncontrar límites en el dominio de negocio: lenguaje ubicuo, subdominios centrales, de soporte y genéricos, contextos delimitados, relaciones del mapa de contextos, event storming, agregados y un mapa de contextos de la plataforma de reparto de comida.Continuar →

Fuentes y lecturas recomendadas

¿Tienes una pregunta sobre esta sección? ArchitectBot, el asistente de estudio con IA del curso, responde primero a partir del curso.

Pregunta: “Nuestra app de Django tiene las reglas de negocio repartidas entre modelos, vistas y tareas de Celery. ¿Cómo podríamos introducir puertos y adaptadores de forma gradual sin reescribirla?”