DEV Community

Kittipat.po
Kittipat.po

Posted on • Updated on

Understanding the Options Pattern in Go

Image description

Options Pattern

The Options Pattern is a powerful and flexible design pattern commonly used in Go to configure and customize the behavior of a struct or function. This pattern is especially useful when you have a struct with numerous configuration parameters, and you want to provide a clean and readable way for users to set these parameters without cluttering the code with multiple constructor or function parameters.

Let's dive into an example of how to implement the Options Pattern in Go:

Suppose we want to create a simple HTTP client that can be configured with various options like timeout, user agent, and whether to follow redirects. Instead of defining separate constructors or functions for each combination of options, we can use the Options Pattern to provide a cleaner and more flexible API.

package httpclient

import (
    "net/http"
    "time"
)

// Client represents our HTTP client.
type Client struct {
    client       *http.Client
    timeout      time.Duration
    userAgent    string
    followRedirects bool
}

// Option is a functional option type that allows us to configure the Client.
type Option func(*Client)

// NewClient creates a new HTTP client with default options.
func NewClient(options ...Option) *Client {
    client := &Client{
        client:       &http.Client{},
        timeout:      30 * time.Second, // Default timeout
        userAgent:    "My HTTP Client", // Default user agent
        followRedirects: true,         // Default follows redirects
    }

    // Apply all the functional options to configure the client.
    for _, opt := range options {
        opt(client)
    }

    return client
}

// WithTimeout is a functional option to set the HTTP client timeout.
func WithTimeout(timeout time.Duration) Option {
    return func(c *Client) {
        c.timeout = timeout
    }
}

// WithUserAgent is a functional option to set the HTTP client user agent.
func WithUserAgent(userAgent string) Option {
    return func(c *Client) {
        c.userAgent = userAgent
    }
}

// WithoutRedirects is a functional option to disable following redirects.
func WithoutRedirects() Option {
    return func(c *Client) {
        c.followRedirects = false
    }
}

// UseInsecureTransport is a functional option to use an insecure HTTP transport.
func UseInsecureTransport() Option {
    return func(c *Client) {
        c.client.Transport = &http.Transport{
            TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
        }
    }
}

// Get performs an HTTP GET request.
func (c *Client) Get(url string) (*http.Response, error) {
    // Use c.client with all the configured options to perform the request.
    // ...
}

// Example usage:
func main() {
    // Create a new HTTP client with custom options.
    client := NewClient(
        WithTimeout(10 * time.Second),
        WithUserAgent("My Custom User Agent"),
        UseInsecureTransport(),
    )

    // Use the client to make HTTP requests.
    response, err := client.Get("https://api.example.com/data")
    if err != nil {
        // Handle the error
    }
    defer response.Body.Close()

    // Process the response.
    // ...
}
Enter fullscreen mode Exit fullscreen mode

In this example, we have a Client struct that represents our HTTP client with its default options. We define a set of functional options (Option type) that modify the Client configuration. These options are then passed to the NewClient function, which applies them accordingly.

This approach allows users of the Client to easily customize its behavior without needing to specify all the parameters in the constructor. Additionally, it keeps the codebase clean, extensible, and readable.

Conclusion 🥂

By using the Options Pattern, you can create more flexible and maintainable APIs for your Go packages and provide users with a convenient way to configure your structs or functions. As your project grows, you can easily add more options without changing the existing API, making it a great pattern for creating reusable and modular code.

Top comments (2)

Collapse
 
anandsunderraman profile image
anandsunderraman

I always wondered about this pattern in go.
Thank you for writing about it

Collapse
 
hoangnguyen679 profile image
HoangNguyen679

You set timeout for client but not http client.
It seems meaningless ?