0

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()
}
Mostafa Solati
  • 1,235
  • 2
  • 13
  • 33

2 Answers2

4

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

Community
  • 1
  • 1
Oleg Sklyar
  • 9,834
  • 6
  • 39
  • 62
  • The example is horrible in the way it uses names (foo/Foo/foo). Even, when correct, it would confuse 100 times less, if the foo package in example is named pkg1 or anything but foo. :) – MBODM Jun 10 '22 at 13:11
  • 1
    @MBODM From what I know, one can suggest/make an edit if the example is not to your liking... – Oleg Sklyar Jun 17 '22 at 16:14
  • Yes, you are right. Had been the better way. Sorry! – MBODM Jun 19 '22 at 19:35
0

Since, in my opinion, the example code given by previous poster(s) could be a bit less confusing for beginners, just by renaming things, i quickly try to do this here:

package contracts

type IConfig interface {
    GetSomeString() string
}
package pkg1

type Foo struct{}

func (f *Foo) GetSomeString() string {
    return "hello“
}
package pkg2

type Boo struct{}

func (b *Boo) GetSomeString() string {
    return "world" 
}
package main

func run(config contracts.IConfig) string {
    s := config.GetSomeString()
    return s
}
 
func main() {
    foo := &pkg1.Foo{}
    result1 := run(foo)
    log.Print(result1)

    boo := &pkg2.Boo{}
    result2 := run(boo)
    log.Print(result2)

    // Prints: helloworld

    // You learned:
    // Since the run() func use a IConfig interface as
    // parameter, the run() func can handle both struct
    // types (Foo and Boo) as input, because both struct
    // types (Foo and Boo) implement the IConfig interface.
}
MBODM
  • 141
  • 1
  • 1
  • 11