192 Patterns from Effective Go Recipes (2024)
192 Patterns from Effective Go Recipes (2024)
This source emphasizes practical recipes across I/O, JSON streaming, HTTP middleware/timeouts, text normalization, function options, errors, concurrency, sockets, C interop, testing, and shipping.
What This Adds to Our Book
- Recipe-level production patterns that are small, composable, and immediately reusable.
- Better treatment of streaming and incremental processing.
- Stronger shipping pipeline topics: build tags, static builds, version injection, Docker packaging.
Streaming-Oriented Mental Model
source -> decoder -> transform -> validator -> sink
(stream) (bounded memory)
Deep Integration Example: Streaming JSON Pipeline with Backpressure
package pipeline
import (
"bufio"
"context"
"encoding/json"
"errors"
"io"
"sync"
"time"
)
type Event struct {
ID string `json:"id"`
Type string `json:"type"`
}
func DecodeEvents(ctx context.Context, r io.Reader) (<-chan Event, <-chan error) {
out := make(chan Event, 128)
errCh := make(chan error, 1)
go func() {
defer close(out)
defer close(errCh)
dec := json.NewDecoder(bufio.NewReader(r))
for {
var e Event
if err := dec.Decode(&e); err != nil {
if errors.Is(err, io.EOF) {
return
}
errCh <- err
return
}
select {
case <-ctx.Done():
errCh <- ctx.Err()
return
case out <- e:
}
}
}()
return out, errCh
}
func ProcessEvents(ctx context.Context, in <-chan Event, workers int, fn func(Event) error) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
errCh := make(chan error, workers)
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for e := range in {
opCtx, stop := context.WithTimeout(ctx, 250*time.Millisecond)
err := fn(e)
stop()
if opCtx.Err() != nil {
errCh <- opCtx.Err()
cancel()
return
}
if err != nil {
errCh <- err
cancel()
return
}
}
}()
}
wg.Wait()
close(errCh)
for err := range errCh {
if err != nil {
return err
}
}
return nil
}Why This Matters
Modern Go systems increasingly process streams instead of loading full datasets in memory. Recipe-style patterns are especially effective for this style because they focus on one robust tactic at a time.
Curriculum Upgrades Recommended
- Add a chapter on streaming-first design (
json.Decoder,io.Reader, chunked processing). - Add chapter on function options + config normalization.
- Add chapter on shipping pipelines: reproducible builds, tags, embedding, static binaries.
- Add explicit
os/execand signal-handling narrative for operational tooling.
More examples
Functional options recipe
mkdir -p /tmp/go-egr-opts && cd /tmp/go-egr-opts
go mod init example.com/egr-optsSave as main.go:
package main
import (
"fmt"
"time"
)
type Client struct {
timeout time.Duration
retries int
}
type Option func(*Client)
func WithTimeout(d time.Duration) Option {
return func(c *Client) { c.timeout = d }
}
func WithRetries(n int) Option {
return func(c *Client) { c.retries = n }
}
func NewClient(opts ...Option) *Client {
c := &Client{timeout: time.Second, retries: 1}
for _, o := range opts {
o(c)
}
return c
}
func main() {
c := NewClient(WithTimeout(200*time.Millisecond), WithRetries(3))
fmt.Printf("timeout=%s retries=%d\n", c.timeout, c.retries)
}go run .Expected output:
timeout=200ms retries=3
Table-driven validation recipe
mkdir -p /tmp/go-egr-table && cd /tmp/go-egr-table
go mod init example.com/egr-tableSave as main.go:
package main
import (
"fmt"
"strings"
)
func validEmail(s string) bool {
return strings.Count(s, "@") == 1 && !strings.HasPrefix(s, "@") && !strings.HasSuffix(s, "@")
}
func main() {
cases := []struct {
in string
want bool
}{
{"a@b.co", true},
{"@x", false},
{"nope", false},
}
for _, tc := range cases {
got := validEmail(tc.in)
fmt.Printf("%q got=%v want=%v ok=%v\n", tc.in, got, tc.want, got == tc.want)
}
}go run .Expected output:
"a@b.co" got=true want=true ok=true
"@x" got=false want=false ok=true
"nope" got=false want=false ok=true
Runnable example
Recipe-style patterns: functional options for config, streaming json.Decoder over a reader, and a context-aware worker fan-in.
mkdir -p /tmp/go-recipes && cd /tmp/go-recipes
go mod init example.com/recipesSave as main.go:
package main
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
)
type Config struct {
Workers int
Timeout time.Duration
}
type Option func(*Config)
func WithWorkers(n int) Option {
return func(c *Config) { c.Workers = n }
}
func WithTimeout(d time.Duration) Option {
return func(c *Config) { c.Timeout = d }
}
func NewConfig(opts ...Option) Config {
c := Config{Workers: 2, Timeout: time.Second}
for _, o := range opts {
o(&c)
}
return c
}
func main() {
cfg := NewConfig(WithWorkers(3), WithTimeout(200*time.Millisecond))
fmt.Printf("config: workers=%d timeout=%s\n", cfg.Workers, cfg.Timeout)
// Streaming JSON values (NDJSON)
r := strings.NewReader("{\"n\":1}\n{\"n\":2}\n{\"n\":3}\n")
dec := json.NewDecoder(r)
var sum int
for dec.More() {
var row struct {
N int `json:"n"`
}
if err := dec.Decode(&row); err != nil {
panic(err)
}
sum += row.N
}
fmt.Println("stream_sum:", sum)
// Bounded workers
ctx, cancel := context.WithTimeout(context.Background(), cfg.Timeout)
defer cancel()
jobs := make(chan int, 8)
var wg sync.WaitGroup
var mu sync.Mutex
var out int
for i := 0; i < cfg.Workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := range jobs {
select {
case <-ctx.Done():
return
default:
mu.Lock()
out += j
mu.Unlock()
}
}
}()
}
for i := 1; i <= 5; i++ {
jobs <- i
}
close(jobs)
wg.Wait()
fmt.Println("worker_sum:", out)
}go run .Expected output:
config: workers=3 timeout=200ms
stream_sum: 6
worker_sum: 15
What to notice
- Options keep defaults + overrides readable without telescoping constructors.
json.Decoderstreams; it does not requireReadAllof a huge payload.- Workers always honor
ctx.Done()for operational cancellation.
Try next
- Add
WithNameoption and validateWorkers > 0inNewConfig. - Decode a large file with
os.Open+ Decoder in a loop.