Dependency Injection in Go
About Me
Justus Perlwitz, Software Developer
Email: hello@justus.pw
The Problem
Big applications have hidden dependencies
Hidden dependencies make code hard to test
High maintenance cost in strong coupling
Example
Mail Delivery Application
package main
type Mail struct {
  content   string
  recipient string
}
Mail Sending Function
// External Mailer API
import "mailbib" // Treat this a black box
func (e *Mail) Send() error {
  err := mailbib.Mailer.Send(e.content, e.recipient)
  if err != nil {
    return fmt.Errorf("MailBib error: %+v", err)
  }
  return nil
}
MailBib
func (m MailBib) Send(c string, r string) error {
  if r == "Santa" {
    return fmt.Errorf("Recipient too far away")
  }
  m.mails = append(m.mails, c)
  fmt.Printf("Sent '%s' to %sn", c, r)
  return nil
}
Let's use it
mail := Mail{
    content:   "Hello, World!",
    recpient: "Bigfoot",
}
if err := mail.Send(); err != nil {
    fmt.Printf("Error: %+vn", err)
}
This just works fine
Sent 'Hello, World!' to Bigfoot
A Letter to Santa
// Let's try sending the same mail to Santa
mail.recipient = "Santa"
// What if Mailbib cannot deliver mail to the Arctic Circle?
if err := mail.Send(); err != nil {
    fmt.Printf("Error: %+vn", err)
}
Santa is living too far away
Error: MailBib error: Recipient too far away
Dependency Injection
Let's inject our mailer dependency into the Mail struct:
type MailDelivery interface {
  Send(string, string) error
}
The interface requires the mailer to have one function  Send() 
type Mail struct {
  content   string
  recipient string
  delivery  MailDelivery
}
Trying Different Dependencies
Now, we have to initialize a  Mail as follows
mail := Mail{
    content:   "Hello, World!",
    recipient: "Santa",
    delivery:  mailbib.Mailer,
}
And we adjust the mailing method
func (e *Mail) Send() error {
  err := e.delivery.Send(e.content, e.recipient)
  if err != nil {
    return fmt.Errorf("Error: %+v", err)
  }
  return nil
}
Of course, MailBib still refuses to deliver our mail:
if err := mail.Send(); err != nil {
    fmt.Printf("Could not deliver mail ‐> %+vn", err)
}
outputs
Could not deliver mail ‐> Error: Recipient too far away
Injecting a different dependency
Let's pretend, there is a second delivery API, that delivers mail to the
arctic circle
import "mailarctica"
Now we can inject a different service
mail.delivery = mailarctica.Mailer
Let's send the email again
mail.delivery = mailarctica.Mailer
// This time, MailArctica will deliver our letter
if err := mail.Send(); err != nil {
    fmt.Printf("Error: %+vn", err)
}
will output
Sent 'Hello, World!' to Santa
Useful Packages
There are two packages with similar names that make dependency
injection a lot easier:
inject by Facebook
Inject
Questions?

Pilot Tech Talk #12 — Dependency Injection in Go by Justus Perlwitz