Why Go needs a dependency management tool

I came to the Go world from Symfony. Both things are amazing: if you are a “Symfony guy”, it’s like possessing limitless power in creating web apps of any complexity from small personal sites to a corporate software.

If you are a “gopher”, you are also on a good side: multiple return arguments, low learning curve, errors as values, strictly typed collections, strait-forward coding rules without misinterpretation, async garbage collector, easy and error protected concurrency. In short is true spirit of Google to write fast, business oriented, pragmatic readable logic – who can resist?

I fell in love with Go but I miss so many things of Symfony, especially it’s amazing dependency injection container

As a code purist I feel pain seeing the boilerplate code here and there. As a pragmatic thinker I consider following motivations for a missing Go library:

Strictly controllable, reusable, unavoidable constructors
I came to the conclusion that structs holding the logic should not be created in an uncontrollable way, e.g.:

package user

type RegisteredUserProvider struct {
   sessionStorage *Cache
}

func (rup *RegisteredUserProvider) GetUserName(token string) string {
   return rup.sessionStorage.Read(token)
}
//oh no, it is not initialised correctly and will fail at runtime!
registeredUser := &user.RegisteredUserProvider{}.GetUserName("someToken")

We can do it in init:

var registeredUserProvider *RegisteredUserProvider

func init() {
   sessionCache := &Cache{}
   registeredUserProvider = &RegisteredUserProvider{sessionCache}
}

Yeah looks almost perfect but:

  • I repeat cache initialisation, if the Cache is also initialised in a hard way, I do code repetitions.
  • I can put Cache constructor into an init method of it’s own package, but then it cannot be sure if Cache is initialised first. Imagine having 100 dependencies with very complex relations, where of course order of initialisation matters very much. So no.
  • It’s still boilerplate distributed among multiple packages
  • If I want in some cases not to trigger some initialisations, init functions stand on the way
  • I want to have clear control over reusability of the services, using package globals can lead to dependencies nightmare from the previous argument

There is another alternative:

registeredUser := user.NewUserProvider().GetUserName("someToken")

Yeah it works. But how do I know, what method to call, to get my user provider? Where to put declaration for NewUserProvider? What makes me do it for every “logical” struct?

I can imagine a code like this:

var userProvider RegisteredUserProvider
err = cont.Scan("userProvider", &userProvider)

Answering the questions from above:

But how do I know, what method to call, to get my user provider?

You always call the same method to get an initialised struct. Every single “logical” struct should be created with some centralised code.

Where to put NewUserProvider?

All creational logic is declared in the single place of responsibility, where you declare all container services.

What makes me do it for every “logical” struct?

New developers are always looking at the code to grasp the idea of culture and conventions. If they see that sometimes structs are directly initialised, sometimes created by different methods, they might think there is no convention about it.

If a dependency injection library is used, the creational logic is everywhere the same. It’s stated as a strict rule and all other ways of initialising structs are explicitly visible during the code review!

Clear reusable internal states management

Internal states are mainly reusable resources, such as connections, opened files, structs with input parameters etc. In many cases I want not only to get an initialised struct, but also keep reusing its internal state no matter how many times I call it.
The usual way to achieve this are globals:

package user
var conn *MysqlConn

func init() {
   conn = mysql.Open("conn_string")
}

type UserProvider struct{}
   func (up *UserProvider) GetUserName(userId int) string {
   return conn.Find("select name from user where id = ?", userId)
}

Looks almost ok and no initialisation is needed! But wait, what if in some cases I don’t want to open connection as I never call GetUserName and never use UserProvider? What if some parts of user package are not db related?

Ok fixing:

package user

type UserProvider struct{conn *MysqlConn}

func NewUserProvider() *UserProvider {
    return &UserProvider{conn: mysql.Open("conn_string")}
}

Ok we open connection only when calling NewUserProvider. But of course conn should be reused for different queries, so fixing:

package db

var conn *MysqlConn

func NewMysqlConn() *MysqlConn {
if conn == nil {
  conn = mysql.Open("conn_string")
}
  return conn
}

package user

type UserProvider struct{conn *MysqlConn}

func NewUserProvider(conn *MysqlConn) *UserProvider {
  return &UserProvider{conn: conn}
}

package product
type ProductProvider struct{conn *MysqlConn}

func NewProductProvider(conn *MysqlConn) *ProductProvider {
  return &ProductProvider{conn: conn}
}


func BuildProductProvider() *ProductProvider{
   return NewProductProvider(NewMysqlConn())
}

func BuildUserProvider() *UserProvider{
   return NewUserProvider(NewMysqlConn())
}

Good except for few things:
Every time I need to reuse resources (e.g. *MysqlConn in UserProvider and ProductProvider), I should declare global package variable, initialise resource if it’s not initialised, create builders everywhere and repeat the same steps over and over again! A lot of boilerplate, a lot of boring code work.

How about this:

var userProvider UserProvider
cont.Scan("userProvider", &userProvider)

yeah I literally repeated the same lines from above because this will be the default cont behaviour, but without package variables and nasty building methods calling each other.

Internally the “userProvider” will depend on “MysqlConn”, which will be initialised only once. Calling `Scan` means “fetch cached version of the service and its dependencies”.

So what if I want always initialise some struct every time I require it? Easy, just call another method:

cont.ScanNonCached("userProvider", &userProvider)

Internally the “userProvider” will be recreated as well as all its dependencies.

Lazy and pragmatic initialisation

We want to call initialisation logic only when a struct was going to be used. Because of this the go’s init function is not really suitable for such case, as all inits are executed, when program runs.

Constructors in a dependency management service have 2 lifetime stages: declaration and execution. After declaring constructors, we don’t want them to be executed immediately. The expected behaviour is to call only the initialisations of used services and their dependencies.
This is a lazy initialisation. We execute only the code we need in the current use case, which is a requirement for multifunctional applications with a single entry point.

Centralised struct lifetime management

Lifetime is a cycle of life from birth till death: structs are not only born in a certain way, but sometimes they should also die in a way we want. A typical example is a connection, we should not only open it before the first usage but also close after all operations are done.

No problem, the defer operator comes into the light:

conn := NewConnection()
defer conn.Close()

Imagine that the conn is reused by many different structs in your application:

type UserProvider struct{conn *Conn}
type ProductProvider struct{conn *Conn}

So where do we call defer conn.close()? If we do it in the constructor method NewConnection, the connection will be closed before even used. If we call it in some UserProvider method, there is no guarantee that the ProductProvider won’t try to use it after `conn.Close()` is executed.

Ok, let’s move it to the entry point method main:

func main() {
  conn := NewConnection()
  defer conn.Close()

  userProvider := NewUserProvider(conn)
  productProvider := NewProductProvider(conn)

  userProvider.GetUser()

  productProvider.GetProduct()
  //conn will be closed here
}

The problem is the main is “poisoned” with a lot of boilerplate. Let’s do it in the suggested way:

func BuildContainer() *Container {
  cont := NewContainer()
  cont.AddNewMethod("UserProvider", NewUserProvider, "Conn")
  cont.AddNewMethod("Conn", NewConn)
  cont.AddDestroyMethod("Conn", func(conn *Conn) { conn.Close()})
  cont.AddNewMethod("ProductProvider", NewProductProvider, "Conn")
  return cont
}

func main() {
   cont := BuildContainer()
   //this will call all "Destroy" methods of all registered structs
   defer cont.Destroy()

   var userProvider UserProvider
   cont.Scan("userProvider", &userProvider)
   userProvider.GetUser()

   var productProvider ProductProvider
   cont.Scan("productProvider", &productProvider)
   userProvider.GetProduct()
}

Since the container has knowledge of all application structs, it also can consistently manage their lifecycle: create and destroy them in the centralised way.

Cycle detection

The idea of a dependencies cycle is very easy. I call method buildA() which calls buildB() which calls buildA(): stack overflow as the result.
There is no build-in way to detect such cycles of dependencies in go.

However if we represent a dependency container as a directed graph, where directions are relations between parent structs and their children, the solution appears as an implementation of algorithm for detecting cycles in graphs.

Having a centralised logic for initialisation allows to detect dependency cycles and avoid stack overflows.

Conclusion

My obvious conclusion is that Go needs a dependency management tool to fulfil the listed motivation points. That’s why I decided to create my own library in order to give go a bit more of amazing Symfony power. More about my own library see in my next post.