Arquitectura Hexagonal

¿Qué es la Arquitectura Hexagonal? 🤔

🎯 También conocido como Diseño de Puertos y Adaptadores (Ports and Adapters)

Es un Patrón de Diseño de Software cuyo objetivo principal es «aislar» el núcleo de la aplicación (lógica de negocio) de sus dependencias externas, como base de datos, interfaces de usuarios, servicios web, entre otros.

En términos simples, este patrón busca que el núcleo de la aplicación permanezca independiente, de modo que pueda ser desarrollado, probado y evolucionado sin depender de tecnologías o mecanismos externos. Para lograr esto, se utilizan puertos (interfaces) que definen puntos de Entrada y Salida, y adaptadores que conectan estos puertos con implementaciones concretas.

Simple representación del comportamiento de esta arquitectura. Comunicación con el exterior

De esta forma, el núcleo no sabe nada de las tecnologías externas: solo se comunica a través de interfaces, permitiendo una arquitectura más mantenible, testeable y flexible frente a cambios tecnológicos.

La arquitectura hexagonal no solo propone una estructura técnica, sino también una forma de pensar el diseño del software desde adentro hacia afuera. En lugar de construir una aplicación en torno a una tecnología específica (como un framework web o una base de datos), este enfoque pone en el centro a la lógica del dominio, y organiza el resto del sistema como elementos periféricos que pueden conectarse o reemplazarse sin afectar ese núcleo.

Uno de los beneficios clave de esta arquitectura es que facilita el cambio: cambiar una base de datos, remplazar una interfaz de usuario o modificar el mecanismo de entrada (por ejemplo, de una API a una cola de eventos) no requiere reescribir la lógica de negocio, ya que cada componente externo está desacoplado del corazón del sistema.

La lógica de negocio se encarga de definir el contrato, mientras que las dependencias son responsables de cumplir con los requisitos establecidos por esta.

En una arquitectura hexagonal, el núcleo (core) de la aplicación se encarga de definir los contratos, reglas y comportamientos del dominio, mientras que las capas externas o periféricas, se encargan de implementar esos contratos en función de las tecnologías concretas utilizadas.

Dicho de otra forma:

  • En el core se definen las interfaces (puertos) que representan cómo se espera que interactúen los elementos externos con el dominio (por ejemplo: repositorios, servicios de mensajería, interfaces de usuario).
  • Fuera del core, en carpetas como infraestructure, adapters o interfaces, se ubican las implementaciones concretas de esas interfaces (por ejemplo: repositorios que usan una base de datos específica, un controlador HTTP, etc).

Este enfoque permite cambiar cualquier implementación externa sin modificar el núcleo, ya que el núcleo solo conoce las interfaces, no las tecnologías que las implementan.

El Core contiene la lógica de negocio y define cómo deben hacerse las cosas. Las dependencias se encargan de cumplir con esos requerimientos siguiendo la planificación establecida por el Core

¿El Core, tiene puertos? 🤔

El Core de una aplicación en arquitectura hexagonal define los puertos, tanto de entrada como de salida, pero no contiene adaptadores. Los adaptadores se ubican fuera del Core y son los encargados de conectar el mundo exterior con la lógica de negocio.

Cuando una solicitud externa, como una llamada a una API o una acción del usuario, necesita ingresar al sistema, lo hace a través de un adaptador de entrada, el cual invoca un puerto de entrada definido en el Core. De igual manera, cuando el Core necesita interactuar con recursos externos, como guardar datos en una base de datos o enviar mensajes, lo hace mediante un puerto de salida, cuya implementación concreta está a cargo de un adaptador de salida.

Este diseño permite que el núcleo del sistema permanezca independiente, limpio y enfocado únicamente en las reglas del dominio, mientras que la infraestructura y los detalles técnicos se manejan de forma desacoplada en los adaptadores.

Esquema simplificado de la comunicación que tiene la Arquitectura Hexagonal – Loopy Data
ConceptoUbicaciónResponsabilidadEjemplos
Puerto EntradaDentro del CoreDefine una interfaz que expone las operaciones del dominio. Permite que agentes externos (adaptadores) interactúen con el Core de forma controlada.ICrearUsuario, IProcesarPedido, IConsultarSaldo
Puerto SalidaDentro del CoreDefine una interfaz que abstrae las dependencias que el Core necesita. El Core declara lo que necesita hacer, sin conocer cómo se implementa.IRepositorioUsuario, INotificadorEmail, IProveedorPagos
Adaptador EntradaFuera del CoreImplementa la lógica de recepción de eventos externos (API REST, eventos, CLI, etc). Traduce solicitudes externas en llamadas al puerto de entrada.Controladores HTTP, Listeners de eventos, Shell commands
Adaptador SalidaFuera del CoreImplementa los puertos de salida definidos por el Core. Se conecta con tecnologías específicas: Bases de datos, servicios externos, sistemas de archivos, etc.Repositorios JPA/SQL, servicios de email, gateways de pago

Relación funcional entre Puertos y Adaptadores 🔁

En la arquitectura hexagonal, los puertos representan interfaces que definen los contratos de comunicación con el mundo exterior. Esos contratos establecen qué necesita el Core y cómo debe interactuar con los elementos externos, sin imponer ninguna tecnología específica.

Por su parte, los adaptadores son las implementaciones concretas de esos puertos. Ellos se encargan de cumplir con las expectativas del Core, traduciendo los detalles técnicos del entorno (como HTTP, SQL, mensajes, etc.) a una forma que el Core pueda entender y utilizar.

Una característica clave de este modelo es que el Core nunca conoce a los adaptadores, pero sí conoce perfectamente las interfaces (puertos) que él mismo define o requiere. Es decir, el Core sabe lo que necesita, pero no le importa cómo se implemente. Esto permite mantener la lógica de negocio completamente desacoplada de los detalles técnicos, facilitando el testeo, la escalabilidad y la evolución del sistema.

🔌 Analogía: Puertos y Adaptadores como Enchufes y Tomas de Corriente

Imaginá que el Core de tu aplicación es como un dispositivo electrónico, por ejemplo, una notebook. Esta notebook tiene un puerto de entrada, que es el enchufe de alimentación, con un diseño específico (por ejemplo, clavija tipo C o tipo I).

Ahora bien, si querés conectarla en distintos países, te vas a encontrar con diferentes tomas de corriente. En Europa usan un tipo, en Estados Unidos otro, y en Argentina otro distinto. ¿Qué hacés entonces? Usás un adaptador de enchufe.

En esta analogía:

  • El puerto es la forma estándar en la que la notebook espera recibir energía.
  • El adaptador es el componente que convierte la toma de corriente del país (la tecnología externa) al formato que entiende la notebook.
  • La notebook (Core) no necesita saber cómo es la electricidad del país; solo sabe que requiere energía con cierta forma y voltaje, y espera que alguien lo resuelva mientras cumpla su contrato.

De igual manera, en arquitectura hexagonal:

  • El Core define lo que necesita (el contrato), por ejemplo: “necesito guardar un usuario”.
  • El puerto de salida expresa esa necesidad: guardar (usuario).
  • El adaptador de salida implementa ese método usando alguna tecnología concreta, como una base de datos SQL o un servicio externo.

El Core no tiene que preocuparse por si se guarda en PostgreSQL, Mongo o en un archivo de texto. Solo espera que se cumpla el contrato. Eso es lo que permite independencia, modularidad y reutilización.

¿Por qué se llama Arquitectura Hexagonal? 🤔

El nombre “Arquitectura Hexagonal” proviene de una metáfora visual propuesta por su creador, Alistair Cockburn, en el año 2005. No se trata de un término técnico en sí mismo, sino de una forma de representar gráficamente una idea fundamental: un sistema de software que puede interactuar con el exterior desde múltiples direcciones, sin darle prioridad a una forma de entrada o salida específica.

La figura del hexágono se eligió para simbolizar esta capacidad de conexión desde varios lados —como si cada lado del hexágono fuera una puerta de entrada o salida—, reforzando la noción de que la lógica central de la aplicación (Core) permanece aislada y flexible, sin depender de cómo se conectan los componentes externos.

Así, más allá de la forma geométrica, lo importante es el principio que transmite: un sistema desacoplado, fácilmente testeable y abierto a múltiples formas de integración.

Ejemplo de Hexágono, explicación Hexagonal – Loopy Data

Objetivo de la Arquitectura Hexagonal

La arquitectura hexagonal tiene como propósito principal aislar el núcleo de la aplicación (Core) de cualquier dependencia externa, como bases de datos, servicios web, interfaces gráficas, sistemas de archivos, entre otros.

Para lograr esta separación, se emplea una estructura basada en puertos e interfaces (que define el Core) y adaptadores (que se implementan fuera del Core). Esta organización permite que el Core se mantenga independiente, limpio y enfocado únicamente en la lógica del negocio, sin preocuparse por detalles técnicos o tecnologías específicas.

Puertos y Adaptadores

Aunque el término “arquitectura hexagonal” se volvió popular gracias a su valor visual, el nombre que realmente expresa el enfoque técnico del patrón es Ports and Adapters (Puertos y Adaptadores).

En este modelo:

  • Los puertos representan los puntos de conexión definidos por el núcleo (Core). Son interfaces que indican qué espera o qué ofrece la aplicación:
    • Los puertos de entrada exponen las operaciones que el sistema permite ejecutar desde el exterior.
    • Los puertos de salida declaran lo que el Core necesita hacer hacia fuera, como guardar datos o comunicarse con otro sistema.
  • Los adaptadores, por su parte, son las implementaciones concretas que se conectan a esos puertos. Traducen las solicitudes y respuestas entre el mundo externo y el Core. Pueden ser:
    • Adaptadores de entrada: como una API REST, una línea de comandos (CLI) o una interfaz gráfica.
    • Adaptadores de salida: como una base de datos, un sistema de colas, un servicio externo, etc.

Este patrón permite que la lógica de negocio permanezca independiente, facilitando cambios tecnológicos sin afectar el corazón de la aplicación.

Explicación de Ports and Adapters – Loopy Data

¿Por qué Hexagonal? 🤔

Una representación más justa del sistema

En los esquemas convencionales, la arquitectura suele representarse con capas apiladas: interfaz de usuario arriba, lógica de negocio al medio y base de datos al final. Este modelo sugiere una jerarquía que puede ser engañosa, ya que hace parecer que ciertos componentes son más importantes que otros.

En cambio, el hexágono ubica al Core en el centro y pone todos los puntos de entrada y salida en un mismo nivel, conectados desde los lados. Esto refuerza visualmente que:

  • El núcleo no depende del entorno.
  • Las tecnologías externas son detalles secundarios, intercambiables y de igual jerarquía.

Múltiples conexiones posibles

El hexágono simboliza que el sistema puede tener múltiples puntos de interacción —no solo uno de entrada y uno de salida—. Cada lado representa un puerto distinto, al cual se puede conectar un adaptador específico: una API, una base de datos, una consola, una cola de eventos, etc.

El dominio como el verdadero centro

Una de las premisas centrales de esta arquitectura es colocar la lógica de negocio (el dominio) como el núcleo del sistema, completamente desacoplada de cualquier preocupación técnica.

  • No importa si las solicitudes llegan por REST, consola o cualquier otro medio.
  • Tampoco importa si los datos se almacenan en MySQL, MongoDB o en un archivo local.

El Core define lo que necesita hacer, y los detalles técnicos se adaptan a ese requerimiento, no al revés.

Independencia tecnológica

La forma hexagonal representa también una neutralidad frente a la tecnología. El foco está en que el Core sea consistente, testeable y reusable, sin importar si el entorno cambia. Esto permite reemplazar tecnologías sin reescribir la lógica central de la aplicación.

Principios Clave de esta Arquitectura

La Arquitectura Hexagonal se basa en un principio fundamental: separar claramente la lógica de negocio (Core) de los detalles técnicos externos (bases de datos, frameworks, interfaces, redes, etc.). Esto permite construir sistemas limpios, desacoplados y preparados para cambios sostenibles a lo largo del tiempo.

Objetivo principal

Promover la separación de responsabilidades (separation of concerns) para que el núcleo del sistema sea independiente de tecnologías externas. Esto brinda beneficios claros:

  • Facilita el mantenimiento: los cambios en infraestructura no afectan la lógica de negocio.
  • Mejora la testabilidad: el Core puede probarse aisladamente con tests unitarios.
  • Favorece la escalabilidad y evolución: se pueden cambiar adaptadores sin modificar el dominio.
  • educe el acoplamiento: cada componente conoce solo lo necesario.

1. Core de la Aplicación (Dominio)

Es el corazón del sistema, donde reside la lógica pura sin dependencia alguna de frameworks o tecnología externa.

Contiene:

  • Entidades: objetos con identidad y comportamiento propios del dominio.
  • Servicios del dominio: reglas y lógica que no encajan en una sola entidad.
  • Casos de uso: coordinan la interacción para cumplir funcionalidades específicas.
  • Interfaces de puertos: definen lo que el Core necesita del exterior (puertos de salida) y cómo interactuar con él (puertos de entrada).

Regla de oro: El Core no conoce nada del exterior ni debe cambiar si cambian tecnologías o interfaces.

2. Puertos (Ports)

Son interfaces que actúan como puntos de conexión controlados entre el Core y el exterior.

  • Puertos de entrada (Inbound Ports):
    • Ubicación: Dentro del Core.
    • Responsabilidad: Exponen qué puede hacer el mundo exterior con el sistema.
    • Ejemplos: casos de uso como RegistrarUsuario, CrearOrden.
  • Puertos de salida (Outbound Ports):
    • Ubicación: También dentro del Core.
    • Responsabilidad: Representan lo que el Core necesita del mundo externo para funcionar.
    • Ejemplos: interfaces como RepositorioDeUsuario, ServicioDeCorreo.

Importante: El Core define las interfaces (puertos); la infraestructura implementa su comportamiento.

3. Adaptadores (Adapters)

Componentes externos que implementan las interfaces definidas por los puertos, traduciendo entre el mundo exterior y el lenguaje del dominio.

  • Adaptadores de entrada (Inbound Adapters):
    • Ubicación: Fuera del Core.
    • Responsabilidad: Reciben solicitudes del usuario o entorno y las traducen en llamadas al Core.
    • Ejemplos: controladores HTTP REST, CLI, listeners de eventos.
  • Adaptadores de salida (Outbound Adapters):
    • Ubicación: Fuera del Core.
    • Responsabilidad: Implementan la funcionalidad requerida por el Core, interactuando con sistemas externos.
    • Ejemplos: repositorios de base de datos, integración con APIs, envío de emails.

Conceptualmente, los adaptadores dependen del Core, no al revés.

4. Relaciones de dependencia

Todas las dependencias apuntan hacia el núcleo (Core). Las tecnologías externas se consideran detalles intercambiables, lo que permite:

  • Cambiar la base de datos sin modificar el Core.
  • Añadir nuevas interfaces (REST, CLI, eventos) sin alterar la lógica del dominio.
  • Testear el dominio sin depender de servidores, bases o servicios externos.

Conclusión

La Arquitectura Hexagonal no es solo un esquema visual atractivo, sino una estrategia sólida para diseñar software:

  • Enfocado en el valor del negocio.
  • Resistente al paso del tiempo y a los cambios tecnológicos.
  • Capaz de aislar detalles técnicos que cambian frecuentemente.
  • Facilita la prueba, mantenimiento y evolución constante del sistema.

Diseñar con hexágonos significa poner el dominio en el centro y mantener una separación clara entre lógica y tecnología.

¿Qué es el Vendor Lock-In? 🤔

Uno de los objetivos clave de la Arquitectura Hexagonal es evitar el vendor lock-in, también conocido como bloqueo del proveedor o dependencia tecnológica.

¿Qué significa Vendor Lock-In?

El vendor lock-in ocurre cuando una aplicación queda atrapada en tecnologías externas específicas — como frameworks, bases de datos, servicios en la nube o bibliotecas de terceros — de tal forma que reemplazarlas o migrarlas implica un alto costo en tiempo, esfuerzo o riesgos para el negocio.

En otras palabras, cuando tu negocio depende de una tecnología específica en lugar de que la tecnología sirva al negocio, estás sufriendo un bloqueo por parte del proveedor.

Ejemplos comunes de vendor lock-in

En aplicaciones tradicionales es frecuente que la lógica de negocio esté directamente acoplada a:

  • Frameworks: Django, Spring Boot, Express, Laravel, etc.
  • Bases de datos: MySQL, PostgreSQL, MongoDB, Firebase, entre otros.
  • Proveedores de nube: AWS, Google Cloud, Azure, Firebase, etc.
  • Librerías específicas: ORMs particulares, motores de colas, servicios de correo, etc.

Este acoplamiento fuerte implica que cambiar una tecnología afecta partes críticas del sistema, incluso la lógica de negocio, lo que dificulta o encarece la migración.

¿Cuándo el vendor lock-in es un problema?

El bloqueo del proveedor se vuelve un obstáculo serio cuando:

  • Se desea migrar de un proveedor cloud a otro (por ejemplo, de Firebase a AWS).
  • s necesario cambiar la base de datos (de MongoDB a PostgreSQL, por ejemplo).
  • Hay que reemplazar una librería obsoleta o insegura.
  • Se quiere adaptar la aplicación a otro entorno, como on-premise o serverless.
  • Se busca escalar el sistema de una manera que el proveedor actual no soporta eficientemente.

Si el núcleo del sistema está acoplado a estas tecnologías, dichos cambios resultan costosos y riesgosos.

¿Cómo ayuda la Arquitectura Hexagonal a evitar el vendor lock-in?

La Arquitectura Hexagonal aborda directamente este problema aislando las dependencias tecnológicas del núcleo del sistema.

¿Cómo?

  • La lógica de negocio vive en el Core, completamente libre de dependencias tecnológicas externas.
  • Toda interacción con tecnologías externas (bases de datos, servicios web, mensajería, almacenamiento, etc.) se realiza mediante puertos (interfaces definidas por el Core).
  • Las implementaciones concretas de estas tecnologías se colocan en los adaptadores, componentes externos al Core.

Así, el Core:

  • No sabe si se usa MySQL, MongoDB, Firebase o archivos planos.
  • No conoce si un correo se envía vía SMTP, SendGrid o AWS SES.
  • Solo conoce la existencia de un puerto, por ejemplo, ServicioDeCorreo.

¿Qué sucede si cambia la tecnología?

Si decidimos reemplazar una tecnología o proveedor (por razones de costo, rendimiento, seguridad o evolución), solo modificamos el adaptador correspondiente.

El Core permanece intacto, porque su contrato con el exterior (la interfaz o puerto) no cambia.

Resultado

  • Alta flexibilidad para cambiar tecnologías.
  • Mínimo impacto en la lógica de negocio.
  • Cero acoplamiento al proveedor o tecnología concreta.

Conclusión

El vendor lock-in es una amenaza real para la evolución sostenible del software.

La Arquitectura Hexagonal lo previene al:

  • Aislar las dependencias externas en adaptadores.
  • Definir contratos estables en el dominio mediante interfaces (puertos).
  • Mantener el Core libre de tecnologías externas.

Esto asegura que el negocio controle la tecnología, y no al revés.

Diseñar para el cambio es más importante que diseñar para la tecnología actual.

Estructuración en proyecto Hexagonal

Antes de definir cómo estructurar las carpetas, es importante tener en cuenta que esta estructura o arquitectura es agnóstica, lo que significa que no está ligada a una tecnología, lenguaje de programación o framework específico. En otras palabras, es un enfoque de organización de software que puede aplicarse en cualquier entorno, ya sea Node.js, .NET, Java, Python, PHP, entre otros.

Además, los nombres de las carpetas deben reflejar los conceptos teóricos del modelo arquitectónico, para dejar en claro que se está implementando este tipo de diseño de software.

En una arquitectura hexagonal, la carpeta domain representa el núcleo del negocio. Ahí se definen las entidades, que son los objetos con identidad y comportamiento propio como por ejemplo un User, un Product, o un Order. También se definen los value objects, que son objetos sin identidad propia como Email, Address, o Money, que ayudan a encapsular lógica específica de un valor. Dentro del dominio también es común tener interfaces o contratos, que funcionan como los puertos de salida del sistema, es decir, definiciones abstractas de las dependencias que el dominio necesita pero sin acoplarse a implementaciones concretas, como puede ser un repositorio o un servicio de email.

La carpeta application contiene los casos de uso, que son los flujos de negocio concretos que la aplicación debe resolver, como por ejemplo CreateUser, SendInvoice, o PlaceOrder. Estos casos de uso utilizan las entidades y objetos del dominio, y dependen únicamente de las interfaces declaradas en él. En esta capa también se suelen declarar los DTOs (Data Transfer Objects), que son estructuras que facilitan el transporte de datos entre capas, asegurando que la entrada y salida de datos esté bien definida. A veces también se definen interfaces adicionales para abstraer cómo se recibe la solicitud, por ejemplo si viene por HTTP, por una cola de eventos o por consola.

La carpeta infrastructure contiene las implementaciones concretas de las dependencias que el dominio o la aplicación necesitan, por ejemplo, la conexión a base de datos, los adaptadores para servicios externos como proveedores de email, servicios de pago o almacenamiento de archivos. Dentro de esta capa puede haber una subcarpeta repositories, donde se implementan las interfaces del dominio encargadas de recuperar y almacenar entidades. También puede haber una carpeta de configuración (config), donde se definen cosas como la conexión a la base de datos, el motor de correos, las claves API de servicios externos y demás configuraciones globales.

Por su parte, la carpeta adapters representa los puertos de entrada del sistema, es decir, los puntos por donde el mundo exterior interactúa con tu aplicación. Si el proyecto es una API, aquí se colocan los controladores HTTP y las rutas. Estos controladores reciben las solicitudes, transforman los datos entrantes en DTOs, invocan los casos de uso de la capa de aplicación y luego transforman la salida en una respuesta adecuada para el cliente. También puede haber mappers o transformers que se encargan de convertir entidades del dominio a objetos de salida, o viceversa.

Finalmente, la carpeta tests agrupa todas las pruebas. Las pruebas unitarias suelen estar dirigidas al dominio y los casos de uso, para validar que la lógica funciona en aislamiento. Las pruebas de integración prueban la interacción entre múltiples capas, incluyendo persistencia o servicios externos. Este enfoque garantiza una alta mantenibilidad, independencia de tecnología, y permite testear la lógica más crítica del negocio sin necesidad de levantar servidores o conectar bases de datos.

🧱 Estructura de carpetas en .NET

/MySolution

├── /Core
│ ├── /Domain
│ │ ├── Entities
│ │ ├── ValueObjects
│ │ └── Interfaces (Outbound Ports, ej. IRepository, IEmailService)
│ │
│ └── /Application
│ ├── UseCases (Servicios de aplicación)
│ ├── DTOs
│ └── Interfaces (Inbound Ports, ej. IUserService)

├── /Infrastructure
│ ├── /Persistence
│ │ ├── DbContext
│ │ ├── Repositories (implementa IRepository)
│ │ └── Migrations
│ │
│ ├── /ExternalServices
│ │ ├── EmailService
│ │ └── APIs externas
│ │
│ └── /Configuration
│ └── DI, Options, Logging, etc.

├── /Adapters
│ ├── /WebApi (Controllers)
│ │ ├── Controllers
│ │ └── Mappings (DTO ↔ Domain)
│ │
│ ├── /CLI (opcional)
│ └── /MessageConsumers (RabbitMQ, Kafka, etc.)

└── /Tests
├── /UnitTests
└── /IntegrationTests

🧱 Estructura de carpetas en Node.js

/src
├── core
│ ├── domain
│ │ ├── entities
│ │ ├── value-objects
│ │ └── interfaces # Outbound Ports (IUserRepository, IEmailService)
│ └── application
│ ├── use-cases # Lógica de negocio (casos de uso)
│ ├── dtos
│ └── interfaces # Inbound Ports (IUserService)

├── infrastructure
│ ├── persistence
│ │ ├── repositories # Implementaciones de interfaces
│ │ └── models # ODM/ORM (Mongoose, Sequelize, etc.)
│ ├── external-services
│ │ └── email # Implementación concreta
│ └── config # Variables de entorno, logger, DI

├── adapters
│ ├── controllers # Express/Fastify/Routers
│ └── mappings # DTO ↔ Domain

└── tests
├── unit
└── integration

🧱 Estructura de carpetas en Laravel

/app
├── Core
│ ├── Domain
│ │ ├── Entities
│ │ ├── ValueObjects
│ │ └── Contracts # Outbound Ports (interfaces)
│ └── Application
│ ├── UseCases # Casos de uso
│ ├── DTOs
│ └── Contracts # Inbound Ports

├── Infrastructure
│ ├── Persistence
│ │ ├── Eloquent # Modelos Eloquent
│ │ └── Repositories # Implementaciones de los Contracts
│ ├── Services
│ │ └── EmailService
│ └── Config

├── Adapters
│ ├── Http
│ │ └── Controllers
│ └── Console

└── Tests
├── Unit
└── Feature

Loopy Data

Scroll al inicio