inject

package module
v1.5.2 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2019 License: MIT Imports: 6 Imported by: 2

README

Tweet

Documentation Release Build Status Code Coverage Contributors

Dependency injection container allows you to inject dependencies into constructors or structures without the need to have specified each argument manually.

This container implementation inspired by google/wire, uber-go/fx and uber-go/dig.

See godoc for feel the difference.

Contents

Installing

go get -u github.com/defval/inject

Type injection

Define constructors:

// NewHTTPHandler is a http mux constructor.
func NewHTTPServeMux() *http.ServeMux {
	return &http.ServeMux{}
}

// NewHTTPServer is a http server constructor, handler will be injected 
// by container. If environment variable `STATUS == "stoped"` extract
// server cause error.
func NewHTTPServer(handler *net.ServeMux) (*http.Server, error) {
	if os.Getenv("STATUS") == "stopped" {
		return nil, errors.New("server stoped")
	}
	
	return &http.Server{
		Handler: handler,
	}, nil
}

Build container and extract values:

// build container
container, err := inject.New(
    inject.Provide(NewHTTPServeMux), // provide mux
    inject.Provide(NewHTTPServer), // provide server
)

// don't forget to handle errors © golang

// define variable for *http.Server
var server *http.Server

// extract into this variable
container.Extract(&server)

// use it!
server.ListenAndServe()

Groups

When you have two or more implementations of same interface:

// NewUserController
func NewUserController() *UserController {
	return &UserController{}
}

// NewPostController
func NewPostController() *PostController {
	return &PostController()
}

// Controller
type Controller interface {
	RegisterRoutes()
}

Group it!

// IController is a java style interface alias =D
// inject.As(new(Controller)) looks worse in readme.
var IController = new(Controller)

container, err := inject.New(
	inject.Provide(NewUserController, inject.As(IController)),
	inject.Provide(NewPostController, inject.As(IController)),
)

var controllers []Controller
// extract all controllers
container.Extract(&controllers)

// and do something!!!
for _, ctrl := range controllers {
	ctrl.RegisterRoutes()
}

Return structs, accept interfaces!

Bind implementations as interfaces:

// NewHandler is a http mux constructor. Returns concrete
// implementation - *http.ServeMux.
func NewServeMux() *http.ServeMux {
	return &http.ServeMux{}
}

// NewServer is a http server constructor. Needs handler for 
// working.
func NewServer(handler http.Handler) *http.Server {
	return &http.Server{
		Handler: handler,
	}
}

Provide concrete implementation as interface:

var IHandler = new(http.Handler)

container, err := inject.New(
    inject.Provide(NewServeMux, inject.As(IHandler)),
    inject.Provide(NewServer),
)

var handler http.Handler
container.Extract(&handler) // *http.ServeMux will be extracted

var server *http.Server
container.Extract(&server) // server.Handler is *http.ServeMux

Bundles

// ProcessingBundle responsible for processing
var ProcessingBundle = inject.Bundle(
    inject.Provide(processing.NewDispatcher),
    inject.Provide(processing.NewProvider),
    inject.Provide(processing.NewProxy, inject.As(IProxy)),
)

// BillingBundle responsible for billing
var BillingBundle = inject.Bundle(
    inject.Provide(billing.NewInteractor),
    inject.Provide(billing.NewInvoiceRepository, inject.As(new(InvoiceRepository)))
)

And test each one separately.

func TestProcessingBundle(t *testing.T) {
    bundle, err := inject.New(
        ProcessingBundle,
        inject.Replace(processing.NewDevProxy, inject.As(IProxy)),
    )
    
    var dispatcher *processing.Dispatcher
    container.Extract(&dispatcher)
    
    dispatcher.Dispatch(ctx context.Context, thing)
}

Replace

var options []inject.Options

if os.Getenv("ENV") == "dev" {
    options = append(options, inject.Replace(billing.NewInvoiceRepositoryMock), inject.As(new(InvoiceRepository)))
}

container, err := inject.New(options...)

Named definitions

container, err := inject.New{
	inject.Provide(NewDefaultServer, inject.WithName("default")),
	inject.Provide(NewAdminServer, inject.WithName("admin")),
}

var defaultServer *http.Server
var adminServer *http.Server

container.Extract(&defaultServer, inject.Name("default"))
container.Extract(&adminServer, inject.Name("admin"))

Or with struct provider:

// Application
type Application struct {
    Server *http.Server `inject:"default"`
    AdminServer *http.Server `inject:"admin"`
}
container, err := inject.New(
    inject.Provide(NewDefaultServer, inject.WithName("default")), 
    inject.Provide(NewAdminServer, inject.WithName("admin")),
    inject.Provide(&Application)
)

If you don't like tags as much as I do, then look to inject.Exported() provide option.

Use combined provider

For advanced providing use combined provider. It's both - struct and constructor providers.

// ServerProvider
type ServerProvider struct {
	Mux *http.Server `inject:"dude_mux"`
}

// Provide is a container predefined constructor function for *http.Server.
func (p *ServerProvider) Provide() *http.Server {
	return &http.Server{
		Handler: p.Mux,
	}
}

Visualize (Graphviz)

Write visualization into io.Writer. Check out result on graphviz online tool!

    // visualization data target
    buffer := &bytes.Buffer{}
    
    // write container visualization
    container.WriteTo(buffer)

or

    // define github.com/emicklei/*dot.Graph type
    var graph *dot.Graph
    
    // extract graph
    container.Extract(&graph)

    // use
    graph.Write(buffer)
    

This is visualization of container example.

Documentation

Overview

Package inject make your dependency injection easy. Container allows you to inject dependencies into constructors or structures without the need to have specified each argument manually.

Provide

First of all, when creating a new container, you need to describe how to create each instance of a dependency. To do this, use the container option inject.Provide().

container, err := New(
	Provide(NewDependency),
	Provide(NewAnotherDependency)
)

func NewDependency(dependency *pkg.AnotherDependency) *pkg.Dependency {
	return &pkg.Dependency{
		dependency: dependency,
	}
}

func NewAnotherDependency() (*pkg.AnotherDependency, error) {
	if dependency, err = initAnotherDependency(); err != nil {
		return nil, err
	}

	return dependency, nil
}

Now, container knows how to create *pkg.Dependency and *pkg.AnotherDependency. For advanced providing see inject.Provide() and inject.ProvideOption documentation.

Extract

After building a container, it is easy to get any previously provided type. To do this, use the container's Extract() method.

var anotherDependency *pkg.AnotherDependency
if err = container.Extract(&anotherDependency); err != nil {
	// handle error
}

The container collects a dependencies of *pkg.AnotherDependency, creates its instance and places it in a target pointer. For advanced extraction see Extract() and inject.ExtractOption documentation.

Example
package main

import (
	"log"
	"net/http"
	"os"

	"github.com/defval/inject"
)

func main() {
	// build container
	container, err := inject.New(
		// inject constructor
		inject.Provide(NewLogger),
		inject.Provide(NewServer),

		// inject as interface
		inject.Provide(NewRouter,
			inject.As(new(http.Handler)), // *http.Server mux implements http.Handler interface
		),

		// controller interface group
		inject.Provide(&AccountController{},
			inject.As(new(Controller)), // add AccountController to controller group
			inject.WithName("account"),
			inject.Exported(), // inject all exported fields
		),
		inject.Provide(&AuthController{},
			inject.As(new(Controller)), // add AuthController to controller group
			inject.WithName("auth"),
			inject.Exported(), // inject all exported fields
		),
	)

	// build error
	if err != nil {
		panic(err)
	}

	// extract server from container
	var server *http.Server
	if err = container.Extract(&server); err != nil {
		panic(err)
	}

}

// NewLogger
func NewLogger() *log.Logger {
	logger := log.New(os.Stdout, "", 0)
	defer logger.Println("Logger loaded")

	return logger
}

// NewServer
func NewServer(logger *log.Logger, handler http.Handler) *http.Server {
	defer logger.Println("Server created!")
	return &http.Server{
		Handler: handler,
	}
}

// NewRouter
func NewRouter(logger *log.Logger, controllers []Controller) *http.ServeMux {
	logger.Println("Create router")
	defer logger.Println("Router created!")

	mux := &http.ServeMux{}

	for _, ctrl := range controllers {
		ctrl.RegisterRoutes(mux)
	}

	return mux
}

// Controller
type Controller interface {
	RegisterRoutes(mux *http.ServeMux)
}

// AccountController
type AccountController struct {
	Logger *log.Logger
}

// RegisterRoutes
func (c *AccountController) RegisterRoutes(mux *http.ServeMux) {
	c.Logger.Println("AccountController registered!")

	// register your routes
}

// AuthController
type AuthController struct {
	Logger *log.Logger
}

// RegisterRoutes
func (c *AuthController) RegisterRoutes(mux *http.ServeMux) {
	c.Logger.Println("AuthController registered!")

	// register your routes
}
Output:

Logger loaded
Create router
AccountController registered!
AuthController registered!
Router created!
Server created!

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrTypeNotProvided = graph.ErrTypeNotProvided
)

Functions

This section is empty.

Types

type Container

type Container struct {
	// contains filtered or unexported fields
}

Container is a dependency injection container.

func New

func New(options ...Option) (_ *Container, err error)

New creates a new container with provided options.

func (*Container) Extract

func (c *Container) Extract(target interface{}, options ...ExtractOption) (err error)

Extract populates given target pointer with type instance provided in the container.

var server *http.Server
if err = container.Extract(&server); err != nil {
  // extract failed
}

If the target type does not exist in a container or instance type building failed, Extract() returns an error. Use ExtractOption for modifying the behavior of this function.

func (*Container) WriteTo added in v1.3.0

func (c *Container) WriteTo(w io.Writer) (n int64, err error)

WriteTo writes container entities as a graphviz dot nodes to writer, like https://raw.githubusercontent.com/defval/inject/master/graph.png.

type ExtractOption

type ExtractOption interface {
	// contains filtered or unexported methods
}

ExtractOption modifies default extract behavior. See inject.Name().

func Name

func Name(name string) ExtractOption

Name specify definition name.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option configures container. See inject.Provide(), inject.Bundle(), inject.Replace().

func Bundle

func Bundle(options ...Option) Option

Bundle group together container options.

accountBundle := inject.Bundle(
  inject.Provide(NewAccountController),
  inject.Provide(NewAccountRepository),
)

authBundle := inject.Bundle(
  inject.Provide(NewAuthController),
  inject.Provide(NewAuthRepository),
)

container, _ := New(
  accountBundle,
  authBundle,
)

func Provide

func Provide(provider interface{}, options ...ProvideOption) Option

Provide returns container option that explains how to create an instance of a type inside a container.

The first argument is the provider. The provider can be constructor function, a pointer to a structure (or just structure) or everything else. There are some differences between these providers.

A constructor function is a function that creates an instance of the required type. It can take an unlimited number of arguments needed to create an instance - the first returned value.

func NewServer(mux *http.ServeMux) *http.Server {
  return &http.Server{
    Handle: mux,
  }
}

Optionally, you can return a initializing error.

func NewServer(mux *http.ServeMux) (*http.Server, err error) {
  if time.Now().Day = 1 {
    return nil, errors.New("the server is down on the first day of a month")
  }
  return &http.Server{
    Handler: mux,
  }
}

Other function signatures will cause error.

For advanced providing use inject.Provider.

type AdminServerProvider struct {
  inject.Provider

  AdminMux http.Handler `inject:"admin"` // use named definition
}

func (p *AdminServerProvider) Provide() *http.Server {
  return &http.Server{
    Handler: p.AdminMux,
  }
}

func Replace

func Replace(provider interface{}, options ...ProvideOption) Option

Replace replaces a already provided definition to another one. This method also works like Provide(). The difference is that Replace() replaces already provided definition. The method returns an error when the container does not provide a replaceable definition.

You may replace concrete provided type to another one.

inject.New(
  inject.Provide(&http.Server{Addr: ":80"}),
  inject.Replace(&http.Server{Addr: ":8080"}),
)

Alternatively, it may replace one interface implementation to another one.

inject.New(
  inject.Provide(&http.ServeMux{}, inject.As(new(http.Handler))),
  inject.Replace(&mux.AnotherMux{}, inject.As(new(http.Handler))),
)

type ProvideOption

type ProvideOption interface {
	// contains filtered or unexported methods
}

ProvideOption modifies default provide behavior. See inject.WithName(), inject.As(), inject.Exported().

func As

func As(ifaces ...interface{}) ProvideOption

As specifies interfaces that implement provider instance. Provide with As() automatically checks that instance implements interface and creates slice group with it.

Provide(&http.ServerMux{}, inject.As(new(http.Handler)))

var handler http.Handler
container.Extract(&handler) // extract as interface

var handlers []http.Handler
container.Extract(&handlers) // extract group

func Exported

func Exported() ProvideOption

Exported indicates that all public fields of the structure should be injected.

type AccountController struct {
  Accounts AccountRepository // will be injected without tag 'inject'
}

inject.Provide(NewAccountRepository, inject.As(new(AccountRepository)))
inject.Provide(&AccountController{}, inject.Exported())

Also works with inject.Provider structures.

func WithName

func WithName(name string) ProvideOption

WithName sets string identifier for provided value.

inject.Provide(&http.Server{}, inject.WithName("first"))
inject.Provide(&http.Server{}, inject.WithName("second"))

container.Extract(&server, inject.Name("second"))

type Provider added in v1.2.0

type Provider struct {
}

Provider helper struct that indicates that structure is injection provider.

func (*Provider) IsInjectProvider added in v1.2.0

func (p *Provider) IsInjectProvider()

IsInjectProvider ...

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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