Advanced Design Patterns in Go with Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Design Patterns in Go: Advanced Level for Professional Developers

\n\n

Go (Golang) is a language designed for efficient work with concurrency and microservice architecture. Basic design patterns such as Singleton or Factory are well known, but creating high-load and fault-tolerant systems requires an advanced level of pattern mastery. In this article, we will analyze four key patterns: Fan-Out/Fan-In, Pipeline, Worker Pool, and Circuit Breaker. Each of them solves practical problems of scaling, parallel processing, and protecting the system from cascading failures.

\n\n

1. Fan-Out / Fan-In Pattern: Parallel Data Processing

\n\n

This pattern is the foundation of parallel computing in Go. Fan-Out distributes the load among multiple goroutines, while Fan-In collects the results back into a single channel.

\n\n

Fan-Out Implementation

\n

Imagine a task: you need to process 100 URLs. Instead of sequential processing, we launch 10 goroutines, each taking a task from a common channel.

\n\n
package main\n\nimport (\n    "fmt"\n    "sync"\n)\n\nfunc worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {\n    defer wg.Done()\n    for job := range jobs {\n        // Simulating processing\n        results <- job * 2\n        fmt.Printf("Worker %d processed job %d\\n", id, job)\n    }\n}\n\nfunc main() {\n    const numJobs = 20\n    const numWorkers = 5\n\n    jobs := make(chan int, numJobs)\n    results := make(chan int, numJobs)\n    var wg sync.WaitGroup\n\n    // Fan-Out: launching workers\n    for w := 1; w <= numWorkers; w++ {\n        wg.Add(1)\n        go worker(w, jobs, results, &wg)\n    }\n\n    // Sending tasks\n    for j := 1; j <= numJobs; j++ {\n        jobs <- j\n    }\n    close(jobs)\n\n    // Fan-In: waiting for completion and closing results\n    go func() {\n        wg.Wait()\n        close(results)\n    }()\n\n    // Collecting results\n    for result := range results {\n        fmt.Println("Result:", result)\n    }\n}\n
\n\n

Key points:

\n
    \n
  • The jobs channel serves as the Fan-Out point: all workers read from it.
  • \n
  • The results channel is the Fan-In point: all workers write to it.
  • \n
  • Using sync.WaitGroup ensures we close results only after all goroutines have completed.
  • \n
\n\n

2. Pipeline Pattern: Conveyor Data Processing

\n\n

Pipeline is a chain of stages, where each stage processes data and passes it to the next. In Go, this is implemented through a sequence of channels. The pattern is ideal for ETL processes, log processing, or data transformation.

\n\n

Example: Text Processing Pipeline

\n\n
package main\n\nimport "fmt"\n\n// Stage 1: number generation\nfunc generate(nums ...int) <-chan int {\n    out := make(chan int)\n    go func() {\n        for _, n := range nums {\n            out <- n\n        }\n        close(out)\n    }()\n    return out\n}\n\n// Stage 2: squaring\nfunc square(in <-chan int) <-chan int {\n    out := make(chan int)\n    go func() {\n        for n := range in {\n            out <- n * n\n        }\n        close(out)\n    }()\n    return out\n}\n\n// Stage 3: multiplication by 2\nfunc multiply(in <-chan int, factor int) <-chan int {\n    out := make(chan int)\n    go func() {\n        for n := range in {\n            out <- n * factor\n        }\n        close(out)\n    }()\n    return out\n}\n\nfunc main() {\n    // Building the pipeline\n    pipeline := multiply(square(generate(1, 2, 3, 4)), 10)\n\n    // Consuming results\n    for result := range pipeline {\n        fmt.Println(result) // 10, 40, 90, 160\n    }\n}\n
\n\n

Advantages:

\n
    \n
  • Each stage is a separate

Blogs

Book Recommendations