Dependency Injection

Using closures to hold dependencies

When building a set of REST APIs in Go, it's common to have a function per end point. These functions usually require a set of services, such as storage libraries, databases or client connections to other micro services. When I first started in Go I would put all of these dependencies into a single Server struct and add methods for each end point. The methods benefit from dependency injection as they don't initialise the values in the struct, but each method has access to the super set of all services. The only way to know what services a method depends on is to read the whole of the method and see what properties of the struct it accesses.

An alternative approach is to use closures and have the end point functions be returned by a function that takes it's dependencies as arguments. The struct approach such as:

type server struct {
    site    *siteInfo
    message *messages
}

func (s *server) GetSite(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(s.site.GetSiteName()))
}

func (s *server) GetMessage(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(s.message.GetMessage()))
}

Becomes a more loosely coupled function driven:

func SitePublisher(site *siteInfo) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(site.GetSiteName()))
    }
}

func MessagePublisher(message *messages) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(message.GetMessage()))
    }
}

Using closures like this brings a number of advantages:

  1. It is easier to reuse a function and publish it on multiple end points. You can reuse the exact same function but with different dependencies injected into it.
  2. There's immediate clarity as to what services / resources an end point requires. In this example it's clear that SitePublisher requires a siteInfo object, but doesn't use the messages object.
  3. Refactoring to use different services becomes easier as it's immediately clear as to which end points are using which services.

    Although it's possible to combine these approaches (having methods on a struct with common services return functions), I prefer to stick to one approach or the other.

Last Modified: Sat, 28 Jan 2017 16:06:45 CET

Made with PubTal 3.5

Copyright 2021 Colin Stewart

Email: colin at owlfish.com