liveBook · Manning

Anticorruption layer

As explained in Learn Go with Pocket-Sized Projects

An anti-corruption layer is an integration boundary that translates an external model into a domain's internal model while preventing unwanted coupling.

An anti-corruption layer keeps a backend’s data structures and calling conventions out of the rest of an application. In an HTML server backed by a habits service, the `client` package isolates the generated gRPC structures, while the `habit` package defines data that can pass between internal packages. section 11.1.3

Listing 11.14 list.go: Client can list the habits
// ListHabits lists the habits available.
func (hc *HabitsClient) ListHabits(ctx context.Context, 
    t time.Time) ([]habit.Habit, error) { #1
    resp, err := hc.cli.ListHabits(ctx, 
        &api.ListHabitsRequest{}) #2
    if err != nil {...}

    list := make([]habit.Habit, len(resp.Habits))
    for i, h := range resp.Habits {
        list[i] = habit.Habit{ #3
            ID: //...
        }
    }
    return list, nil
}

The wrapping client calls the generated gRPC client, then starts constructing a slice of internal `habit.Habit` values from the response. Callers receive domain data rather than Protobuf response types. section 11.1.3

Keep the boundary testable by passing the generated `api.HabitsClient` interface into the wrapper’s constructor. A handler can depend on an even smaller interface that returns `[]habit.Habit`; it need not know how the backend call works. Tests can substitute a mock client and check both returned habits and backend errors. section 11.1.3

Listing 11.20 server.go: Using the client dependency
// HabitsClient is the dependency towards the Habits client.
//go:generate minimock -s "_mock.go" -o "mocks" #1
type HabitsClient interface {
    ListHabits(ctx context.Context) ([]habit.Habit, error)
}

// Server serves all the HTML routes on this service.
type Server struct {
    client HabitsClient #2
    lgr    Logger
}

// New builds a new server.
func New(cli HabitsClient, lgr Logger) *Server { #3
    return &Server{
        client: cli,
        lgr: lgr,
    }
}

// Router returns an HTTP handler that listens to all the proper paths.
func (s *Server) Router() http.Handler {...}

The HTTP server’s dependency names the one operation it needs, `ListHabits`, in terms of internal habit values. This keeps generated gRPC types out of the handler interface. section 11.1.3

The same boundary can serve another API: a service reads external data and inserts it into its domain. The practical takeaway is to put translation and external calls in one place, then let the application work with its own model. section 1.3.1 section 11.1.3

How Learn Go with Pocket-Sized Projects covers it

How deep it goes
Works through package separation, client wrapping, response conversion, dependency injection, and mock-based error tests.
Examples
Uses a habits service whose client lists habits and requests each habit’s status before returning internal values.
What it assumes
Assumes an existing gRPC habits service and generated Protobuf client, then defines the HTTP server’s internal packages.
What you can do after
Build it from scratch by wrapping a generated gRPC client, returning domain values, and testing the wrapper with a mock.
How it connects
Connects domain models, gRPC clients, Protobuf, Go interfaces, dependency injection, and HTTP handlers.

Also explained in

Compare how 3 books explain anticorruption layer →

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.