Software Development

Hexagonal Architecture (Ports and Adapters): Achieving True Domain Independence

In 2005, Alistair Cockburn proposed the hexagonal architecture pattern, also known as the ports and adapters pattern, with the aim of creating loosely coupled architectures where application components can be tested independently. Nearly two decades later, this architectural pattern has evolved from a theoretical concept into a fundamental approach for building maintainable, testable, and future-proof software systems. The pattern emerged from a simple yet profound observation: the traditional layered architecture treats user interfaces and databases asymmetrically, when in reality, both are external actors that should interact with the application in fundamentally similar ways.

The problem Cockburn sought to solve was pervasive across software development. Teams built applications where business logic became entangled with infrastructure concerns—database access code mixed with domain rules, UI logic bleeding into service layers, and external API integrations scattered throughout the codebase. This contamination made applications brittle, difficult to test, and resistant to change. The hexagonal architecture was invented in an attempt to avoid known structural pitfalls in object-oriented software design, such as undesired dependencies between layers and contamination of user interface code with business logic.

hexagonal architecture

The Core Philosophy: Inside-Outside Asymmetry

At the heart of hexagonal architecture lies a fundamental shift in perspective. The primary purpose of this pattern is to focus on the inside-outside asymmetry, pretending briefly that all external items are identical from the perspective of the application. Rather than organizing code around technical layers—presentation, business logic, data access—hexagonal architecture organizes around the distinction between what is core to the application and what is peripheral.

The business logic sits at the center, protected and isolated from the external world. Everything else—databases, user interfaces, message queues, external APIs, even test frameworks—exists outside this core and interacts with it through well-defined interfaces. This inversion of traditional thinking creates profound benefits for software design, testability, and long-term maintainability.

Key Architectural Concepts:

ConceptDefinitionPurpose
The Hexagon (Application Core)Contains business logic, domain models, and use casesRemains technology-agnostic and independent of external concerns
PortsTechnology-agnostic interfaces that define how external actors communicate with the coreEnable multiple implementations without changing business logic
AdaptersConcrete implementations of ports that handle specific technologiesTranslate between external systems and the application core
Primary (Driving) ActorsExternal entities that initiate interactions with the applicationUser interfaces, test scripts, API consumers
Secondary (Driven) ActorsExternal entities that the application depends on to fulfill its goalsDatabases, external services, message brokers

The term hexagonal architecture comes from the visual effect created by drawing the application component as a hexagonal cell, with the hexagon not being important because the number six is significant, but rather to allow people doing the drawing to have room to insert ports and adapters as they need. The shape itself carries no special meaning beyond providing visual space for representing multiple connection points.

Ports: Defining Clear Boundaries

Ports represent technology-agnostic entry and exit points for the application. Ports are technology-agnostic entry points into an application component, custom interfaces that determine the interface allowing external actors to communicate with the application component, regardless of who or what implements the interface. This concept draws a useful analogy: just as a USB port allows many different devices to communicate with a computer using different adapters, application ports allow various external systems to interact with business logic through different technological implementations.

Ports come in two fundamental types, each serving a distinct purpose in the architecture. Primary ports, also called driving ports, represent the application’s use cases and services. These are the interfaces through which external actors trigger application behavior. When a user submits a form through a web interface or an automated test invokes a service, they do so through primary ports. The application implements these ports, exposing its capabilities to the outside world.

Secondary ports, or driven ports, represent the capabilities the application needs from the external world to fulfill its objectives. When business logic needs to persist data, send notifications, or integrate with external services, it does so through secondary ports. Critically, the application defines these interfaces based on what it needs, not on what the infrastructure provides. This inverts the traditional dependency direction—rather than business logic depending on database implementations, the database adapter must conform to interfaces defined by business needs.

Port Types and Their Characteristics:

Port TypeFlow DirectionImplementationExample Use Cases
Primary (Driving)External → ApplicationImplemented by application coreREST APIs, GraphQL endpoints, CLI commands, test interfaces
Secondary (Driven)Application → ExternalImplemented by adaptersRepository interfaces, notification services, payment gateways, external API clients

This distinction creates what Cockburn recognized as both symmetry and asymmetry coexisting: all adapters depend on the hexagon making the application technology agnostic on both sides, but the configurable dependency implementation differs for each side.

Adapters: Bridging Worlds

If ports define what the application needs and provides, adapters implement those definitions using specific technologies. Adapters interact with the application through a port by using a specific technology, plugging into these ports, receiving data from or providing data to the ports, and transforming the data for further processing. Adapters serve as translators, converting between the language of the domain and the language of infrastructure.

Primary adapters initiate interactions with the application. A REST API adapter receives HTTP requests, translates them into domain objects or commands, invokes the appropriate port methods, and transforms the results back into HTTP responses. A command-line interface adapter parses arguments, calls port methods, and formats output for console display. Test adapters invoke ports with specific inputs and assert expected outcomes. Each adapter understands both the technology it represents and the port interface it uses, but the application core remains oblivious to these details.

Secondary adapters respond to the application’s needs. When business logic calls a repository port to save an entity, a database adapter receives that call, translates the domain object into database records, executes the appropriate SQL or NoSQL operations, and returns results in the format the port specifies. Similarly, a message queue adapter implements a notification port by translating domain events into messages and publishing them to the appropriate queue, while an email adapter implements the same port by rendering emails and sending them via SMTP.

The power of this separation manifests most clearly when requirements change. If a company decides to switch from a relational database to a NoSQL database, only the adapter responsible for database interaction needs to be modified, with the core logic remaining unaffected. The port interface stays constant, the business logic continues working unchanged, and only the adapter implementation needs updating.

Domain Independence: The Ultimate Goal

The driving motivation behind hexagonal architecture is achieving true domain independence—business logic that stands alone, testable in isolation, and immune to infrastructure churn. The domain should not hold any references to frameworks, technologies, or real-world devices and should contain all of the business logic of the application. This independence creates multiple cascading benefits that compound over the software lifecycle.

Testing becomes dramatically simpler and more reliable. With business logic isolated from infrastructure, unit tests can verify domain behavior using simple in-memory implementations of ports rather than complex mocks or test databases. Since external dependencies like databases or APIs are abstracted away, the core logic can be easily unit-tested without the need for complex mocks or stubs, allowing you to mock interfaces for external systems and enabling the core to be tested in isolation. Tests run faster, require less setup, and focus purely on business rules rather than integration concerns.

Technology evolution becomes manageable rather than traumatic. Modern software systems must adapt continuously to changing technology landscapes—new databases, evolving cloud services, updated framework versions, emerging integration patterns. When business logic depends directly on these technologies, each change ripples through the entire codebase. Hexagonal architecture contains this disruption at the adapter level, protecting the core from external volatility.

Development velocity improves through parallel workstreams. Different teams can work independently on various parts of the system, with one team focusing on the core business logic while another works on adapters for specific technologies. Frontend teams can develop UI adapters against port specifications while backend teams implement business logic and infrastructure teams build database adapters. Mock implementations enable each team to proceed independently until integration points mature.

Real-World Applications: Where Hexagonal Architecture Shines

The pattern’s practical value emerges most clearly in specific contexts where its benefits outweigh its costs. Notable examples of successful implementations include Shopify decoupling its core business logic from various payment gateways and shipping providers through adapters, enabling merchants to switch between different payment options without affecting core functionalities. Similarly, PayPal uses hexagonal architecture to separate its core transaction processing logic from various external services it interacts with, such as different banking APIs, fraud detection systems, and user interfaces, allowing PayPal to adapt to changes in regulations or integrations seamlessly.

Ideal Application Scenarios:

  • Complex business domains: Systems with sophisticated business rules that change more frequently than infrastructure
  • Multiple client interfaces: Applications accessed through web, mobile, API, CLI, and other channels
  • Long-lived systems: Projects expected to evolve over many years with changing technology landscapes
  • High testability requirements: Applications where comprehensive automated testing provides critical value
  • Microservices architectures: Services that must remain independent while integrating with various external systems
  • Regulatory compliance: Systems where business logic must be auditable and verifiable in isolation from infrastructure

The pattern proves particularly powerful when combined with Domain-Driven Design. A third dimension to hexagonal microservices can be Domain-Driven Design, where we design domains, subdomains, and bounded contexts, then break them down into one or more microservices leveraging hexagonal principles to isolate the domains from external dependencies. The strategic patterns of DDD identify bounded contexts and domain models, while hexagonal architecture provides the tactical pattern for structuring code within those contexts.

Financial systems provide an illuminating example. Core logic encompasses functionalities like transaction processing, risk assessment, and portfolio management remaining independent of external services, with ports defining contracts like a PaymentGateway port with methods to process payments and retrieve transaction status, and adapters like OnlinePaymentAdapter implementing these ports by translating payment requests into calls to external payment providers. This separation enables thorough testing of financial logic without involving actual payment providers, regulatory compliance audits focused purely on business rules, and adaptation to new payment providers or banking regulations by creating new adapters.

The Trade-offs: Complexity and When to Avoid

Despite its benefits, hexagonal architecture introduces genuine costs that teams must weigh carefully. The architecture introduces additional layers like ports, adapters, and application services which can make the initial design more complex, with developers needing to write more boilerplate code to create interfaces and adapters. For small applications with minimal business logic, this overhead may not justify the benefits.

Key Challenges:

ChallengeImpactMitigation Strategy
Initial ComplexityMore files, interfaces, and indirection layersStart simple, add structure as complexity grows
Learning CurveTeam members unfamiliar with pattern need trainingProvide examples, pair programming, gradual adoption
Boilerplate CodeMore code to write and maintainUse code generation, establish clear conventions
Mapping OverheadConverting between domain and external modelsKeep mappings simple, centralize transformation logic
Performance ImpactAdditional layers may introduce latencyMeasure actual impact, optimize critical paths
Over-engineering RiskApplying pattern where simpler solutions sufficeAssess project size and change frequency realistically

The fact that hexagonal architecture can be quite complex means that when not applied accurately, it can turn out to be resource and cost-intensive, taking a lot of time to implement in case of such complexities. Teams must honestly assess whether their project warrants this investment. A simple CRUD application with stable requirements and a single access channel probably doesn’t need hexagonal architecture. The pattern makes sense when change is expected, when multiple access channels exist, or when business logic complexity justifies the protection it provides.

For a microservice with minimal business logic, the extra effort for ports, adapters, and mapping is not worth it, with no fixed size limit meaning a decision must be made based on experience. Generally, when using domain-driven design and a microservice comprises an aggregate with several entities and associated services, hexagonal architecture proves sensible.

Implementation Strategies: Getting Started Right

Successfully implementing hexagonal architecture requires thoughtful planning and disciplined execution. Key strategies include defining core business logic first by establishing the central domain model which should remain independent of external systems, ensuring business rules are at the heart of the system and not affected by infrastructure changes.

Implementation Roadmap:

  1. Identify Core Business Logic: Map out domain models, business rules, and use cases that represent the essence of what the application does
  2. Define Port Boundaries: Determine what the application needs from the outside world (secondary ports) and what services it provides (primary ports)
  3. Start with In-Memory Adapters: Build simple in-memory implementations of ports to enable testing and development without infrastructure dependencies
  4. Implement Real Adapters Gradually: Add production adapters for databases, APIs, and other infrastructure as needed
  5. Establish Testing Strategy: Write unit tests against ports, integration tests for adapters, and end-to-end tests through primary adapters
  6. Create Clear Package Structure: Organize code to make the hexagonal structure visible and maintainable, with clear separation between core, ports, and adapters

When properly implemented and paired with other methodologies like Domain-Driven Design, ports and adapters can ensure an application’s long-term stability and extensibility, bringing a great deal of value to the system and the enterprise. However, it’s not the silver bullet for all applications and involves a certain level of complexity that when handled with care will bring great benefits, but if broken windows are allowed, it might cause a lot of headaches.

The key lies in progressive adoption rather than all-or-nothing transformation. Teams can introduce hexagonal principles gradually, starting with the most volatile parts of the system where isolation provides immediate value. As comfort with the pattern grows, additional portions of the codebase can be refactored to follow hexagonal principles.

Relationship to Other Architectures

Hexagonal architecture doesn’t exist in isolation but relates to several other architectural patterns and principles. The clean architecture proposed by Robert C. Martin in 2012 combines the principles of hexagonal architecture, the onion architecture, and several other variants, providing additional levels of detail of the component presented as concentric rings. Clean Architecture shares hexagonal architecture’s emphasis on dependency inversion and domain protection while providing more specific guidance about internal structure.

The relationship to traditional layered architecture proves particularly instructive. Traditional layered architecture often leads to database-driven design where we start planning how to store our model in tables rather than considering how our model should behave. Hexagonal architecture inverts this, encouraging developers to think about business behavior first and storage concerns second. The domain drives the architecture, not the database.

Microservices architectures benefit significantly from hexagonal principles. When applying hexagonal architecture principles to microservices, the focus is on encapsulating the core business logic within each microservice while keeping implementation details and external dependencies separate. Each microservice becomes a hexagon, exposing well-defined ports to other services and isolating its domain logic from infrastructure concerns. This isolation makes services more independently deployable, testable, and maintainable.

What We Have Learned

Hexagonal architecture, despite nearly twenty years since its introduction, remains profoundly relevant for modern software development. The pattern addresses a timeless challenge: how to build software that protects business logic from infrastructure volatility while remaining testable, maintainable, and adaptable to change.

The core insight proves elegantly simple yet powerful in application. By treating all external concerns—whether user interfaces or databases—as external actors that interact with business logic through well-defined ports, we invert traditional dependencies and achieve true domain independence. The hexagon contains only what matters to the business problem, insulated from technological choices that will inevitably evolve.

Ports establish clear contracts between the application and external world, defining what the application provides to external actors through primary ports and what it needs from them through secondary ports. These interfaces remain stable even as implementations change, enabling technology substitution without business logic disruption. Adapters implement these contracts using specific technologies, translating between domain concepts and infrastructure realities while keeping the core blissfully ignorant of these details.

The benefits manifest across multiple dimensions. Testing becomes simpler and more reliable with business logic tested in isolation using in-memory implementations. Technology evolution becomes manageable with infrastructure changes contained at adapter boundaries. Development velocity improves through parallel workstreams and clear interfaces. Shopify, PayPal, and others demonstrate these benefits at scale, maintaining complex business logic across multiple channels and integrations.

However, the pattern introduces genuine costs that must be weighed honestly. Initial complexity, learning curves, boilerplate code, and mapping overhead all represent real investments. For simple applications with stable requirements, these costs may outweigh benefits. The pattern makes sense when change is expected, when multiple access channels exist, when testability provides critical value, or when business logic complexity justifies the protection isolation provides.

Implementation success requires thoughtful planning and disciplined execution. Defining core business logic first, establishing clear port boundaries, starting with in-memory adapters for testing, and gradually implementing real infrastructure ensures teams build understanding progressively. The pattern combines powerfully with Domain-Driven Design, with DDD’s strategic patterns identifying bounded contexts and hexagonal architecture providing tactical structure within those contexts.

The relationship to other architectural approaches clarifies hexagonal architecture’s role. Clean Architecture extends hexagonal principles with additional internal structure. Microservices benefit from hexagonal isolation within each service. The contrast with traditional layered architecture highlights how hexagonal thinking inverts conventional wisdom—starting with business behavior rather than database schema, protecting domain logic rather than exposing it to infrastructure concerns.

Looking forward, hexagonal architecture’s relevance will likely increase rather than diminish. As systems grow more complex, as technology churn accelerates, and as quality expectations rise, the pattern’s emphasis on isolation, testability, and adaptability becomes more valuable. The principles underlying hexagonal architecture—dependency inversion, interface segregation, separation of concerns—remain fundamental to well-designed software regardless of specific technologies or frameworks.

The ultimate lesson transcends any particular pattern or technique. Software architecture succeeds when it acknowledges reality: business logic changes more slowly than infrastructure, some parts of systems are more valuable than others, and protecting what matters from what churns creates long-term value. Hexagonal architecture codifies this wisdom into a practical pattern that teams can apply, adapt, and benefit from across diverse domains and contexts. Whether called hexagonal architecture, ports and adapters, or simply good design, the principles endure.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button