Dependency Injection and IoC Container in Go (Golang)

Milad Rahimi
3 min readSep 30, 2019

--

Dependency Injection and IoC Container in Go (Golang) programming language

If you’ve switched from a language like Java or PHP to Go (Golang), you might have searched for a way to use dependency injection for better code maintenance and testing, just like I did.

In this post, I’m going to introduce the GoLobby Container package. This package provides an IoC (Inversion of Control) container you probably need in your Golang projects.

Simplicity and performance are the main goals of the Go programming language, and it’s what the GoLobby Container package brings to your codes. The Container package is very lightweight and fast. It reduces your code complexity and lets you write tests easier.

To add the Container package to your project, run the following command:

go get github.com/golobby/container/v3

Let’s take a look at a simple example. Consider you have a Database interface and a MySQL implementation for it, like this:

type Database interface {
Connect() bool
}
type MySQL struct {}func (m MySQL) Connect() bool {
return true
}

Now, if you want to bind the Database to MySQL, you can define an anonymous function (resolver) that returns the appropriate concrete, like this:

func() Database {
return &MySQL{}
}

The return type of the resolver function is the abstraction (interface). It means you can return any implementation of the Database in this function.

If the resolving process might raise any error, you can return the error as the second return value like the following resolver function.

func() (Database, error) {
// ...
if err {
return nil, err
} else {
return db, nil
}
}

Now let the Container know about this binding:

container.Singleton(func() Database {
return &MySQL{}
})

It tells the Container that the appropriate concrete for the Database is MySQL.

In the mentioned example we’ve used Singletion() method to bind the Database interface to the MySQL implementation. The Container invokes the resolver function once and provides the same object to further requests. If you need to have a new instance each time you need the implementation, you should use theTransient() method instead, the logic is the same but it causes Container to invoke the resolver function each time you need an implementation. Take a look at this example:

container.Transient(func() Shape {
return &Rectangle{}
})

After binding, normally you need a method to get the concretes somehow. No worry! It’s even easier, just take a look at the following example that provides the same MySQL:

var db Database
container.Bind(&db)
// The db will be an instance of MySQL
db.Connect();

There is another way to resolve dependencies, using a closure. take a look at the following example:

container.Call(func(db Database) {
// Now db is an instance of MySQL
db.Connect()
})

You might need to resolve multiple abstractions at once like this:

container.Call(func(db Database, s Shape) {
// db is an instance of MySQL
db.Connect()
// s is an instance of Rectangle
s.Area()
})

There is another nice feature that lets you resolve abstraction in binding time. For example, You are going to bind Database to MySQL. But you need to configure the MySQL instance before binding. So you need to resolve the Config before binding the Database or you can do both at once like this example:

// Bind Config to JsonConfig
container.Singleton(func() Config {
return &JsonConfig{...}
})

// Bind Database to MySQL
container.Singleton(func(c Config) Database {
// "c" will be the instance of JsonConfig
return &MySQL{
Username: c.Get("DB_USERNAME"),
Password: c.Get("DB_PASSWORD"),
}
})

There is also a new way of resolving dependencies in the latest versions. In this way, you pass a struct with fields of abstractions to the container and the container fills the fields with appropriate concretes.

type App struct {
m Mailer `container:"inject"`
d Database `container:"inject"`
x string `container:"ignore"`
y int
}

myApp := App{}

err := container.Fill(&myApp)
// `myApp.m` will be an implementation of the Mailer interface
// `myApp.s` will be an implementation of the Database interface
// `myApp.x` will be ignored since it has no appropriate tag
// `myApp.y` will be ignored since it has no tag

As you can see, the container resolves struct fields that have an injection tag. The container ignores the fields with other tags and the fields without tags.

We have introduced the Container package and showed you some examples. This will be useful to you!

Don’t forget to visit the repository in Github to see the latest documentation at https://github.com/golobby/container.

--

--

Milad Rahimi
Milad Rahimi

Written by Milad Rahimi

An enterprise-class software architect and senior developer with 7+ years of experience developing PHP and Go apps. https://miladrahimi.com

Responses (1)