193 Patterns from Go in Practice (2nd ed., 2025)
193 Patterns from Go in Practice (2nd ed., 2025)
The 2025 edition is structured around four parts: fundamentals, robust application practices, end-to-end web systems, and cloud/advanced topics (reflection, code generation, interop).
What This Adds to Our Book
- Strong mid-level bridge: from language fluency to production engineering.
- Better integration of testing, debugging, and benchmarking as one quality discipline.
- Explicit chapter path toward microservices and external service integration.
End-to-End Service Model
HTTP edge -> business layer -> storage/external APIs -> telemetry -> deployment/runtime checks
Deep Integration Example: External Service Client with Typed Error Strategy
package external
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
)
var ErrUpstreamUnavailable = errors.New("upstream unavailable")
type Client struct {
HTTP *http.Client
Base string
}
type Profile struct {
ID string `json:"id"`
Name string `json:"name"`
}
func NewClient(base string) Client {
return Client{
Base: base,
HTTP: &http.Client{Timeout: 2 * time.Second},
}
}
func (c Client) GetProfile(ctx context.Context, id string) (Profile, error) {
ctx, cancel := context.WithTimeout(ctx, 1200*time.Millisecond)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.Base+"/profiles/"+id, nil)
if err != nil {
return Profile{}, err
}
resp, err := c.HTTP.Do(req)
if err != nil {
return Profile{}, fmt.Errorf("http call failed: %w", ErrUpstreamUnavailable)
}
defer resp.Body.Close()
if resp.StatusCode >= 500 {
return Profile{}, fmt.Errorf("status %d: %w", resp.StatusCode, ErrUpstreamUnavailable)
}
if resp.StatusCode != http.StatusOK {
return Profile{}, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
var p Profile
if err := json.NewDecoder(resp.Body).Decode(&p); err != nil {
return Profile{}, err
}
return p, nil
}Why This Matters
Production services fail most often at integration boundaries. This pattern teaches typed failure handling and timeout discipline in a way that scales across microservices.
Curriculum Upgrades Recommended
- Add one full chapter on external service client design (
timeouts,retry policy,error typing). - Add one chapter on data ingress/egress patterns (upload/download, validation, content type).
- Add one chapter on reflection and code generation boundaries: when useful, when harmful.
More examples
Middleware chain composition
mkdir -p /tmp/go-gip-mw && cd /tmp/go-gip-mw
go mod init example.com/gip-mwSave as main.go:
package main
import (
"fmt"
"net/http"
"net/http/httptest"
)
type Middleware func(http.Handler) http.Handler
func chain(h http.Handler, mws ...Middleware) http.Handler {
for i := len(mws) - 1; i >= 0; i-- {
h = mws[i](h)
}
return h
}
func main() {
var order []string
mw := func(name string) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
order = append(order, name)
next.ServeHTTP(w, r)
})
}
}
h := chain(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
order = append(order, "handler")
fmt.Fprintln(w, "ok")
}), mw("a"), mw("b"))
rr := httptest.NewRecorder()
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/", nil))
fmt.Println(order)
}go run .Expected output:
[a b handler]
Content-type gate for JSON POST
mkdir -p /tmp/go-gip-ct && cd /tmp/go-gip-ct
go mod init example.com/gip-ctSave as main.go:
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
)
func requireJSON(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ct := r.Header.Get("Content-Type")
if !strings.HasPrefix(ct, "application/json") {
http.Error(w, "want json", http.StatusUnsupportedMediaType)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
h := requireJSON(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "json ok")
}))
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`))
h.ServeHTTP(rr, req)
fmt.Println("no ct:", rr.Code)
rr = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`))
req.Header.Set("Content-Type", "application/json")
h.ServeHTTP(rr, req)
fmt.Println("json:", rr.Code, strings.TrimSpace(rr.Body.String()))
}go run .Expected output:
no ct: 415
json: 200 json ok
Runnable example
External client practice: typed upstream errors, client timeout, and status classification with httptest.
mkdir -p /tmp/go-gip && cd /tmp/go-gip
go mod init example.com/gipSave as main.go:
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"time"
)
var ErrUpstreamUnavailable = errors.New("upstream unavailable")
type Profile struct {
Name string `json:"name"`
}
func fetchProfile(ctx context.Context, client *http.Client, base string) (Profile, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, base+"/profile", nil)
if err != nil {
return Profile{}, err
}
resp, err := client.Do(req)
if err != nil {
return Profile{}, fmt.Errorf("http: %w", ErrUpstreamUnavailable)
}
defer resp.Body.Close()
if resp.StatusCode >= 500 {
return Profile{}, fmt.Errorf("status %d: %w", resp.StatusCode, ErrUpstreamUnavailable)
}
if resp.StatusCode != http.StatusOK {
return Profile{}, fmt.Errorf("unexpected status %d", resp.StatusCode)
}
var p Profile
if err := json.NewDecoder(resp.Body).Decode(&p); err != nil {
return Profile{}, err
}
return p, nil
}
func main() {
var hits int
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
hits++
if hits == 1 {
w.WriteHeader(http.StatusBadGateway)
return
}
_ = json.NewEncoder(w).Encode(Profile{Name: "ada"})
}))
defer ts.Close()
client := &http.Client{Timeout: 300 * time.Millisecond}
ctx := context.Background()
_, err := fetchProfile(ctx, client, ts.URL)
fmt.Println("first:", err, "unavailable?", errors.Is(err, ErrUpstreamUnavailable))
p, err := fetchProfile(ctx, client, ts.URL)
fmt.Println("second:", p, err)
}go run .Expected output:
first: status 502: upstream unavailable unavailable? true
second: {ada} <nil>
What to notice
- Wrap with
%wand a sentinel so callers decide retry vs fail without parsing strings. - Always set
http.Client.Timeout(or context deadline) on outbound deps. - Close response bodies to preserve connection reuse.
Try next
- Retry only when
errors.Is(err, ErrUpstreamUnavailable)with jitter. - Map 404 to a distinct
ErrNotFoundsentinel.