Skillsgolang-backend-development
G

golang-backend-development

Complete guide for Go backend development including concurrency patterns, web servers, database integration, microservices, and production deployment

manutej
36 stars
1.2k downloads
Updated 1w ago

Readme

golang-backend-development follows the SKILL.md standard. Use the install command to add it to your agent stack.

---
name: golang-backend-development
description: Complete guide for Go backend development including concurrency patterns, web servers, database integration, microservices, and production deployment
tags: [golang, go, concurrency, web-servers, microservices, backend, goroutines, channels, grpc, rest-api]
tier: tier-1
---

# Go Backend Development

A comprehensive skill for building production-grade backend systems with Go. Master goroutines, channels, web servers, database integration, microservices architecture, and deployment patterns for scalable, concurrent backend applications.

## When to Use This Skill

Use this skill when:

- Building high-performance web servers and REST APIs
- Developing microservices architectures with gRPC or HTTP
- Implementing concurrent processing with goroutines and channels
- Creating real-time systems requiring high throughput
- Building database-backed applications with connection pooling
- Developing cloud-native applications for containerized deployment
- Writing performance-critical backend services
- Building distributed systems with service discovery
- Implementing event-driven architectures
- Creating CLI tools and system utilities with networking capabilities
- Developing WebSocket servers for real-time communication
- Building data processing pipelines with concurrent workers

**Go excels at:**
- Network programming and HTTP services
- Concurrent processing with lightweight goroutines
- System-level programming with garbage collection
- Cross-platform compilation
- Fast compilation times for rapid development
- Built-in testing and benchmarking

## Core Concepts

### 1. Goroutines: Lightweight Concurrency

Goroutines are lightweight threads managed by the Go runtime. They enable concurrent execution with minimal overhead.

**Key Characteristics:**
- Extremely lightweight (start with ~2KB stack)
- Multiplexed onto OS threads by the runtime
- Thousands or millions can run concurrently
- Scheduled cooperatively with integrated scheduler

**Basic Goroutine Pattern:**

```go
func main() {
    // Launch concurrent computation
    go expensiveComputation(x, y, z)
    anotherExpensiveComputation(a, b, c)
}
```

The `go` keyword launches a new goroutine, allowing `expensiveComputation` to run concurrently with `anotherExpensiveComputation`. This is fundamental to Go's concurrency model.

**Common Use Cases:**
- Background processing
- Concurrent API calls
- Parallel data processing
- Real-time event handling
- Connection handling in servers

### 2. Channels: Safe Communication

Channels provide type-safe communication between goroutines, eliminating the need for explicit locks in many scenarios.

**Channel Types:**

```go
// Unbuffered channel - synchronous communication
ch := make(chan int)

// Buffered channel - asynchronous up to buffer size
ch := make(chan int, 100)

// Read-only channel
func receive(ch <-chan int) { /* ... */ }

// Write-only channel
func send(ch chan<- int) { /* ... */ }
```

**Synchronization with Channels:**

```go
func computeAndSend(ch chan int, x, y, z int) {
    ch <- expensiveComputation(x, y, z)
}

func main() {
    ch := make(chan int)
    go computeAndSend(ch, x, y, z)
    v2 := anotherExpensiveComputation(a, b, c)
    v1 := <-ch  // Block until result available
    fmt.Println(v1, v2)
}
```

This pattern ensures both computations complete before proceeding, with the channel providing both communication and synchronization.

**Channel Patterns:**
- Producer-consumer
- Fan-out/fan-in
- Pipeline stages
- Timeouts and cancellation
- Semaphores and rate limiting

### 3. Select Statement: Multiplexing Channels

The `select` statement enables multiplexing multiple channel operations, similar to a switch for channels.

**Timeout Implementation:**

```go
timeout := make(chan bool, 1)
go func() {
    time.Sleep(1 * time.Second)
    timeout <- true
}()

select {
case <-ch:
    // Read from ch succeeded
case <-timeout:
    // Operation timed out
}
```

**Context-Based Cancellation:**

```go
select {
case result := <-resultCh:
    return result
case <-ctx.Done():
    return ctx.Err()
}
```

### 4. Context Package: Request-Scoped Values

The `context.Context` interface manages deadlines, cancellation signals, and request-scoped values across API boundaries.

**Context Interface:**

```go
type Context interface {
    // Done returns a channel closed when work should be canceled
    Done() <-chan struct{}

    // Err returns why context was canceled
    Err() error

    // Deadline returns when work should be canceled
    Deadline() (deadline time.Time, ok bool)

    // Value returns request-scoped value
    Value(key any) any
}
```

**Creating Contexts:**

```go
// Background context - never canceled
ctx := context.Background()

// With cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// With deadline
deadline := time.Now().Add(10 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()

// With values
ctx = context.WithValue(parentCtx, key, value)
```

**Best Practices:**
- Always pass context as first parameter: `func DoSomething(ctx context.Context, ...)`
- Call `defer cancel()` immediately after creating cancelable context
- Propagate context through call chain
- Check `ctx.Done()` in long-running operations
- Use context values only for request-scoped data, not optional parameters

### 5. WaitGroup: Coordinating Goroutines

`sync.WaitGroup` waits for a collection of goroutines to finish.

**Basic Pattern:**

```go
var wg sync.WaitGroup

for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        // Do work
    }(i)
}

wg.Wait()  // Block until all goroutines complete
```

**Common Use Cases:**
- Waiting for parallel tasks
- Coordinating worker pools
- Ensuring cleanup completion
- Synchronizing shutdown

### 6. Mutex: Protecting Shared State

When shared state is necessary, use `sync.Mutex` or `sync.RWMutex` for protection.

**Mutex Pattern:**

```go
var (
    service   map[string]net.Addr
    serviceMu sync.Mutex
)

func RegisterService(name string, addr net.Addr) {
    serviceMu.Lock()
    defer serviceMu.Unlock()
    service[name] = addr
}

func LookupService(name string) net.Addr {
    serviceMu.Lock()
    defer serviceMu.Unlock()
    return service[name]
}
```

**RWMutex for Read-Heavy Workloads:**

```go
var (
    cache   map[string]interface{}
    cacheMu sync.RWMutex
)

func Get(key string) interface{} {
    cacheMu.RLock()
    defer cacheMu.RUnlock()
    return cache[key]
}

func Set(key string, value interface{}) {
    cacheMu.Lock()
    defer cacheMu.Unlock()
    cache[key] = value
}
```

### 7. Concurrent Web Server Pattern

Go's standard pattern for handling concurrent connections:

```go
for {
    rw := l.Accept()
    conn := newConn(rw, handler)
    go conn.serve()  // Handle each connection concurrently
}
```

Each accepted connection is handled in its own goroutine, allowing the server to scale to thousands of concurrent connections efficiently.

## Web Server Development

### HTTP Server Basics

**Simple HTTP Server:**

```go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe("localhost:8080", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello!")
}
```

### Request Handling Patterns

**Handler Functions:**

```go
func handler(w http.ResponseWriter, r *http.Request) {
    // Read request
    method := r.Method
    path := r.URL.Path
    query := r.URL.Query()

    // Write response
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, `{"message": "success"}`)
}
```

**Handler Structs:**

```go
type APIHandler struct {
    db *sql.DB
    logger *log.Logger
}

func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // Access dependencies
    h.logger.Printf("Request: %s %s", r.Method, r.URL.Path)
    // Handle request
}
```

### Middleware Pattern

**Logging Middleware:**

```go
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// Usage
http.Handle("/api/", loggingMiddleware(apiHandler))
```

**Authentication Middleware:**

```go
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if !isValidToken(token) {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}
```

**Chaining Middleware:**

```go
handler := loggingMiddleware(authMiddleware(corsMiddleware(apiHandler)))
http.Handle("/api/", handler)
```

### Context in HTTP Handlers

**HTTP Request with Context:**

```go
func handleSearch(w http.ResponseWriter, req *http.Request) {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // Check query parameter
    query := req.FormValue("q")
    if query == "" {
        http.Error(w, "missing query", http.StatusBadRequest)
        return
    }

    // Perform search with context
    results, err := performSearch(ctx, query)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Render results
    renderTemplate(w, results)
}
```

**Context-Aware HTTP Request:**

```go
func httpDo(ctx context.Context, req *http.Request,
            f func(*http.Response, error) error) error {
    c := &http.Client{}

    // Run request in goroutine
    ch := make(chan error, 1)
    go func() {
        ch <- f(c.Do(req))
    }()

    // Wait for completion or cancellation
    select {
    case <-ctx.Done():
        <-ch  // Wait for f to return
        return ctx.Err()
    case err := <-ch:
        return err
    }
}
```

### Routing Patterns

**Custom Router:**

```go
type Router struct {
    routes map[string]http.HandlerFunc
}

func (r *Router) Handle(pattern string, handler http.HandlerFunc) {
    r.routes[pattern] = handler
}

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    if handler, ok := r.routes[req.URL.Path]; ok {
        handler(w, req)
    } else {
        http.NotFound(w, req)
    }
}
```

**RESTful API Structure:**

```go
// GET /api/users
func listUsers(w http.ResponseWriter, r *http.Request) { /* ... */ }

// GET /api/users/:id
func getUser(w http.ResponseWriter, r *http.Request) { /* ... */ }

// POST /api/users
func createUser(w http.ResponseWriter, r *http.Request) { /* ... */ }

// PUT /api/users/:id
func updateUser(w http.ResponseWriter, r *http.Request) { /* ... */ }

// DELETE /api/users/:id
func deleteUser(w http.ResponseWriter, r *http.Request) { /* ... */ }
```

## Concurrency Patterns

### 1. Pipeline Pattern

Pipelines process data through multiple stages connected by channels.

**Generator Stage:**

```go
func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}
```

**Processing Stage:**

```go
func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}
```

**Pipeline Usage:**

```go
func main() {
    // Set up pipeline
    c := gen(2, 3)
    out := sq(c)

    // Consume output
    for n := range out {
        fmt.Println(n)  // 4 then 9
    }
}
```

**Buffered Generator (No Goroutine Needed):**

```go
func gen(nums ...int) <-chan int {
    out := make(chan int, len(nums))
    for _, n := range nums {
        out <- n
    }
    close(out)
    return out
}
```

### 2. Fan-Out/Fan-In Pattern

Distribute work across multiple workers and merge results.

**Fan-Out: Multiple Workers:**

```go
func main() {
    in := gen(2, 3, 4, 5)

    // Fan out: distribute work across two goroutines
    c1 := sq(in)
    c2 := sq(in)

    // Fan in: merge results
    for n := range merge(c1, c2) {
        fmt.Println(n)
    }
}
```

**Merge Function (Fan-In):**

```go
func merge(cs ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)

    // Start output goroutine for each input channel
    output := func(c <-chan int) {
        for n := range c {
            out <- n
        }
        wg.Done()
    }

    wg.Add(len(cs))
    for _, c := range cs {
        go output(c)
    }

    // Close out once all outputs are done
    go func() {
        wg.Wait()
        close(out)
    }()
    return out
}
```

### 3. Explicit Cancellation Pattern

**Cancellation with Done Channel:**

```go
func sq(done <-chan struct{}, in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            select {
            case out <- n * n:
            case <-done:
                return
            }
        }
    }()
    return out
}
```

**Broadcasting Cancellation:**

```go
func main() {
    done := make(chan struct{})
    defer close(done)  // Broadcast to all goroutines

    in := gen(done, 2, 3, 4)
    c1 := sq(done, in)
    c2 := sq(done, in)

    // Process subset of results
    out := merge(done, c1, c2)
    fmt.Println(<-out)

    // done closed on return, canceling all pipeline stages
}
```

**Merge with Cancellation:**

```go
func merge(done <-chan struct{}, cs ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)

    output := func(c <-chan int) {
        defer wg.Done()
        for n := range c {
            select {
            case out <- n:
            case <-done:
                return
            }
        }
    }

    wg.Add(len(cs))
    for _, c := range cs {
        go output(c)
    }

    go func() {
        wg.Wait()
        close(out)
    }()
    return out
}
```

### 4. Worker Pool Pattern

**Fixed Number of Workers:**

```go
func handle(queue chan *Request) {
    for r := range queue {
        process(r)
    }
}

func Serve(clientRequests chan *Request, quit chan bool) {
    // Start handlers
    for i := 0; i < MaxOutstanding; i++ {
        go handle(clientRequests)
    }
    <-quit  // Wait to exit
}
```

**Semaphore Pattern:**

```go
var sem = make(chan int, MaxOutstanding)

func handle(r *Request) {
    sem <- 1        // Acquire
    process(r)
    <-sem           // Release
}

func Serve(queue chan *Request) {
    for req := range queue {
        go handle(req)
    }
}
```

**Limiting Goroutine Creation:**

```go
func Serve(queue chan *Request) {
    for req := range queue {
        sem <- 1  // Acquire before creating goroutine
        go func() {
            process(req)
            <-sem  // Release
        }()
    }
}
```

### 5. Query Racing Pattern

Query multiple sources and return first result:

```go
func Query(conns []Conn, query string) Result {
    ch := make(chan Result)
    for _, conn := range conns {
        go func(c Conn) {
            select {
            case ch <- c.DoQuery(query):
            default:
            }
        }(conn)
    }
    return <-ch
}
```

### 6. Parallel Processing Example

**Serial MD5 Calculation:**

```go
func MD5All(root string) (map[string][md5.Size]byte, error) {
    m := make(map[string][md5.Size]byte)
    err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        if !info.Mode().IsRegular() {
            return nil
        }
        data, err := ioutil.ReadFile(path)
        if err != nil {
            return err
        }
        m[path] = md5.Sum(data)
        return nil
    })
    return m, err
}
```

**Parallel MD5 with Pipeline:**

```go
type result struct {
    path string
    sum  [md5.Size]byte
    err  error
}

func sumFiles(done <-chan struct{}, root string) (<-chan result, <-chan error) {
    c := make(chan result)
    errc := make(chan error, 1)

    go func() {
        defer close(c)
        err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
            if err != nil {
                return err
            }
            if !info.Mode().IsRegular() {
                return nil
            }

            // Start goroutine for each file
            go func() {
                data, err := ioutil.ReadFile(path)
                select {
                case c <- result{path, md5.Sum(data), err}:
                case <-done:
                }
            }()

            // Check for early cancellation
            select {
            case <-done:
                return errors.New("walk canceled")
            default:
                return nil
            }
        })

        select {
        case errc <- err:
        case <-done:
        }
    }()
    return c, errc
}

func MD5All(root string) (map[string][md5.Size]byte, error) {
    done := make(chan struct{})
    defer close(done)

    c, errc := sumFiles(done, root)

    m := make(map[string][md5.Size]byte)
    for r := range c {
        if r.err != nil {
            return nil, r.err
        }
        m[r.path] = r.sum
    }

    if err := <-errc; err != nil {
        return nil, err
    }
    return m, nil
}
```

### 7. Leaky Buffer Pattern

Efficient buffer reuse:

```go
var freeList = make(chan *Buffer, 100)

func server() {
    for {
        b := <-serverChan  // Wait for work
        process(b)

        // Try to reuse buffer
        select {
        case freeList <- b:
            // Buffer on free list
        default:
            // Free list full, GC will reclaim
        }
    }
}
```

## Database Integration

### Connection Management

**Database Connection Pool:**

```go
import "database/sql"

func initDB(dataSourceName string) (*sql.DB, error) {
    db, err := sql.Open("postgres", dataSourceName)
    if err != nil {
        return nil, err
    }

    // Configure connection pool
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(5 * time.Minute)
    db.SetConnMaxIdleTime(10 * time.Minute)

    // Verify connection
    if err := db.Ping(); err != nil {
        return nil, err
    }

    return db, nil
}
```

### Query Patterns

**Single Row Query:**

```go
func getUser(db *sql.DB, userID int) (*User, error) {
    user := &User{}
    err := db.QueryRow(
        "SELECT id, name, email FROM users WHERE id = $1",
        userID,
    ).Scan(&user.ID, &user.Name, &user.Email)

    if err == sql.ErrNoRows {
        return nil, fmt.Errorf("user not found")
    }
    if err != nil {
        return nil, err
    }

    return user, nil
}
```

**Multiple Row Query:**

```go
func listUsers(db *sql.DB) ([]*User, error) {
    rows, err := db.Query("SELECT id, name, email FROM users")
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var users []*User
    for rows.Next() {
        user := &User{}
        if err := rows.Scan(&user.ID, &user.Name, &user.Email); err != nil {
            return nil, err
        }
        users = append(users, user)
    }

    if err := rows.Err(); err != nil {
        return nil, err
    }

    return users, nil
}
```

**Insert/Update with Context:**

```go
func createUser(ctx context.Context, db *sql.DB, user *User) error {
    query := "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id"
    err := db.QueryRowContext(ctx, query, user.Name, user.Email).Scan(&user.ID)
    return err
}
```

### Transaction Handling

```go
func transferFunds(ctx context.Context, db *sql.DB, from, to int, amount decimal.Decimal) error {
    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    defer tx.Rollback()  // Rollback if not committed

    // Debit from account
    _, err = tx.ExecContext(ctx,
        "UPDATE accounts SET balance = balance - $1 WHERE id = $2",
        amount, from)
    if err != nil {
        return err
    }

    // Credit to account
    _, err = tx.ExecContext(ctx,
        "UPDATE accounts SET balance = balance + $1 WHERE id = $2",
        amount, to)
    if err != nil {
        return err
    }

    return tx.Commit()
}
```

### Prepared Statements

```go
func insertUsers(db *sql.DB, users []*User) error {
    stmt, err := db.Prepare("INSERT INTO users (name, email) VALUES ($1, $2)")
    if err != nil {
        return err
    }
    defer stmt.Close()

    for _, user := range users {
        _, err := stmt.Exec(user.Name, user.Email)
        if err != nil {
            return err
        }
    }

    return nil
}
```

## Error Handling

### Custom Error Types

```go
type ValidationError struct {
    Field string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Message)
}

// Usage
if email == "" {
    return &ValidationError{Field: "email", Message: "required"}
}
```

### Error Wrapping

```go
import "fmt"

func processData(data []byte) error {
    err := validateData(data)
    if err != nil {
        return fmt.Errorf("process data: %w", err)
    }
    return nil
}

// Unwrapping
if errors.Is(err, ErrValidation) {
    // Handle validation error
}

if errors.As(err, &validationErr) {
    // Access ValidationError fields
}
```

### Sentinel Errors

```go
var (
    ErrNotFound = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrInvalidInput = errors.New("invalid input")
)

// Usage
if errors.Is(err, ErrNotFound) {
    http.Error(w, "Resource not found", http.StatusNotFound)
}
```

## Testing

### Unit Tests

```go
func TestGetUser(t *testing.T) {
    db := setupTestDB(t)
    defer db.Close()

    user, err := getUser(db, 1)
    if err != nil {
        t.Fatalf("getUser failed: %v", err)
    }

    if user.Name != "John Doe" {
        t.Errorf("expected name John Doe, got %s", user.Name)
    }
}
```

### Table-Driven Tests

```go
func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        wantErr bool
    }{
        {"valid email", "user@example.com", false},
        {"missing @", "userexample.com", true},
        {"empty string", "", true},
        {"missing domain", "user@", true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := validateEmail(tt.email)
            if (err != nil) != tt.wantErr {
                t.Errorf("validateEmail(%q) error = %v, wantErr %v",
                    tt.email, err, tt.wantErr)
            }
        })
    }
}
```

### Benchmarks

```go
func BenchmarkConcurrentMap(b *testing.B) {
    m := make(map[string]int)
    var mu sync.Mutex

    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            mu.Lock()
            m["key"]++
            mu.Unlock()
        }
    })
}
```

### HTTP Handler Testing

```go
func TestHandler(t *testing.T) {
    req := httptest.NewRequest("GET", "/api/users", nil)
    w := httptest.NewRecorder()

    handler(w, req)

    resp := w.Result()
    if resp.StatusCode != http.StatusOK {
        t.Errorf("expected status 200, got %d", resp.StatusCode)
    }

    body, _ := ioutil.ReadAll(resp.Body)
    // Assert body content
}
```

## Production Patterns

### Graceful Shutdown

```go
func main() {
    srv := &http.Server{
        Addr:    ":8080",
        Handler: router,
    }

    // Start server in goroutine
    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()

    // Wait for interrupt signal
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("Shutting down server...")

    // Graceful shutdown with timeout
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("Server forced to shutdown:", err)
    }

    log.Println("Server exited")
}
```

### Configuration Management

```go
type Config struct {
    ServerPort  int           `env:"PORT" envDefault:"8080"`
    DBHost      string        `env:"DB_HOST" envDefault:"localhost"`
    DBPort      int           `env:"DB_PORT" envDefault:"5432"`
    LogLevel    string        `env:"LOG_LEVEL" envDefault:"info"`
    Timeout     time.Duration `env:"TIMEOUT" envDefault:"30s"`
}

func loadConfig() (*Config, error) {
    cfg := &Config{}
    if err := env.Parse(cfg); err != nil {
        return nil, err
    }
    return cfg, nil
}
```

### Structured Logging

```go
import "log/slog"

func setupLogger() *slog.Logger {
    return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelInfo,
    }))
}

func handler(w http.ResponseWriter, r *http.Request) {
    logger := slog.With(
        "method", r.Method,
        "path", r.URL.Path,
        "remote", r.RemoteAddr,
    )

    logger.Info("handling request")

    // Process request

    logger.Info("request completed", "status", 200)
}
```

### Health Checks

```go
func healthHandler(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Check database
        if err := db.Ping(); err != nil {
            w.WriteHeader(http.StatusServiceUnavailable)
            json.NewEncoder(w).Encode(map[string]string{
                "status": "unhealthy",
                "error": err.Error(),
            })
            return
        }

        // Check other dependencies...

        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(map[string]string{
            "status": "healthy",
        })
    }
}
```

### Rate Limiting

```go
import "golang.org/x/time/rate"

func rateLimitMiddleware(limiter *rate.Limiter) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if !limiter.Allow() {
                http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

// Usage
limiter := rate.NewLimiter(rate.Limit(10), 20)  // 10 req/sec, burst 20
handler := rateLimitMiddleware(limiter)(apiHandler)
```

### Panic Recovery

```go
func safelyDo(work *Work) {
    defer func() {
        if err := recover(); err != nil {
            log.Println("work failed:", err)
        }
    }()
    do(work)
}

func server(workChan <-chan *Work) {
    for work := range workChan {
        go safelyDo(work)
    }
}
```

## Microservices Patterns

### Service Structure

```go
type UserService struct {
    db     *sql.DB
    cache  *redis.Client
    logger *slog.Logger
}

func NewUserService(db *sql.DB, cache *redis.Client, logger *slog.Logger) *UserService {
    return &UserService{
        db:     db,
        cache:  cache,
        logger: logger,
    }
}

func (s *UserService) GetUser(ctx context.Context, userID string) (*User, error) {
    // Check cache first
    if user, err := s.getFromCache(ctx, userID); err == nil {
        return user, nil
    }

    // Query database
    user, err := s.getFromDB(ctx, userID)
    if err != nil {
        return nil, err
    }

    // Update cache
    go s.updateCache(context.Background(), user)

    return user, nil
}
```

### gRPC Service

```go
type server struct {
    pb.UnimplementedUserServiceServer
    db *sql.DB
}

func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    user := &pb.User{}
    err := s.db.QueryRowContext(ctx,
        "SELECT id, name, email FROM users WHERE id = $1",
        req.GetId(),
    ).Scan(&user.Id, &user.Name, &user.Email)

    if err != nil {
        return nil, status.Errorf(codes.NotFound, "user not found")
    }

    return user, nil
}
```

### Service Discovery

```go
type ServiceRegistry struct {
    services map[string][]string
    mu       sync.RWMutex
}

func (r *ServiceRegistry) Register(name, addr string) {
    r.mu.Lock()
    defer r.mu.Unlock()
    r.services[name] = append(r.services[name], addr)
}

func (r *ServiceRegistry) Discover(name string) (string, error) {
    r.mu.RLock()
    defer r.mu.RUnlock()

    addrs := r.services[name]
    if len(addrs) == 0 {
        return "", fmt.Errorf("service %s not found", name)
    }

    // Simple round-robin
    return addrs[rand.Intn(len(addrs))], nil
}
```

### Circuit Breaker

```go
type CircuitBreaker struct {
    maxFailures int
    timeout     time.Duration
    failures    int
    lastFailure time.Time
    state       string  // closed, open, half-open
    mu          sync.Mutex
}

func (cb *CircuitBreaker) Call(fn func() error) error {
    cb.mu.Lock()

    if cb.state == "open" {
        if time.Since(cb.lastFailure) > cb.timeout {
            cb.state = "half-open"
        } else {
            cb.mu.Unlock()
            return errors.New("circuit breaker open")
        }
    }

    cb.mu.Unlock()

    err := fn()

    cb.mu.Lock()
    defer cb.mu.Unlock()

    if err != nil {
        cb.failures++
        cb.lastFailure = time.Now()
        if cb.failures >= cb.maxFailures {
            cb.state = "open"
        }
        return err
    }

    cb.failures = 0
    cb.state = "closed"
    return nil
}
```

## Best Practices

### 1. Goroutine Management

- Always consider goroutine lifecycle and cleanup
- Use contexts for cancellation propagation
- Avoid goroutine leaks by ensuring all goroutines can exit
- Be cautious with closures in loops - pass values explicitly

**Anti-pattern:**
```go
for _, v := range values {
    go func() {
        fmt.Println(v)  // All goroutines share same v
    }()
}
```

**Correct:**
```go
for _, v := range values {
    go func(val string) {
        fmt.Println(val)  // Each goroutine gets its own copy
    }(v)
}
```

### 2. Channel Best Practices

- Close channels from sender, not receiver
- Use buffered channels to prevent goroutine leaks
- Consider using `select` with `default` for non-blocking operations
- Remember: sending on closed channel panics, receiving returns zero value

### 3. Error Handling

- Return errors, don't panic (except for truly exceptional cases)
- Wrap errors with context using `fmt.Errorf("%w", err)`
- Use custom error types for programmatic handling
- Log errors with sufficient context

### 4. Performance

- Use `sync.Pool` for frequently allocated objects
- Profile before optimizing: `go test -bench . -cpuprofile=cpu.prof`
- Consider `sync.Map` for concurrent map access patterns
- Use buffered channels for known capacity
- Avoid unnecessary allocations in hot paths

### 5. Code Organization

```
project/
├── cmd/
│   └── server/
│       └── main.go          # Application entry point
├── internal/
│   ├── api/                 # HTTP handlers
│   ├── service/             # Business logic
│   ├── repository/          # Data access
│   └── middleware/          # HTTP middleware
├── pkg/
│   └── utils/               # Public utilities
├── migrations/              # Database migrations
├── config/                  # Configuration files
└── docker/                  # Docker files
```

### 6. Security

- Validate all inputs
- Use prepared statements for SQL queries
- Implement rate limiting
- Use HTTPS in production
- Sanitize error messages sent to clients
- Use context timeouts to prevent resource exhaustion
- Implement proper authentication and authorization

### 7. Testing

- Write table-driven tests
- Use `t.Helper()` for test helper functions
- Mock external dependencies
- Use `httptest` for HTTP handler testing
- Write benchmarks for performance-critical code
- Aim for >80% test coverage on business logic

## Common Pitfalls

### 1. Race Conditions

**Problem:**
```go
var service map[string]net.Addr

func RegisterService(name string, addr net.Addr) {
    service[name] = addr  // RACE CONDITION
}

func LookupService(name string) net.Addr {
    return service[name]  // RACE CONDITION
}
```

**Solution:**
```go
var (
    service   map[string]net.Addr
    serviceMu sync.Mutex
)

func RegisterService(name string, addr net.Addr) {
    serviceMu.Lock()
    defer serviceMu.Unlock()
    service[name] = addr
}
```

### 2. Goroutine Leaks

**Problem:**
```go
func process() {
    ch := make(chan int)
    go func() {
        ch <- expensive()  // Blocks forever if no receiver
    }()
    // Returns without reading from ch
}
```

**Solution:**
```go
func process() {
    ch := make(chan int, 1)  // Buffered channel
    go func() {
        ch <- expensive()  // Won't block
    }()
}
```

### 3. Not Closing Channels

Receivers need to know when no more values are coming:

```go
func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)  // IMPORTANT: close when done
    }()
    return out
}
```

### 4. Blocking on Unbuffered Channels

```go
// This will deadlock
ch := make(chan int)
ch <- 1  // Blocks forever - no receiver
v := <-ch
```

Use buffered channels or separate goroutines.

### 5. Unsynchronized Channel Operations

```go
c := make(chan struct{})

// RACE CONDITION
go func() { c <- struct{}{} }()
close(c)
```

Ensure happens-before relationship with proper synchronization.

## Resources and References

### Official Documentation
- Go Documentation: https://go.dev/doc/
- Effective Go: https://go.dev/doc/effective_go
- Go Blog: https://go.dev/blog/
- Go by Example: https://gobyexample.com/

### Concurrency Resources
- Go Concurrency Patterns: https://go.dev/blog/pipelines
- Context Package: https://go.dev/blog/context
- Share Memory By Communicating: https://go.dev/blog/codelab-share

### Standard Library
- net/http: https://pkg.go.dev/net/http
- database/sql: https://pkg.go.dev/database/sql
- context: https://pkg.go.dev/context
- sync: https://pkg.go.dev/sync

### Tools
- Race Detector: `go test -race`
- Profiler: `go tool pprof`
- Benchmarking: `go test -bench`
- Static Analysis: `go vet`, `staticcheck`

---

**Skill Version**: 1.0.0
**Last Updated**: October 2025
**Skill Category**: Backend Development, Systems Programming, Concurrent Programming
**Prerequisites**: Basic programming knowledge, understanding of HTTP, familiarity with command line
**Recommended Next Skills**: docker-deployment, kubernetes-orchestration, grpc-microservices

Install

Requires askill CLI v1.0+

Metadata

LicenseUnknown
Version-
Updated1w ago
Publishermanutej

Tags

apici-cddatabasedockergojavajavascriptkuberneteslangchainllmmlobservabilitypostgrespythonreactredisrustsecuritysvelteterraformtestingvue