Hexagonal architecture structures a microservice so its business logic is not tied to an external interface or dependency. The application core connects through ports, which define contracts; adapters implement those contracts for callers and services such as databases. This ports-and-adapters arrangement helps keep components loosely coupled and testable. section 4.1 section 4.3
An incoming adapter translates an actor’s technology-specific request into a call the application understands. An outgoing adapter translates an application request into an operation on an external dependency. A web or CLI interface can call the core, while a database adapter can persist its data. The core works through port contracts rather than the adapters’ implementation details. section 4.1.4 section 4.2.1
A practical Go project can make these boundaries visible in its folders:
- application holds the domain model and API that implement core behavior. section 4.2.1
- ports holds contracts for accessing the core or an external service, such as a database. section 4.2.1
- adapters holds concrete implementations of those contracts, such as a gRPC request handler. section 4.2.1
Build the application core first, then add outer layers that depend on it. Folder names are a convention, not a rule: application, ports, and adapters may sit under internal, while cmd provides the entry point and wires dependencies such as a database connection into the application. section 4.2.3 section 4.2.1
Placing an order without binding the core to a database
Define an Order domain object and an application API with PlaceOrder. Give the application a ports.DBPort when constructing it; PlaceOrder calls that port to save the order and returns an error if saving fails. A database adapter supplies the concrete implementation, so the application API need not know its internals. section 4.2.3
package api
import (
"github.com/huseyinbabal/microservices/order/internal/application/core/
domain" #1
"github.com/huseyinbabal/microservices/order/internal/ports" #2
)
type Application struct {
db ports.DBPort #3
}
func NewApplication(db ports.DBPort) *Application {
return &Application{
db: db, #4
}
}
func (a Application) PlaceOrder(order domain.Order) (domain.Order, error) {
err := a.db.Save(&order) #5
if err != nil {
return domain.Order{}, err
}
return order, nil
}
The Go listing shows constructor injection of DBPort and the PlaceOrder call through that interface, including propagation of a save error. section 4.2.3





