background-shape
feature-image

Hexagonal architecture, also known as Ports and Adapters architecture, is a software design pattern that aims to separate an application’s core business logic from its external dependencies. The pattern was first introduced by Alistair Cockburn in 2005, and it has since become a popular choice for building scalable, maintainable, and testable software systems.

In GoLang, hexagonal architecture can be particularly useful for distributed applications because of its emphasis on modularity and simplicity. GoLang’s lightweight concurrency primitives make it easy to implement hexagonal architecture in a distributed environment.

In this blog post, we will explore how to implement a hexagonal architecture in Golang. We will start by discussing the key principles of the pattern, and then we will provide code examples to demonstrate how to apply those principles in a Golang application.

Principles of Hexagonal Architecture

The hexagonal architecture is based on the following principles:

  1. Separation of Concerns

The hexagonal architecture separates an application’s core business logic from its external dependencies. This means that the application’s business logic is not coupled to any specific technology or framework, making it easier to change or replace those dependencies without affecting the business logic.

  1. Ports and Adapters

The hexagonal architecture defines two types of components: ports and adapters. Ports are interfaces that define the application’s input and output boundaries, while adapters are implementations of those interfaces that handle the interactions with external systems.

  1. Testability

The hexagonal architecture promotes testability by isolating the application’s core business logic from its external dependencies. This allows developers to write unit tests for the business logic without having to worry about the details of the external systems.

  1. Flexibility

The hexagonal architecture provides flexibility by allowing developers to replace or add new adapters without changing the application’s core business logic. This makes it easier to adapt the application to changing requirements or new technologies.

Implementing Hexagonal Architecture in Golang

Now that we have discussed the key principles of hexagonal architecture, let’s see how to implement them in Golang. We will use an example of a simple e-commerce application that allows customers to place orders for products.

  1. Define Ports

The first step in implementing hexagonal architecture is to define the application’s input and output boundaries. In Golang, we can do this by defining interfaces for the application’s services.

1
2
3
4
5
6
7
type OrderService interface {
    PlaceOrder(order Order) error
}

type ProductService interface {
    GetProduct(id string) (*Product, error)
}

In this example, we have defined two interfaces: OrderService and ProductService. The OrderService interface defines the input boundary for the application’s order placement service, while the ProductService interface defines the input boundary for the application’s product retrieval service.

  1. Implement Adapters

The next step is to implement adapters that implement the interfaces defined in step 1. In Golang, we can do this by creating structs that implement the interfaces.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type OrderServiceAdapter struct {
    db *sql.DB
    productService ProductService
}

func (s *OrderServiceAdapter) PlaceOrder(order Order) error {
    // implementation
}

type ProductServiceAdapter struct {
    db *sql.DB
}

func (s *ProductServiceAdapter) GetProduct(id string) (*Product, error) {
    // implementation
}

In this example, we have created two adapters: OrderServiceAdapter and ProductServiceAdapter. The OrderServiceAdapter struct implements the OrderService interface and handles the interactions with the database and the ProductService adapter. The ProductServiceAdapter struct implements the ProductService interface and handles the interactions with the database.

  1. Wire Up Dependencies

The final step is to wire up the dependencies between the adapters and the application’s core business logic. In Golang, we can do this using dependency injection.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func main() {
    db, _ := sql.Open("mysql", "user:password@tcp(localhost:3306)/ecommerce")
    productService := &ProductServiceAdapter{db: db}
    orderService := &OrderServiceAdapter{db: db, productService: productService}

    http.HandleFunc("/orders", func(w http.ResponseWriter, r *http.Request) {
        // implementation
    })

    http.ListenAndServe(":8080", nil)
}

In this example, we have created instances of the ProductServiceAdapter and OrderServiceAdapter structs and passed them to the OrderHandler function. The OrderHandler function is responsible for handling HTTP requests and delegating them to the appropriate service.

Conclusion

In this blog post, we have discussed how to implement hexagonal architecture in Golang. We started by discussing the key principles of the pattern, and then we provided code examples to demonstrate how to apply those principles in a Golang application. By following the principles of hexagonal architecture, we can create scalable, maintainable, and testable software systems that are flexible and adaptable to changing requirements and technologies.