Hexagonal Architecture, Ports and Adapters in Go

Kyodo Tech
3 min readJul 30, 2023

Developing scalable, maintainable, and testable software poses a significant challenge. The Hexagonal Architecture, also known as Ports and Adapters, offers a robust foundation to meet these objectives. This architectural approach enables clean boundaries and better separation of concerns in our application. While it upholds the concept of layering found in traditional architectures, it emphasizes a clear demarcation between our application’s core logic and its interactions with external agents. This separation ensures that changes in peripheral systems do not impact the central business logic, thereby promoting maintainability and testability. In this post, we’ll explore Hexagonal Architecture’s principles and demonstrate their application in Go with a minimal example.

Hexagonal Architecture revolves around key components:

  • Domain Model: This consists of the business logic at the core of our application.
  • Ports: These are interfaces defining the I/O operations of our system. They serve as contracts for the adapters.
  • Adapters: These entities implement the defined ports, enabling interaction between our application and external systems.
  • Layers: The organization of our application into separate layers each with a distinct responsibility.

The term “Hexagonal Architecture” doesn’t imply six components but represents an architecture where the domain model, surrounded by ports and adapters, resembles a hexagon, with each side as a potential communication point with external systems.

Advantages of a Hexagonal Architecture

Decoupling from technology: Hexagonal Architecture separates the application’s core business logic from its input and output technologies, such as databases, UIs, and testing suites. This separation yields a more maintainable, adaptable codebase. By separating concerns, we can test the business logic independently of its infrastructure, simplifying and speeding up tests. This approach often maps directly onto the application’s use cases, streamlining traceability throughout the system. We also seek to avoid the pitfalls of over-engineering by using Go code generators, ensuring that the complexity of our abstractions is justified by their value, particularly for smaller services.

Applying Hexagonal Architecture in Go

Go’s design philosophy resonates with Hexagonal Architecture, particularly due to its static typing that guarantees compile-time interface implementation checks. This feature is particularly useful when defining Ports (interfaces), as it supports lean error handling and straightforward Adapter implementation. Here’s how we can apply it:

  1. Domain Definition: Define our business logic within Go structs and interfaces.
  2. Ports as Interfaces: Define ports as interfaces in Go. These outline the methods required for interaction with external systems.
type MessagePublisher interface {
Publish(message Message) error
}
  1. Adapter Implementation: Create types that implement these interfaces to serve as adapters. Each adapter handles the specifics of an external system, such as the NSQ distributed messaging platform .
type NSQPublisher struct {
producer *nsq.Producer
}

func (n *NSQPublisher) Publish(message Message) error {
// NSQ-specific publishing operation
}
  1. Layering our Application: In the application’s entry point, instantiate our adapters and link them to our application through the ports, carefully organizing these components into distinct layers.

Our application should be structured into three distinct layers:

  • Infrastructure Layer: Where we instantiate the adapters and implement the I/O handling (database connections, network protocols, etc.). The adapters in this layer serve as the bridge between our application and the outside world.
  • Application Layer: The use-cases of our system, defining the operations that can be performed on our domain models. It serves as the orchestrator, guiding the interaction between the Domain Layer and Infrastructure Layer.
  • Domain Layer: The core of our application, where the business logic resides.

Conclusion

Implementing Hexagonal Architecture in Go contributes to the development of scalable, maintainable, and testable software. It enables us to segregate our core business logic from external concerns, fostering clean, testable code and facilitating organized, decoupled structures. Such an architecture prepares our Go applications to flexibly accommodate changes in the business domain.

--

--