di

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 16, 2018 License: MIT Imports: 6 Imported by: 0

README

DI

Dependency injection container library for go programs (golang).

DI handles the life cycle of the objects in your application. It creates them when they are needed, resolves their dependencies and closes them properly when they are no longer used.

If you do not know if DI could help improving your application, learn more about dependency injection and dependency injection containers:

DI is focused on performance. It does not rely on reflection.

Table of contents

Build Status GoDoc Test Coverage Maintainability codebeat goreport

Basic usage

Object definition

A Definition contains at least the Name of the object and a Build function to create the object.

di.Definition{
    Name: "my-object",
    Build: func(ctn di.Container) (interface{}, error) {
        return &MyObject{}, nil
    },
}

The definition can be added to a Builder with the AddDefinition method:

builder, _ := di.NewBuilder()

builder.AddDefinition(di.Definition{
    Name: "my-object",
    Build: func(ctn di.Container) (interface{}, error) {
        return &MyObject{}, nil
    },
})

Object retrieval

Once the definitions have been added to a Builder, the Builder can generate a Container. This Container will provide the objects defined in the Builder.

ctn := builder.Build()
obj := ctn.Get("my-object").(*MyObject)

The objects in a Container are singletons. You will retrieve the exact same object every time you call the Get method on the same Container. The Build function will only be called once.

Nested definition

The Build function can also call the Get method of the Container. That allows to build objects that depend on other objects defined in the Container.

di.Definition{
    Name: "nested-object",
    Build: func(ctn di.Container) (interface{}, error) {
        return &MyNestedObject{
            Object: ctn.Get("my-object").(*MyObject),
        }, nil
    },
}

You can not create a cycle in the definitions (A needs B and B needs A). If that happens, an error will be returned at the time of the creation of the object.

Scopes

Definitions can also have a scope. They can be useful in request based applications (like a web application).

di.Definition{
    Name: "my-object",
    Scope: di.Request,
    Build: func(ctn di.Container) (interface{}, error) {
        return &MyObject{}, nil
    },
}

The scopes are defined when the Builder is created:

builder, err := di.NewBuilder(di.App, di.Request)

Scopes are defined from the widest to the narrowest. If no scope is given to NewBuilder, it is created with the three default scopes: di.App, di.Request and di.SubRequest. These scopes should be enough almost all the time.

Containers created by the Builder belongs to one of these scopes. A Container may have a parent with a wider scope and children with a narrower scope. A Container is only able to build objects from its own scope, but it can retrieve objects with a wider scope from its parent Container.

If a Definition does not have a scope, the widest scope will be used.

// Create a Builder with the default scopes.
builder, _ := di.NewBuilder()

// Define an object in the App scope.
builder.AddDefinition(di.Definition{
    Name: "app-object",
    Scope: di.App,
    Build: func(ctn di.Container) (interface{}, error) {
        return &MyObject{}, nil
    },
})

// Define an object in the Request scope.
builder.AddDefinition(di.Definition{
    Name: "request-object",
    Scope: di.Request,
    Build: func(ctn di.Container) (interface{}, error) {
        return &MyObject{}, nil
    },
})

// Build creates a Container in the widest scope (App).
app := builder.Build()

// The App Container can create sub-containers in the Request scope.
req1, _ := app.SubContainer()
req2, _ := app.SubContainer()

// app-object can be retrieved from the three containers.
// The retrieved objects are the same: o1 == o2 == o3.
// The object is stored in the App Container.
o1 := app.Get("app-object").(*MyObject)
o2 := req1.Get("app-object").(*MyObject)
o3 := req2.Get("app-object").(*MyObject)

// request-object can only be retrieved from req1 and req2.
// The retrieved objects are not the same: o4 != o5.
o4 := req1.Get("request-object").(*MyObject)
o5 := req2.Get("request-object").(*MyObject)

Container deletion

A definition can also have a Close function.

di.Definition{
    Name: "my-object",
    Scope: di.App,
    Build: func(ctn di.Container) (interface{}, error) {
        return &MyObject{}, nil
    },
    Close: func(obj interface{}) {
        obj.(*MyObject).Close()
    }
}

This function is called when the Delete method is called on a Container.

// Create the Container.
app := builder.Build()

// Retrieve an object.
obj := app.Get("my-object").(*MyObject)

// Delete the Container, the Close function will be called on obj.
app.Delete()

Delete closes all the objects stored in the Container. Once a Container has been deleted, it becomes unusable.

There are actually two delete methods: Delete and DeleteWithSubContainers

DeleteWithSubContainers deletes the Container children and then the Container right away. Delete is a softer approach. It does not delete the Container children. Actually it does not delete the Container as long as it still has a child alive. So you have to call Delete on all the children to delete the Container.

You probably want to use Delete and close the children manually. DeleteWithSubContainers can cause errors if the parent is deleted while its children are still used.

The database example at the end of this documentation is a good example of how you can use Delete.

Define an already built object

The Builder Set method is a shortcut to define an already built object in widest scope.

builder.Set("my-object", object)

is the same as:

builder.AddDefinition(di.Definition{
    Name: "my-object",
    Scope: di.App,
    Build: func(ctn di.Container) (interface{}, error) {
        return object, nil
    },
})

Methods to retrieve an object

Get

Get returns an interface that can be cast afterward. If the item can not be created, nil is returned.

// It can be used safely.
objectInterface := ctn.Get("my-object")
object, ok := objectInterface.(*MyObject)

// Or if you do not care about panicking...
object := ctn.Get("my-object").(*MyObject)

SafeGet

Get is fine to retrieve an object, but it does not give you any information if something goes wrong. SafeGet works like Get but also returns an error. It can be used to find why an object could not be created.

objectInterface, err := ctn.SafeGet("my-object")

Fill

The third method to retrieve an object is Fill. It returns an error if something goes wrong like SafeGet, but it may be more practical in some situations. It uses reflection to fill the given object, so it is slower than SafeGet

var object *MyObject
err = ctn.Fill("my-object", &MyObject)

Unscoped retrieval

The previous methods can retrieve an object defined in the same scope or a wider one. If you need an object defined in a narrower scope, you need to create a sub-container to retrieve it. It is logical but not always very practical.

UnscopedGet, UnscopedSafeGet and UnscopedFill work like Get, SafeGet and Fill but can retrieve objects defined in a narrower scope. To do so they generate sub-containers that can only be accessed by these three methods. To remove these containers without deleting the current container, you can call the Clean method.

builder, _ := di.NewBuilder()

builder.AddDefinition(di.Definition{
    Name: "request-object",
    Scope: di.Request,
    Build: func(ctn di.Container) (interface{}, error) {
        return &MyObject{}, nil
    },
    Close: func(obj interface{}) {
        obj.(*MyObject).Close()
    }
})

app := builder.Build()

// app can retrieve a request-object with unscoped methods.
obj := app.UnscopedGet("request-object").(*MyObject)

// Once the objects created with unscoped methods are no longer used,
// you can call the Clean method. In this case, the Close function
// will be called on the object.
app.Clean()

Logger

If a Logger is set in the Builder when the Container is created, it will be used to log errors that might happen when an object is retrieved or closed. It is particularly useful if you use the Get method that does not return an error.

builder, _ := di.NewBuilder()
builder.Logger = di.BasicLogger{}

Panic in Build and Close functions

Panic in Build and Close functions of a Definition are recovered. In particular that allows you to use the Get method in a Build function.

Using Get in a Build function instead of SafeGet is way more practical. But it also can make debugging a nightmare. Be sure to define a Logger in the Builder if you want to be able to trace the errors.

Database example

Here is an example that shows how DI can be used to get a database connection in your application.

package main

import (
	"context"
	"database/sql"
	"net/http"

	"github.com/sarulabs/di"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	app := createApp()

	defer app.Delete()

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// Create a request and delete it once it has been handled.
		// Deleting the request will close the connection.
		request, _ := app.SubContainer()
		defer request.Delete()

		handler(w, r, request)
	})

	http.ListenAndServe(":8080", nil)
}

func createApp() di.Container {
	builder, _ := di.NewBuilder()

	// Use a logger or you will lose the errors
	// that can happen during the creation of the objects.
	builder.Logger = &di.BasicLogger{}

	// Define the connection pool in the App scope.
	// There will be one for the whole application.
	builder.AddDefinition(di.Definition{
		Name:  "mysql-pool",
		Scope: di.App,
		Build: func(ctn di.Container) (interface{}, error) {
			db, err := sql.Open("mysql", "user:password@/")
			db.SetMaxOpenConns(1)
			return db, err
		},
		Close: func(obj interface{}) {
			obj.(*sql.DB).Close()
		},
	})

	// Define the connection in the Request scope.
	// Each request will use its own connection.
	builder.AddDefinition(di.Definition{
		Name:  "mysql",
		Scope: di.Request,
		Build: func(ctn di.Container) (interface{}, error) {
			pool := ctn.Get("mysql-pool").(*sql.DB)
			return pool.Conn(context.Background())
		},
		Close: func(obj interface{}) {
			obj.(*sql.Conn).Close()
		},
	})

	// Returns the app Container.
	return builder.Build()
}

func handler(w http.ResponseWriter, r *http.Request, ctn di.Container) {
	// Retrieve the connection.
	conn := ctn.Get("mysql").(*sql.Conn)

	var variable, value string

	row := conn.QueryRowContainer(context.Background(), "SHOW STATUS WHERE `variable_name` = 'Threads_connected'")
	row.Scan(&variable, &value)

	// Display how many connections are opened.
	// As the connection is closed when the request is deleted,
	// the value should not be be higher than the number set with db.SetMaxOpenConns(1).
	w.Write([]byte(variable + ": " + value))
}

Documentation

Index

Constants

View Source
const App = "app"

App is the name of the application scope.

View Source
const Request = "request"

Request is the name of the request scope.

View Source
const SubRequest = "subrequest"

SubRequest is the name of the subrequest scope.

Variables

This section is empty.

Functions

This section is empty.

Types

type BasicLogger

type BasicLogger struct{}

BasicLogger is a Logger that uses log.Println to write the error on the standard output.

func (*BasicLogger) Error

func (l *BasicLogger) Error(args ...interface{})

type Builder

type Builder struct {
	Logger Logger
	// contains filtered or unexported fields
}

Builder is the only way to create a working Container. The scopes and object definitions are set in the Builder that can create a Container based on this information.

func NewBuilder

func NewBuilder(scopes ...string) (*Builder, error)

NewBuilder is the only way to create a working Builder. It initializes the Builder with a list of scopes. The scope are ordered from the wider to the narrower. If no scope is provided, the default scopes are used: [App, Request, SubRequest]

func (*Builder) AddDefinition

func (b *Builder) AddDefinition(def Definition) error

AddDefinition adds an object Definition in the Builder. It returns an error if the Definition can't be added.

func (*Builder) Build

func (b *Builder) Build() Container

Build creates a Container in the wider scope with all the current scopes and definitions.

func (*Builder) Definitions

func (b *Builder) Definitions() map[string]Definition

Definitions returns a map with the objects definitions added with the AddDefinition method. The key of the map is the name of the Definition.

func (*Builder) IsDefined

func (b *Builder) IsDefined(name string) bool

IsDefined returns true if there is a definition with the given name.

func (*Builder) Scopes

func (b *Builder) Scopes() []string

Scopes returns the list of available scopes.

func (*Builder) Set

func (b *Builder) Set(name string, obj interface{}) error

Set adds a definition for an already build object. The scope used as the Definition scope is the Builder wider scope.

type Container

type Container interface {
	// Definition returns the map of the available Definitions ordered by Definition name.
	// These Definitions represent all the objects that this Container can build.
	Definitions() map[string]Definition

	// Scope returns the Container scope.
	Scope() string

	// Scopes returns the list of available scopes.
	Scopes() []string

	// ParentScopes returns the list of scopes wider than the Container scope.
	ParentScopes() []string

	// SubScopes returns the list of scopes narrower than the Container scope.
	SubScopes() []string

	// Parent returns the parent Container.
	Parent() Container

	// SubContainer creates a new Container in the next subscope
	// that will have this Container as parent.
	SubContainer() (Container, error)

	// SubContext is the previous name of the SubContainer method.
	// DEPRECATED: will be removed in v2.
	SubContext() (Container, error)

	// SafeGet retrieves an object from the Container.
	// The object has to belong to this scope or a wider one.
	// If the object does not already exist, it is created and saved in the Container.
	// If the object can't be created, it returns an error.
	SafeGet(name string) (interface{}, error)

	// Get is similar to SafeGet but it does not return the error.
	Get(name string) interface{}

	// Fill is similar to SafeGet but it does not return the object.
	// Instead it fills the provided object with the value returned by SafeGet.
	// The provided object must be a pointer to the value returned by SafeGet.
	Fill(name string, dst interface{}) error

	// UnscopedSafeGet retrieves an object from the Container, like SafeGet.
	// The difference is that the object can be retrieved
	// even if it belongs to a narrower scope.
	// To do so UnscopedSafeGet creates a sub-container.
	// When the created object is no longer needed,
	// it is important to use the Clean method to Delete this sub-container.
	UnscopedSafeGet(name string) (interface{}, error)

	// UnscopedGet is similar to UnscopedSafeGet but it does not return the error.
	UnscopedGet(name string) interface{}

	// UnscopedFill is similar to UnscopedSafeGet but copies the object in dst instead of returning it.
	UnscopedFill(name string, dst interface{}) error

	// NastySafeGet is the previous name of the UnscopedSafeGet method.
	// DEPRECATED: will be removed in v2.
	NastySafeGet(name string) (interface{}, error)

	// NastyGet is the previous name of the UnscopedGet method.
	// DEPRECATED: will be removed in v2.
	NastyGet(name string) interface{}

	// NastyFill is the previous name of the UnscopedFill method.
	// DEPRECATED: will be removed in v2.
	NastyFill(name string, dst interface{}) error

	// Clean deletes the sub-container created by UnscopedSafeGet, UnscopedGet or UnscopedFill.
	Clean()

	// DeleteWithSubContainers takes all the objects saved in this Container
	// and calls the Close function of their Definition on them.
	// It will also call DeleteWithSubContainers on each child and remove its reference in the parent Container.
	// After deletion, the Container can no longer be used.
	DeleteWithSubContainers()

	// DeleteWithSubContexts is the previous name of the DeleteWithSubContainers method.
	// DEPRECATED: will be removed in v2.
	DeleteWithSubContexts()

	// Delete works like DeleteWithSubContainers if the Container does not have any child.
	// But if the Container has sub-containers, it will not be deleted right away.
	// The deletion only occurs when all the sub-containers have been deleted.
	// So you have to call Delete or DeleteWithSubContainers on all the sub-containers.
	Delete()

	// IsClosed returns true if the Container has been deleted.
	IsClosed() bool
}

Container represents a dependency injection container. A Container has a scope and may have a parent with a wider scope and children with a narrower scope. Objects can be retrieved from the Container. If the desired object does not already exist in the Container, it is built thanks to the object Definition. The following attempts to get this object will return the same object.

type Context

type Context = Container

Context is the previous name of the Container interface. DEPRECATED: will be removed in v2.

type Definition

type Definition struct {
	Name  string
	Scope string
	Build func(ctn Container) (interface{}, error)
	Close func(obj interface{})
	Tags  []Tag
}

Definition contains information to build and close an object inside a Container.

type DefinitionMap

type DefinitionMap map[string]Definition

DefinitionMap is a collection of Definition ordered by name.

func (DefinitionMap) Copy

func (m DefinitionMap) Copy() map[string]Definition

Copy returns a copy of the DefinitionMap.

type Logger

type Logger interface {
	Error(args ...interface{})
}

Logger is the interface used to log errors that occurred while an object is built or closed.

type MuteLogger

type MuteLogger struct{}

MuteLogger is a Logger that doesn't log anything.

func (*MuteLogger) Error

func (l *MuteLogger) Error(args ...interface{})

type ScopeList

type ScopeList []string

ScopeList is a slice of scope.

func (ScopeList) Copy

func (l ScopeList) Copy() ScopeList

Copy returns a copy of the ScopeList.

func (ScopeList) ParentScopes

func (l ScopeList) ParentScopes(scope string) ScopeList

ParentScopes returns the scopes before the one given as parameter.

func (ScopeList) SubScopes

func (l ScopeList) SubScopes(scope string) ScopeList

SubScopes returns the scopes after the one given as parameter.

type Tag

type Tag struct {
	Name string
	Args map[string]string
}

Tag can contain more specific information about a Definition. It is useful to find a Definition thanks to its tags instead of its name.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL