We all know dependency injection makes packages decoupled.
But I'm a little confused about best practices of dependency injection in go.
Lets assume package User needs to access Config package.
We can pass a Config object to User methods. In this way I can change the Config package functionality as long as the new code resolves the interfaces.
Another approach is call Config package methods directly , In these scenario I can change Config code too as long as the methods names remains the same. Like so
Update :
What is different between these two approaches :
package User
func foo(config ConfigObject) {
config.Foo()
}
And this one :
package User
import Config
func foo() {
config.Foo()
}
Calling config.Foo
on the config
argument to a method means that you receive an instance of some structure (possibly implementing interface Config
) and call the method Foo
on that instance/interface. Think of this as of calling a method of an object in OO terms:
package user
func foo(cfg config.Config) {
cfg.Foo()
}
Calling config.Foo
having imported the config
package means you are calling the function Foo
of package config
, not of any object/struct/interface. Think of this as pure procedural programming without any objects:
package user
import config
func foo() {
config.Foo()
}
The latter has nothing to do with dependency injection, the former may constitute a part of it if Config
is an interface.
Dependency injection, on the other hand, follows generally the same rules in Go as in other languages:
accept interfaces, supply implementations
Because in Go structs satisfy interfaces implicitly rather than explicitly (as it is the case in Java)
- the code accepting the value only needs to know about the interface and import it;
- the code implementing it does not even need to know about the interface (it can just happen that it satisfies it);
- the code that supplies the impl into a method accepting an
interface, obviously, needs to know both.
For your example this means:
package config
type Config interface {
Foo() string
}
package foo
type Foo struct{}
func (f *Foo) Foo() string {
return "foo"
}
package boo
type Boo struct{}
func (b *Boo) Foo() string {
return "boo"
}
package main
func foo(cfg config.Config) string{
return cfg.Foo()
}
func main() {
// here you inject an instance of Foo into foo(Config)
log.Print(foo(&foo.Foo{}))
// here you inject an instance of Boo into foo(Config)
log.Print(foo(&boo.Boo{})
}
Prints
2018/03/03 13:32:12 foo
2018/03/03 13:32:12 boo