liveBook · Manning

Hexagonal architecture

As explained in Learn Go with Pocket-Sized Projects

Hexagonal architecture is a software architecture pattern that isolates core application logic from external systems through defined interfaces.

Hexagonal architecture, also called ports and adapters, keeps a service’s core logic distinct from its external concerns. Domain types provide a common language: API handlers and data access code can depend on the domain, while the domain depends on neither. This direction of dependency helps prevent import cycles. section 8.3.1

A small Go service can move toward that design without building every layer:

  • Define shared game data in an internal/session package: Game has an ID, attempts left, guesses, and status. Keep business errors, such as a guess after game over, with the domain types. section 8.3.1
  • Let HTTP handlers manage request and response details. Put game creation in a separate function, then convert session.Game into the API’s GameResponse. The conversion gives API formatting one clear home and can be tested on its own. section 8.3.2
  • Wrap a gRPC client inside internal/client. Its public method returns local habit.Habit values rather than exposing generated protobuf types; an HTTP server can depend on a small HabitsClient interface. section 11.1.3
Listing 8.22 convert.go: Adapter between the API and domain
// ToGameResponse converts a session.Game into an GameResponse.
func ToGameResponse(g session.Game) GameResponse {
    apiGame := api.GameResponse{
        ID:           string(g.ID),
        AttemptsLeft: g.AttemptsLeft,
        Guesses:      make([]Guess, len(g.Guesses)), #1
        Status:       string(g.Status),
        // TODO WordLength #2
    }

    for index := 0; index < len(g.Guesses); index++ {
        apiGame.Guesses[index].Word = 
            g.Guesses[index].Word #3
        apiGame.Guesses[index].Feedback = g.Guesses[index].Feedback
    }

    if g.AttemptsLeft == 0 { #4
        apiGame.Solution = ""// TODO solution #2
    }

    return apiGame
}

The API adapter copies a session.Game into a GameResponse, including each guess. Its TODOs also show that the conversion is not yet complete. section 8.3.2

Keep generated types at the client boundary

A habits client holds an api.HabitsClient internally. ListHabits calls the generated gRPC method and builds []habit.Habit for callers. Tests supply a mock gRPC client to check both returned habits and errors. The HTTP server receives the wrapper through an interface that names only the operation it needs. section 11.1.3

Use the boundary where it clarifies responsibilities, not as a reason to multiply packages. For the small game service, domain types live in their own package, but domain logic stays in the handlers package; core logic and API details still belong in distinct functions. section 8.3.1 section 8.3.2

How Learn Go with Pocket-Sized Projects covers it

How deep it goes
Works through package boundaries and conversions, but leaves the small game service short of fully independent layers.
Examples
Uses a game service’s session-to-HTTP conversion and a habits service’s protobuf-to-domain client wrapper.
What it assumes
Assumes familiarity with Go packages, HTTP handlers, and service clients while defining the needed domain types explicitly.
What you can do after
Apply it in Go by defining domain-facing types, isolating transport conversions, and testing adapters with mocks.
How it connects
Connects domain modeling, dependency direction, API adapters, gRPC clients, interfaces, and unit testing.

Also explained in

Compare how 6 books explain hexagonal architecture →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Go with Pocket-Sized Projects. The section links point to the parts of the book it draws on.