Creational Created: 2024-01-27 Updated: 2024-01-27

Config Struct

A pattern for grouping related configuration options into a struct for cleaner function signatures.

Contributors

When to Use

  • When a function has more than 3-4 related parameters
  • When configuration is often passed around together
  • When you need clear documentation of all options

When NOT to Use

  • For functions with few parameters
  • When options are mostly independent
  • When extensibility is more important than explicitness (use functional options)

The Config Struct pattern groups related configuration into a struct, making function signatures cleaner and configuration more explicit. It’s simpler than functional options and works well when all configuration is known upfront.

Implementation

package client

import (
    "net/http"
    "time"
)

type Config struct {
    BaseURL    string
    Timeout    time.Duration
    MaxRetries int
    Headers    map[string]string
    HTTPClient *http.Client
}

// DefaultConfig returns sensible defaults
func DefaultConfig() Config {
    return Config{
        Timeout:    30 * time.Second,
        MaxRetries: 3,
        Headers:    make(map[string]string),
        HTTPClient: http.DefaultClient,
    }
}

type Client struct {
    config Config
}

func New(cfg Config) *Client {
    // Apply defaults for zero values if needed
    if cfg.Timeout == 0 {
        cfg.Timeout = 30 * time.Second
    }
    if cfg.HTTPClient == nil {
        cfg.HTTPClient = http.DefaultClient
    }

    return &Client{config: cfg}
}

func (c *Client) Get(path string) (*http.Response, error) {
    // Use config values
    req, err := http.NewRequest("GET", c.config.BaseURL+path, nil)
    if err != nil {
        return nil, err
    }

    for k, v := range c.config.Headers {
        req.Header.Set(k, v)
    }

    return c.config.HTTPClient.Do(req)
}

Usage

package main

func main() {
    // Start with defaults, override what you need
    cfg := DefaultConfig()
    cfg.BaseURL = "https://api.example.com"
    cfg.Timeout = 10 * time.Second
    cfg.Headers["Authorization"] = "Bearer token"

    client := New(cfg)

    // Or construct inline
    client := New(Config{
        BaseURL:    "https://api.example.com",
        Timeout:    10 * time.Second,
        MaxRetries: 5,
    })

    resp, err := client.Get("/users")
    // ...
}

Benefits

  • Self-documenting: All options are visible in the struct definition
  • IDE friendly: Autocomplete shows available options
  • Easy to validate: All configuration is available in one place
  • Testable: Easy to create test configurations
  • JSON/YAML friendly: Config structs can be unmarshaled from files