Hexagonal Architecture, also known as Ports and Adapters, is a software architecture pattern that aims to isolate the business logic from any interaction with the external world, creating a highly decoupled and maintainable application. This pattern was proposed by Alistair Cockburn and promotes the idea that the business logic of an application should be independent of any external details such as the database, user interface, or communication between systems.

The key to this architecture lies in its focus on "ports" and "adapters." "Ports" are interfaces in the core of the application that define the behaviors expected by the business logic. "Adapters," on the other hand, are implementations of these ports that interact with the external world.

Hexagonal Architecture is named as such because of how it is conceptually visualized. In its diagram, the core of the business logic is represented at the center of a hexagon. The different sides or "faces" of the hexagon represent the "ports" through which the business logic interacts with the external world. Therefore, no matter which direction you look at (or from which side of the hexagon you approach), you are always interacting with a port that connects to the central business logic.

Hexagonal architecture diagram

Some advantages of using Hexagonal Architecture are:

  • Decoupling: The business logic is completely isolated from external concerns, meaning that changes to external interfaces (such as a new database or UI technology) should not affect the business logic itself.
  • Testability: Thanks to decoupling, it is easier to perform unit testing on the business logic. You can use test adapters instead of the real ones to test the behavior of your system under different conditions.
  • Maintainability and evolution capability: The code is easier to maintain and evolve over time. Since the business logic is not mixed with the code that interacts with the external world, developers can focus on improving the business logic without worrying about technical details.

However, this architecture does have some drawbacks to consider before opting for it:

  • Added complexity: This type of architecture may be excessive for smaller or less complex applications. There can be a considerable amount of code dedicated to organizing the architecture, which may seem unnecessary if the application is simple enough.
  • Learning curve: Learning to work with Hexagonal Architecture can take some time. Developers need to understand how ports and adapters are organized and how they interact with each other.
  • Integration challenges: Although the architecture decouples the business logic from the rest of the system, integration with external systems can still be a challenge, especially if those systems do not follow the same design principles.

 Hexagonal architecture pros and cons

Hexagonal Architecture Structure

Hexagonal Architecture is based on the idea of clearly separating the internal and external functions of an application, allowing the business logic to be independent of devices or external services.

The internal structure of an application that follows this pattern can be divided into three main parts:

  • Application Domain or Core: This is the core of the hexagonal architecture and where the business logic resides. The application domain defines the essential rules and behaviors of the system and is completely isolated from external influences.
  • Ports: Ports are interfaces or contracts defined by the application domain. Primary ports or "driven ports" define the functionalities offered by the application domain, while secondary ports or "driving ports" define the functionalities expected by the application domain from the external world.
  • Adapters: Adapters are the implementation of ports that interact with components external to the application domain. They can be input adapters (implementing primary ports) such as UI controllers or API request services, or output adapters (implementing secondary ports) such as database access, messaging services, etc.

Thanks to this structure, the business logic (the application domain) can be developed and tested independently of the infrastructure that will be used. Ports and adapters allow any external technology (databases, libraries, frameworks, etc.) to be swapped with minimal changes to the application domain. This makes the architecture flexible and adaptable to different technologies and changes in system requirements.

It is also worth noting that this architecture focuses on code organization and interactions between different parts of a system and does not prescribe how processes should be handled or how code should be distributed.

The ports and adapters structure in hexagonal architecture allows for a high level of decoupling and modularity in the code, but it does not imply that the entire system must be implemented in a single codebase or run in a single process.

It is possible to have a hexagonal architecture where different parts of the system are implemented in different codebases and run in different processes or even different servers. For example, you could have a system with a backend service implementing the business logic and ports, and multiple adapters implemented as independent microservices.

On the other hand, it is also possible to have a hexagonal architecture implemented in a single codebase and run in a single process. This might be suitable for a smaller application or if the development team prefers to keep all related code in one place for ease of maintenance and code understanding.

In summary, hexagonal architecture is about how you structure and organize your code to keep the business logic separate from input/output details and infrastructure, and it does not have strict rules on how codebases or processes should be managed.

 

Hexagonal architecture structure

Application Domain or Core

The Application Domain, also known as the Core, is the heart of the hexagonal architecture. This is where the central business logic of the application resides. The application domain contains all the essential rules and behaviors of the system and is completely independent of any external layers.

The application domain includes all the entities, domain services, events, and exceptions that define the business or purpose of the application. Entities are the core business objects that represent key concepts of the system and contain associated business logic.

In a hexagonal architecture, the application domain should not have any direct dependencies on the infrastructure, such as database details, user interfaces, messaging services, etc. Instead, the application domain communicates with the external world through interfaces or contracts known as ports.

This independence of the application domain from the infrastructure facilitates unit and integration testing, as the business logic can be tested in isolation. It also makes the application code easier to understand and maintain, as changes in infrastructure details do not affect the business logic. Additionally, this separation of the application domain from technical details facilitates adaptability and evolution of the system.

Hexagonal architecture application domain

Ports

Ports are a fundamental piece of the hexagonal architecture and act as entry and exit points for interaction between the application domain (or Core) and external actors (e.g., users, external systems, or infrastructure within the application such as databases or messaging services). Ports are defined through interfaces in the code, establishing a contract on how the application domain can interact with the external world.

There are two types of ports in a hexagonal architecture:

  • Primary Ports or Driven Ports: Also known as input ports, they define the operations that the application can perform in response to an external request. These operations typically correspond to high-level actions related to the business capabilities of the application. For example, in an order management system, a primary port could define operations such as "create order," "update order," or "delete order."
  • Secondary Ports or Driving Ports: Also known as output ports, they define the operations that the application can request from external elements. A secondary port could, for example, define data persistence operations like "save order" or "retrieve order."

This separation of responsibilities and decoupling between the application domain and external elements allows for changes in the infrastructure (such as migrating to a new database) with minimal impact on the application domain code. At the same time, it facilitates testing, as secondary ports can be easily replaced with test implementations (mocks) during unit testing.

 

Ports in hexagonal architecture

Primary Ports or Driven Ports

Primary ports, also known as driven ports or input ports, represent the abstraction layer between the application domain and the external world. These ports define the interfaces through which external actors can interact with the application domain.

In a hexagonal architecture, primary ports are responsible for defining the high-level operations that the application can perform. The operations defined by primary ports focus on the business capabilities of the application and define the high-level business logic.

For example, in an order management system, primary ports could define operations such as "create order," "update order," or "delete order." These operations are the ones that external actors, such as users or external systems, can invoke.

Primary ports are essentially contracts that define how interaction with the application domain can occur. These contracts establish the rules for interaction between the application domain and the external world, ensuring that the business logic remains isolated and protected from changes in the external layers of the architecture.

In software development, primary ports are often implemented as interfaces in the code. Interfaces provide an abstract description of the operations that the application can perform, without going into details of how these operations are implemented. The concrete implementation of these operations is provided in the application adapters, which interact with the application domain through the primary ports.

Secondary Ports or Driving Ports

Secondary ports, also known as driving ports or output ports, represent the interfaces through which the application domain interacts with the external world. These ports abstract the technical details of the external infrastructure, allowing the application domain to communicate with external systems in a way that is decoupled from specific implementation details.

For example, suppose the application domain needs to interact with a database. Instead of directly interacting with the database, which would couple the application domain to a specific type of database, the domain interacts with a secondary port that defines high-level operations that can be performed on the database, such as "save order" or "retrieve order." This allows the business logic of the application domain to be isolated from the technical details of how the database is accessed and manipulated.

At the implementation level, secondary ports are often realized as interfaces in the code. These interfaces define the operations that the application domain can perform on the external infrastructure but do not provide any concrete implementation of these operations. Instead, the implementation is provided through the application adapters, which implement the interfaces defined by the secondary ports.

Secondary ports play a crucial role in promoting dependency inversion in hexagonal architecture. By interacting with the external infrastructure through the secondary ports, the application domain can remain decoupled from the implementation details of the infrastructure. This facilitates adaptation to changes in the infrastructure without affecting the business logic of the domain.

Adapters

Adapters are fundamental components in the hexagonal architecture, acting as the bridge between the ports and the external infrastructure, i.e., technologies and systems external to the application domain.

  • Adapters for Primary Ports: These adapters receive requests from the external infrastructure and translate them into a format that the application domain can understand and handle. For example, an input adapter could be a controller in an MVC framework that receives HTTP requests, converts them into calls to use case methods within the application domain.
  • Adapters for Secondary Ports: These adapters interact with the external infrastructure on behalf of the application domain. They implement the interfaces defined by the secondary ports and handle the technical details of how to interact with specific systems, such as databases, web services, messaging, etc. For example, an output adapter could be a repository that implements the operations defined in a secondary port, translating them into specific SQL operations to access a database.

The use of adapters allows for a high degree of decoupling and modularity. The details of the external infrastructure are confined within the adapters, keeping the application domain logic clean and focused on the business logic. This also facilitates testing, as adapters can be easily replaced with test doubles in a testing environment. Additionally, if any external technology needs to be replaced, only the corresponding adapter needs to be updated or replaced, minimizing the impact on the application domain.

 

Adapters in hexagonal architecture

Adapters for Primary Ports or Input

Adapters for primary ports, also known as input adapters, enable the interaction between the user or any external agent and the application. These adapters receive requests from the outside and translate them into a format understandable by the application domain.

For example, let's assume we have a web service exposing a REST API. When a client makes an HTTP request to our service, that request will first reach one of our adapters for primary ports. This adapter, often a controller in an MVC framework, is responsible for taking that HTTP request and transforming it into a call to the appropriate use case within the application domain.

This may involve extracting the necessary parameters from the request body, validating these parameters, and finally invoking the corresponding use case with these parameters.

One advantage of this approach is that it completely isolates the application domain from the specific details of the input infrastructure, allowing for changes in how the application is accessed without affecting the domain logic. For example, we could decide to switch from a REST API to a GraphQL API without changing anything in our business logic; we would simply need to create new adapters to handle GraphQL requests.

Adapters for Secondary Ports

Adapters for secondary ports, also known as output adapters, act as intermediaries between the application domain and any external system or service that the application depends on. These adapters are responsible for translating the interactions of the application domain with these external systems.

A common example of an adapter for secondary ports is a data repository. Let's suppose that the application domain needs to persist or retrieve data from a database. Instead of directly interacting with the database, which would couple the application domain to a specific database type, the domain interacts with a repository interface that is part of the secondary ports. The concrete implementation of this interface is handled by the corresponding secondary port adapter.

For example, we could have a "UserRepository" that defines operations like "saveUser" and "findUser." The logic of the application domain uses these operations without knowing how they are implemented. And we have an "UserRepositoryAdapter" that implements these operations for a specific MySQL database.

This way, if we decide to switch from a MySQL database to a NoSQL database at some point, we would only need to implement a new adapter that complies with the interface of the "UserRepository." The application domain logic does not need to be changed, as it continues to interact with the same repository interface.

In this way, adapters for secondary ports allow keeping the application domain logic isolated from the specifics of the used output infrastructure technologies, facilitating software maintenance and evolution.

Request Handling

In hexagonal architecture, user or external system requests are handled through the interaction of several key components: adapters, ports, and the application domain.

  • Request Input: User or system requests are initially received by the input adapters. These adapters, also known as primary adapters, can be user interfaces, APIs, message consumers, etc. These adapters are responsible for translating the received requests into a format that can be understood by the application domain.
  • Request Processing: Once the request is translated by the input adapter, it is passed to the application domain through a primary port. The application domain, also known as the system "core," contains the business logic that handles the request. The business logic may involve operations such as validating request data, performing calculations, or interacting with secondary ports to persist or retrieve data.
  • Response to Requests: After the domain logic has processed the request, the response is passed back to the input adapter through the primary port. The input adapter then translates the response from the application domain into a format that the user or external system can understand and sends it back.

In this way, hexagonal architecture handles requests and responses in a manner that keeps the domain logic isolated from the specific details of input and output systems. This allows for greater flexibility, as input and output adapters can be swapped without affecting the domain logic.

 

Request workflow in hexagonal architecture
Request Input

In hexagonal architecture, user or external system requests enter the system through primary or input adapters. These adapters are responsible for interacting with the external world, which can be a user interface, an API request, a message system, among others.

The input adapter's main task is to translate the requests received from the external world into a format that the application domain layer can understand and process. In other words, the input adapter converts external requests into commands or queries for the application domain.

For example, if a user makes a request through a web user interface, the input adapter could be a controller in an MVC (Model-View-Controller) pattern. The controller receives the HTTP request, extracts the necessary information from the request (such as URL parameters or form data), and translates it into commands or queries that are passed to the application domain through a primary port.

This translation process allows for effective isolation between the business logic (in the application domain) and the specific input and output systems. This means that changes in user interfaces or API technologies should not affect the business logic, thus facilitating maintenance and improving the testability of the business logic.

Request Processing

Request processing in a hexagonal architecture takes place in the application domain, which is also the core of the architecture. In this architectural model, the application domain is completely isolated and has no direct dependencies on any external layer. Requests reach it through the primary ports, and it is here that all the business logic is performed.

In the processing stage, the business logic is responsible for carrying out the appropriate actions for each request. These actions can range from simple operations, such as retrieving data, to complex processes involving multiple steps.

The business logic may interact with domain entities and domain services to carry out its tasks. A domain entity can represent a business concept or object, such as a product in an e-commerce system, while domain services encapsulate behaviors that don't naturally belong to an entity.

When processing the request involves the need to interact with external resources, such as a database or a web service, a secondary port is used to encapsulate this communication. An appropriate adapter is responsible for implementing the actual interaction, allowing the business logic to remain isolated from the specificities of external systems.

Once the request has been processed and a response has been generated, this response can also be sent to an output adapter through a secondary port.

Response to Requests

After the business logic has processed the request in the application domain, the response is generated and sent back through the ports. This output process is also controlled and managed by the corresponding adapters, which are responsible for transforming the domain data into a format that the external client or system expects or needs.

Responses can vary depending on the nature of the request and system requirements. They could be as simple as confirmations that an action has been successfully completed or they could be more complex sets of data if the request involved retrieving information.

It's important to note that the business logic doesn't concern itself with how the response will be handled or presented once it leaves the application domain. This detail is handled by the corresponding output adapter. For example, if the request came through a web user interface, the output adapter would transform the domain data into HTML or JSON. If the request was from a message queue system, the output adapter could transform and send the data as a message in the queue.

The use of adapters to handle the presentation and delivery of responses allows the application domain to remain decoupled and agnostic to external technologies and systems, promoting maintainability and adaptability of the system as a whole.

Scalability

Scalability in a hexagonal architecture is an inherent feature due to its decoupled and modular nature. Although scalability considerations may vary depending on the implementation and specific business needs, there are some general ways in which hexagonal architecture can scale:

  • Vertical Scalability: The decoupling between the application domain and the adapters allows for improving the capabilities of a single system by adding more resources such as CPU or memory as needed. This can be done without changing the application domain logic, and only requires the adapters to be able to leverage the additional resources.
  • Horizontal Scalability: The inherent modularity and decoupling in hexagonal architecture also facilitate horizontal scalability, i.e., adding more instances of the system to handle larger workloads. For example, you could have multiple instances of the application domain, each with its own set of adapters, running in parallel and load-balancing between them. The adapters should be designed to enable this kind of scalability, such as being able to share sessions or handle concurrency properly.
  • Functional Scalability: Another way to scale in a hexagonal architecture is through functional scalability, where new features or functionalities can be added as new adapters without affecting the core business logic. This allows a system to grow and evolve over time without requiring major rewrites or restructuring of existing code.

Hexagonal architecture improves the resilience of the system by allowing failures in an adapter or external system to not affect the core application domain. This means that the system can continue to function, or at least fail in a more controlled manner, in case a part of the system fails.

Scalability in hexagonal architecture

In summary, scalability in a hexagonal architecture is achieved through careful design that focuses on decoupling and modularity, allowing the system to grow and adapt to new conditions and requirements without requiring major structural changes. However, it's important to note that the details of how scalability is handled are implementation-dependent and can vary widely between different systems.

Development and lifecycle

The development process in a hexagonal architecture begins with the definition of the application domain or the core. This is where the business logic of the application is modeled, including the rules and behaviors that define it.

Once the core is defined, the different ports are identified. Primary ports are the interfaces that represent the operations that the application domain can perform, while secondary ports are the interfaces through which the application domain interacts with external systems.

After defining the ports, the adapters are implemented. Adapters are the components that interact with the external world, and each of them is associated with a specific port. Adapters for primary ports interact with users or external systems that invoke operations in the application domain, while adapters for secondary ports interact with external systems that the application domain needs to access.

During development, it is important to maintain the separation between the application domain and the adapters. This allows for changing or replacing adapters without affecting the business logic.

The lifecycle of a hexagonal architecture follows an iterative and incremental process. As business needs change, it is likely that the business logic in the application domain needs to be updated. This may involve adding new operations, modifying existing operations, or removing operations that are no longer needed.

Additionally, it may be necessary to add, modify, or remove adapters to reflect changes in the external systems that the application domain interacts with. This might be necessary, for example, if a decision is made to switch to a new database, if a new user channel is added (such as a mobile application), or if there is a decision to integrate with a new external service.

In each iteration, it is important to ensure that the separation between the application domain and the adapters is maintained, and that proper testing is in place to ensure the correct operation of the system.

Hexagonal architecture lifecycle

 

Use cases

The most common use cases for Hexagonal Architecture are:

  • Development of environment-independent systems: It is especially useful in contexts where the business logic needs to be independent of the execution environment and external systems. For example, it could be used in an enterprise application that needs to interact with different databases, web services, and users, but wants to keep its business logic agnostic to the details of these interactions.
  • Systems with rigorous testing requirements: Since hexagonal architecture facilitates the isolation of business logic from external systems, it is a good choice for systems that need to undergo rigorous testing. Adapters can be easily replaced by test adapters that simulate the behavior of external systems, allowing the business logic to be tested in a controlled environment without depending on the availability or state of these systems.
  • Systems with frequent changes in external systems: In systems that need to adapt to frequent changes in the external systems they interact with, this architecture can be beneficial. Since adapters are the only components that need to be changed when an external system changes, these changes can be made in a controlled manner with minimal impact on the business logic.
  • Applications with refactoring or rewriting needs: Hexagonal architecture is also a suitable approach for refactoring or rewriting existing applications that have a monolithic architecture or a traditional layered architecture. Decomposing the application into an application domain and adapters can help clarify the business logic and facilitate the transition to a more modular and flexible architecture.
  • Applications with multiple interaction channels: It is ideal for applications that need to interact with different input and output channels. For example, an application that needs to support web user interfaces, mobile applications, command-line interfaces, and APIs for other services. In this case, each of these channels would be an adapter in the hexagonal architecture, allowing the core application logic to remain unchanged while adding, modifying, or removing interaction channels.
  • Distributed systems: Systems composed of multiple distributed services or components can also benefit from this architecture. In this case, each service or component could be modeled as an adapter that communicates with the central business logic through a port. This would facilitate the implementation of distributed system design patterns, such as message routing, distributed transaction management, and fault tolerance.