# Functional Options Pattern in Go ## Summary The Functional Options Pattern is a powerful and flexible design pattern in Go for configuring structs and objects. It uses a series of functional setters to provide a clean, readable, and extensible API for object configuration. This pattern is particularly useful when a struct has numerous configuration parameters, avoiding the need for multiple constructors or complex configuration objects. Key benefits: - Improves code readability and maintainability - Allows for optional parameters with sensible defaults - Makes APIs more extensible without breaking changes - Enhances encapsulation of configuration logic - Enables compile-time validation of option types ## Core Concepts ### 1. Basic Structure The pattern consists of three main components: 1. **Target Struct**: The object being configured 2. **Option Type**: A function type that modifies the target struct 3. **Option Providers**: Functions that return options for specific configurations ### 2. Implementation Pattern ```go // 1. Define an Option type type Option func(*TargetStruct) error // 2. Create a constructor that accepts variadic options func New(options ...Option) (*TargetStruct, error) { // Create instance with defaults t := &TargetStruct{ // Default values } // Apply each option for _, option := range options { if err := option(t); err != nil { return nil, err } } return t, nil } // 3. Define option providers func WithSomething(value SomeType) Option { return func(t *TargetStruct) error { // Optionally validate // Modify the target t.something = value return nil } } ``` ## Implementation Examples ### Server Configuration Example ```go package server type Server struct { host string port int timeout time.Duration maxConn int } // Option type for server configuration type Option func(*Server) error // Constructor with functional options func New(options ...Option) (*Server, error) { // Default configuration server := &Server{ host: "localhost", port: 8080, timeout: 30 * time.Second, maxConn: 100, } // Apply all options for _, option := range options { if err := option(server); err != nil { return nil, err } } return server, nil } // Option providers func WithHost(host string) Option { return func(s *Server) error { s.host = host return nil } } func WithPort(port int) Option { return func(s *Server) error { if port < 1 || port > 65535 { return fmt.Errorf("port %d is out of valid range (1-65535)", port) } s.port = port return nil } } func WithTimeout(timeout time.Duration) Option { return func(s *Server) error { s.timeout = timeout return nil } } func WithMaxConn(maxConn int) Option { return func(s *Server) error { s.maxConn = maxConn return nil } } ``` ### Client Usage Example ```go func main() { // Create with defaults server1, err := server.New() if err != nil { log.Fatal(err) } // Create with custom options server2, err := server.New( server.WithHost("api.example.com"), server.WithPort(9000), server.WithTimeout(1 * time.Minute), ) if err != nil { log.Fatal(err) } // Start the servers server1.Start() server2.Start() } ``` ## Advanced Techniques ### 1. Validating Options The functional options pattern allows for validation at the point of option application: ```go func WithPort(port int) Option { return func(s *Server) error { if port < 1 || port > 65535 { return fmt.Errorf("port %d is out of valid range (1-65535)", port) } s.port = port return nil } } ``` ### 2. Dependent Options Options can be designed to work together: ```go func WithTLSConfig(cert, key string) Option { return func(s *Server) error { config, err := loadTLSConfig(cert, key) if err != nil { return err } s.tlsConfig = config s.useTLS = true return nil } } ``` ### 3. Composing Options Multiple options can be grouped together: ```go func WithSecureDefaults() Option { return func(s *Server) error { // Apply multiple changes in one option s.useTLS = true s.forceHTTPS = true s.hsts = true return nil } } ``` ## Comparison with Alternative Approaches ### 1. Multiple Constructors ```go // Not recommended - leads to constructor explosion func New(host string, port int) *Server { /* ... */ } func NewWithTimeout(host string, port int, timeout time.Duration) *Server { /* ... */ } func NewWithTimeoutAndMaxConn(host string, port int, timeout time.Duration, maxConn int) *Server { /* ... */ } ``` **Drawbacks**: - Explodes with the number of parameters - Hard to maintain as options increase - No flexibility in choosing which options to set ### 2. Config Struct ```go type Config struct { Host string Port int Timeout time.Duration MaxConn int } func New(cfg Config) *Server { /* ... */ } ``` **Drawbacks**: - Requires creating a separate struct - All fields exposed even if internal - Changing the struct is a breaking change - Less self-documenting than functional options ## Best Practices ### Naming Conventions - Prefix option providers with `With` (e.g., `WithTimeout`) - Use clear, descriptive names for options - For boolean toggles, consider `WithX` and `WithoutX` pairs ### Error Handling - Return errors from options for validation failures - Check errors when applying options - Provide clear error messages indicating what failed ### Default Values - Set sensible defaults in the constructor - Document default values - Make defaults obvious in the code ### Extensibility - Design for future expansion - Consider backward compatibility when adding options - Keep the option signature stable ## Common Pitfalls ### 1. Ignoring Error Handling Not checking errors from option functions can lead to partially configured objects. ### 2. Overly Complex Options Keeping options simple and focused on one aspect of configuration improves clarity. ### 3. Inconsistent Naming Following a consistent naming convention makes APIs more intuitive. ### 4. Missing Documentation Document each option's purpose, default behavior, and any validation rules. ## 🔗 Related Resources - [Golang Cafe: Functional Options Pattern](https://golang.cafe/blog/golang-functional-options-pattern.html) - [Dave Cheney: Functional Options for Friendly APIs](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis) - [[Go Design Patterns]] - [[Error Handling in Go]]