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:
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.
The full list of my published Software
Made with PubTal 3.5
Copyright 2021 Colin StewartEmail: colin at owlfish.com