Engineering Blog

                            

Optional Function Parameter Pattern

Go doesn’t support optional function parameters. However, the need for optional parameters will always exist. There are many ways to provide optional parameters to a function in Go, but the most graceful way is to use functional options.

Do in this blog we will go through a concrete example and covers different ways to handle optional configurations.

For this senario, let’s say we have to design a library that exposes a function to create an HTTP server. So, this function may accept different inputs: let’s say host and port. The following shows the skeleton of the function:

func NewServer(host string, port int) (*http.Server, error) {
// ...
}

And let’s consider client has started using our library. After sometime client complains about limited functionality and ask to add some extra parameters. In this senario, adding new function parameters breaks the compatibility, forcing the clients to modify the way they call NewServer.

Hence, to ovecome this problem we have functional options pattern.

Functional Option Pattern

A better alternative to this options configuration problem is exaclty the functional options design pattern. we may have seen or heard the functional options pattern in Go projects before but in this example we are going to breakdown the structure and the characteristics of it in detail.


type Server {
  host string
  port int
  timeout time.Duration
  maxConn int
}

func New(options ...func(*Server)) *Server {
  svr := &Server{}
  for _, o := range options {
    o(svr)
  }
  return svr
}

func (s *Server) Start() error {
  // todo
}

func WithHost(host string) func(*Server) {
  return func(s *Server) {
    s.host = host
  }
}

func WithPort(port int) func(*Server) {
  return func(s *Server) {
    s.port = port
  }
}

func WithTimeout(timeout time.Duration) func(*Server) {
  return func(s *Server) {
    s.timeout = timeout
  }
}

func WithMaxConn(maxConn int) func(*Server) {
  return func(s *Server) {
    s.maxConn = maxConn
  }
}

And the relative client implementation below using the new functional option pattern

func main() {
  svr := server.New(
    server.WithHost("localhost"),
    server.WithPort(8080),
    server.WithTimeout(time.Minute),
    server.WithMaxConn(120),
  )
  if err := svr.Start(); err != nil {
    log.Fatal(err)
  }
}

The functional options pattern allows us to define a fixed type signature for each and any possible configuration of our server, buy using the func(*Server) type signature we can create any option to be passed to the server. Our options are also optional by default, so it’s easy to swap any options without any major problem. 

Previous Post
Next Post