# 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]]