di

package module
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Jun 15, 2017 License: MIT Imports: 6 Imported by: 1

README

DI

Build Status GoDoc Coverage codebeat goreport

Dependency injection container for golang.

If you don't know what a dependency injection container is, you may want to read this article before :

http://fabien.potencier.org/do-you-need-a-dependency-injection-container.html

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(ctx di.Context) (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(ctx di.Context) (interface{}, error) {
        return &MyObject{}, nil
    },
})
Object retrieval

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

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

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

Nested definition

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

di.Definition{
    Name: "nested-object",
    Build: func(ctx di.Context) (interface{}, error) {
        return &MyNestedObject{
            Object: ctx.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 :

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

The scopes are defined when the Builder is created :

builder, err := di.NewBuilder("app", "request")

Scopes are defined from the wider to the narrower. 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.

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

If a Definition does not have a scope, the wider 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(ctx di.Context) (interface{}, error) {
        return &MyObject{}, nil
    },
})

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

// Build creates a Context in the wider scope.
app := builder.Build()

// app Context can get children in the Request scope.
req1, _ := app.SubContext()
req2, _ := app.SubContext()

// app-object can be retrieved from the three contexts.
// The retrieved objects are the same : o1 == o2 == o3.
// The object is stored in the app context.
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)

Context deletion

A definition can also have a Close function.

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

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

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

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

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

Delete closes all the objects stored in the Context. This means objects with the same scope created by this Context or one of its children. It will also call the Delete method on all the Context children. Once the Delete method has been called, the Context becomes unusable.

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 the wider scope.

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

is the same as :

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

It can be useful to define you application parameters.

Methods to retrieve an object

Get

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

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

// Or if you don't care about panicking...
object := ctx.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 := ctx.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 certain situations.

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

Nasty retrieval

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

NastyGet, NastySafeGet and NastyFill work like Get, SafeGetand Fill but can retrieve objects defined in a narrower scope. To do so they generate sub-contexts that are not accessible. To remove these contexts, you have to call the Clean method on the Context.

builder, _ := di.NewBuilder()

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

app := builder.Build()

// app can retrieve a request-object with nasty methods.
obj := req1.NastyGet("request-object").(*MyObject)

// Once the objects created with nasty 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 Context 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 retrieval 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.

Database example

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

package main

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

    "github.com/sarulabs/di"

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

func main() {
    app := createApp()

    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 database connection.
        request, _ := app.SubContext()
        defer request.Delete()
        handler(w, r, request)
    })

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

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

    // Define the database configuration.
    builder.Set("dsn", "user:password@/dbname")

    // Define the connection in the Request scope.
    // Each request will use a different connection.
    builder.AddDefinition(di.Definition{
        Name:  "mysql",
        Scope: di.Request,
        Build: func(ctx di.Context) (interface{}, error) {
            dsn := ctx.Get("dsn").(string)
            return sql.Open("mysql", dsn)
        },
        Close: func(obj interface{}) {
            obj.(*sql.DB).Close()
        },
    })

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

func handler(w http.ResponseWriter, r *http.Request, ctx di.Context) {
    // Retrieve the connection.
    db := ctx.Get("mysql").(*sql.DB)

    var variable, value string

    row := db.QueryRow("SHOW STATUS WHERE `variable_name` = 'Threads_connected'")
    row.Scan(&variable, &value)

    // Display how many connection are opened.
    // As the connection is closed when the request is deleted,
    // the number should not increase after each request.
    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 added in v1.1.0

type BasicLogger struct{}

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

func (*BasicLogger) Error added in v1.1.0

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

type Builder added in v1.1.0

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

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

func NewBuilder added in v1.1.0

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 added in v1.1.0

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 added in v1.1.0

func (b *Builder) Build() Context

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

func (*Builder) Definitions added in v1.1.0

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

Definitions returs 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 added in v1.1.0

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

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

func (*Builder) Scopes added in v1.1.0

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

Scopes returns the list of available scopes.

func (*Builder) Set added in v1.1.0

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 Context

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

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

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

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

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

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

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

	// SafeGet retrieves an object from the Context.
	// 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 Context.
	// 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

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

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

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

	// Clean deletes the subcontext created by NastySafeGet, NastyGet or NastyFill.
	Clean()

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

	// Delete works like DeleteWithSubContexts but do not delete the subcontexts.
	// If the Context has subcontexts, it will not be deleted right away.
	// The deletion only occurs when all the subcontexts have been deleted.
	Delete()

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

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

type Definition added in v1.1.0

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

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

type DefinitionMap added in v1.2.0

type DefinitionMap map[string]Definition

DefinitionMap is a collection of Definition ordered by name.

func (DefinitionMap) Copy added in v1.2.0

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

Copy retuns a copy of the DefinitionMap.

type Logger added in v1.1.0

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 added in v1.1.0

type MuteLogger struct{}

MuteLogger is a Logger that doesn't log anything.

func (*MuteLogger) Error added in v1.1.0

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

type ScopeList added in v1.2.0

type ScopeList []string

ScopeList is a slice of scope.

func (ScopeList) Copy added in v1.2.0

func (l ScopeList) Copy() ScopeList

Copy returns a copy of the ScopeList.

func (ScopeList) ParentScopes added in v1.2.0

func (l ScopeList) ParentScopes(scope string) ScopeList

ParentScopes retuns the scopes before the one given as parameter.

func (ScopeList) SubScopes added in v1.2.0

func (l ScopeList) SubScopes(scope string) ScopeList

SubScopes retuns the scopes after the one given as parameter.

type Tag added in v1.1.0

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