liveBook · Manning

Dependency injection

As explained in Go in Action, Second Edition

Dependency injection is a design technique in which a component receives its required collaborators from outside rather than constructing them internally.

Dependency injection keeps component construction outside the component itself. In a Go HTTP service, handlers receive the smallest domain interface they need instead of creating or depending directly on storage. section 11.3.2

Narrowing the injected dependency provides practical design benefits:

  • Makes allowed operations explicit through the type system. section 11.3.2
  • Simplifies tests by requiring mocks for only the operations used. section 11.3.2
  • Separates HTTP translation from domain storage and construction. section 11.3.2

Injecting an ArticleReader

type Handlers struct { articles domain.ArticleReader }\n\nfunc New(articles domain.ArticleReader) *Handlers {\n return &Handlers{articles: articles}\n}\n\nfunc main() {\n store, _ := storage.NewBoltStore("./articles.db", true)\n h := handlers.New(store)\n h.RegisterRoutes(mux)\n}\n\nThe concrete BoltStore is created at the application boundary, while handlers depend only on the read-only interface. section 11.3.2

Listing 11.4 HTTP handlers with an injected ArticleReader
package handlers

import (
    "encoding/json"
    "net/http"
    "strconv"

    "github.com/YOUR_USERNAME/go-news/domain"
)

type Handlers struct { #1
    articles domain.ArticleReader
}

func New(articles domain.ArticleReader) *Handlers { #2
    return &Handlers{
        articles: articles,
    }
}

func (h *Handlers) RegisterRoutes(mux *http.ServeMux) { #3
    mux.HandleFunc("/articles", h.articlesHandler)
}

func (h *Handlers) articlesHandler(w http.ResponseWriter, r *http.Request) { #4
    if r.Method != http.MethodGet {
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
        return
    }

    n := 10
    if countStr := r.URL.Query().Get("count"); countStr != "" { #5
        if count, err := strconv.Atoi(countStr); err == nil && count > 0 {
            n = count
        }
    }

    articles := h.articles.GetRecent(n)

    w.Header().Set("Content-Type", "application/json") #6
    if err := json.NewEncoder(w).Encode(articles); err != nil {
        http.Error(w, "failed to encode response", http.StatusInternalServerError)
        return
    }
}

Inspect the complete handler implementation, including its injected ArticleReader and route registration. section 11.3.2

How Go in Action, Second Edition covers it

How deep it goes
Demonstrates constructor injection with a narrowed interface and concrete storage implementation.
Examples
Uses Go handlers, ArticleReader, BoltStore, route registration, and an API module structure.
What it assumes
Assumes familiarity with Go interfaces, HTTP handlers, domain storage, and module organization.
What you can do after
Build it from scratch by injecting a concrete storage implementation into a handler that requires only a read interface.
How it connects
Links interfaces, HTTP handlers, domain storage, testing, workspaces, and the Liskov substitution principle.

Also explained in

Compare how 21 books explain dependency injection →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go in Action, Second Edition. The section links point to the parts of the book it draws on.