Hexagonal Architecture, also known as Ports and Adapters Architecture, has gained traction for its focus on clean separation of concerns and improved maintainability. In this post I share what exactly happens within the hexagon. Let’s break down the key components and data flow.

Entities

These are the heart of your domain, representing core concepts with attributes and potentially behavior. They encapsulate data and enforce domain rules. Think of a Product entity with properties like name, price, and a method to calculate discountedPrice.

Repository

This acts as an abstraction layer for accessing and manipulating entities. It defines a port, offering methods like save, find, and delete that operate on entities, independent of the underlying storage mechanism (database, file system, etc.). A ProductRepository interface, for instance, would declare methods like saveProduct(product Product) and findProductById(id uuid).

Service

The service layer orchestrates the business logic. It interacts with repositories to retrieve or store entities and implements the core application logic. Services should not depend on specific technologies for presentation or persistence. Imagine a ProductService with methods like createProduct(product Product) that validates data, interacts with the ProductRepository, and performs other business logic.

Controller

This is the entry point for user interactions. It receives requests from the frontend (web, API, etc.), maps incoming data to domain objects (entities), interacts with services to perform actions, and transforms service responses back into a format suitable for the frontend (JSON). A ProductController might receive a POST request with product details, convert it to a Product entity, call the ProductService to create it, and send a success message back to the frontend.

Data Flow

The controller component receives and parses a user request, extracting relevant data to construct a domain object representing the user’s input. This domain object is then passed to the appropriate method within the service layer, where business logic related to the requested action is executed. The service interacts with the repository layer using defined methods, allowing the repository to handle the actual persistence logic, such as storing the data in a database using an Object-Relational Mapping (ORM) tool. Upon receiving confirmation or data from the repository, the service prepares a response based on the outcome of the operation. Finally, the controller sends this response back to the user interface, which may include a success message or details of the created entity.

Project Structure

When implementing a hexagonal architecture in a project, a well-structured folder layout is crucial for adhering to architectural principles. Placing folders and files close to where they are used ensures clarity and reinforces architectural integrity.

  • cmd: Contains command-line application entry points.
  • internal: Holds internal packages of the project.
  • adapter: Contains adapters for external systems.
    • handler: Adapters for various protocols (e.g., HTTP/REST, gRPC).
    • repository: Adapters for different databases (e.g., PostgreSQL, MongoDB).
  • core: Contains the core business logic of the application.
    • domain: Domain entities representing specific objects within the application’s domain.
    • ports: Interfaces defining interactions with adapters.
    • service: Where main business logic is performed.
  • util: Stores utility functions and helpers used across the project.

This hierarchical structure facilitates a clearer separation of concerns and promotes modularity:

/app
|-- /cmd
|   |-- main.go
|-- /docs
|-- /internal
|   |-- /adapter
|   |   |-- /handler
|   |   |   |-- /rest
|   |   |   |-- /gRPC
|   |   |-- /repository
|   |   |   |-- /gorm
|   |   |   |-- /mongo
|   |   |-- /glue
|   |   |   |-- /route
|   |   |   |-- route.go
|   |   |-- /dto
|   |   |-- /templates
|   |-- /core
|   |   |-- /entity
|   |   |-- /port
|   |   |-- /service
|   |   |   |-- /test
|   |   |-- /util

Summary

By separating core logic from presentation and persistence, changes in one area have minimal impact on others, promoting maintainability and testability. The core application remains independent of the underlying technologies used for data storage or presentation. Each layer can be tested in isolation with mock implementations, leading to more reliable and efficient testing practices.

Hexagonal Architecture empowers us to build clean, maintainable, and scalable applications. By understanding the roles of each component and the data flow, you can leverage this powerful approach to streamline your development process.

I have composed a starter project which demonstrates these topics in action with Go. Check it out on GitHub.