Skip to content
eriicafesPublic

About

Fluent dependency injection in Go

Resources

Stars

1 star

Watchers

1 watching

Forks

Repository files navigation

Got

Fluent dependency injection in Go.

Got provides a declarative way of composing dependencies. It integrates nicely with the common constructor patterns in Go applications.

The main advantage of got is removing the need to manually pass references around when instantiating types that depend on one another.

Installation

go get github.com/eriicafes/got

Usage

got has a very small API surface. You typically only need to use two functions from this library.

Create a constructor

Constructor return values are cached the first time they are requested and future calls return the cached value instead of re-running the constructor. See Transient constructors to opt out of caching.

Constructors are global variables with their names prefixed with Get as a convention.

// printer.go
package main

import "github.com/eriicafes/got"

type Printer interface { 
    Print(string) string
}

var GetPrinter = got.Using(func(c *got.Container) Printer {
    return &CapsPrinter{}
})

// CapsPrinter is an implementation of Printer interface
type CapsPrinter struct{}

func (*CapsPrinter) Print(s string) string { 
    return strings.ToUpper(s)
}

Use in another constructor

Retrieve an instance from a constructor by calling GetXXX.From(container)

// office.go
package main

import "github.com/eriicafes/got"

type Office struct {
    Printer Printer
}

var GetOffice = got.Using(func(c *got.Container) *Office {
    return &Office{
        Printer: GetPrinter.From(c),
    }
})

Use in application

Create a container to hold cached instances.

When calling a constructor, any dependencies it has will also be cached, ensuring that shared dependencies use the same instance.

// main.go
package main

import "github.com/eriicafes/got"

func main() {
    c := got.New()

    office := GetOffice.From(c)
    // or using the From function from got
    office := got.From(c, GetOffice)

    office.Printer.Print()
}

Context

Constructors can use a container's context while initializing cached dependencies. got.New() uses context.Background(). To provide a context explicitly, create the container with got.NewContext(context) and retrieve it in a constructor with container.Context().

// main.go
package main

import (
    "context"

    "github.com/eriicafes/got"
)

var GetClient = got.Using(func(container *got.Container) *Client {
    return NewClient(container.Context())
})

func main() {
    container := got.NewContext(context.Background())

    client := GetClient.From(container)
    _ = client
}

Transient constructors

Transient constructors create a new instance each time it is requested.

Use got.NewUsing to create a transient constructor. Its From method does not cache the returned value. You can also call any constructor's New method directly to create a value without caching it.

// office.go
package main

import "github.com/eriicafes/got"

type Office struct {
    Printer Printer
}

var NewPrinter = got.NewUsing(func(c *got.Container) Printer {
    return &CapsPrinter{}
})

var GetOffice = got.Using(func(c *got.Container) *Office {
    return &Office{
        // a new printer is created each time
        Printer: NewPrinter.From(c),
    }
})

Multiple return value constructors

Use got.TryUsing for constructors that return a value and an error. Use got.TryNewUsing when the constructor should be transient.

// bad_office.go
package main

import "github.com/eriicafes/got"

var GetBadOffice = got.TryUsing(func(c *got.Container) (*Office, error) {
    return nil, fmt.Errorf("failed to create office")
})

Use got.Using2 for other constructors that return two values. Use got.NewUsing2 when the constructor should be transient.

var GetHTTPAddress = got.Using2(func(c *got.Container) (string, int) {
    return "127.0.0.1", 8080
})

Clearing cached constructors

Call container.Clear() to remove every cached constructor result. Call container.ClearErrors() to remove only results whose second value is a non-nil error, allowing failed constructors to be retried.

Mocking

You can mock a constructor using got.Mock or got.Mock2.

// main.go
package main

import "github.com/eriicafes/got"

type MockPrinter struct{}

var GetMockPrinter = got.Using(func(c *got.Container) Printer {
    return &MockPrinter{}
})

func (*MockPrinter) Print(s string) string {
    return fmt.Sprintf("mocked %s", s)
}

func main() {
    mc := got.New()
    got.Mock(mc, GetPrinter, GetMockPrinter.New(mc))
    office := GetOffice.From(mc)

    office.Printer.Print()
}

Circular dependency errors

Go prevents you from creating circular dependencies as long as you maintain the convention and use global vars as constructors.

Always declare constructors as top-level vars rather than assigning them lazily, otherwise circular dependencies can go undetected and deadlock.

About

Fluent dependency injection in Go

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages