inject

package module
v2.0.0-beta.5 Latest Latest
Warning

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

Go to latest
Published: Nov 26, 2019 License: MIT Imports: 1 Imported by: 4

README

Tweet

Documentation Release Build Status Code Coverage Contributors

How will dependency injection help me?

Dependency injection is one form of the broader technique of inversion of control. It is used to increase modularity of the program and make it extensible.


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

Installing

go get -u github.com/defval/inject/v2

Documentation

Providing

Let's code a simple application that processes HTTP requests. For this, we need a server and a router. We take the server and mux from the standard library.

// NewServer creates a new http server with provided handler. 
func NewServer(mux *http.ServeMux) *http.Server {
	return &http.Server{
		Handler: mux,
	}
}

// NewServeMux creates a new http serve mux.
func NewServeMux() *http.ServeMux {
	return &http.ServeMux{}
}

Now let's teach a container to build these types.

// Collect container parameters, build and compile container.
container := inject.New(
	inject.Provide(NewServer),  // provide http server
	inject.Provide(NewServeMux) // provide http serve mux
)

Extraction

Now, we can extract the built server from the container. For this, define the variable of extracted type and pass variable pointer to Extract function.

var server *http.Server
err := container.Extract(&server)

If extracted type not found or the process of building instance cause error, Extract return error.

If no error occurred, we can use the variable as if we had built it yourself.

Inversion of control

Let's look on the code without dependency injection container and with it.

Without dependency injection container

You describe types, call a functions and pass the results to other functions. It's obvious and simple:

// data layer
db := NewDatabaseConnection()
profiles := NewProfileRepository(db)
accounts := NewAccountRepository(db)
// api layer
mux := NewServeMux()
server := NewServer(mux)

profileEndpoint := NewProfileEndpoint(mux, profiles)

// func NewProfileEndpoint(mux, profiles) *ProfileEndpoint {
//     endpoint := &ProfileEndpoint{profiles}
//     mux.Get("/profile", endpoint.Retrieve)
//     mux.Post("/profile", endpoint.Create)
// }

accountEndpoint := NewAccountEndpoint(mux, accounts)

// func NewAccountEndpoint(mux, accounts) *AccountEndpoint {
//     endpoint := &AccountEndpoint{accounts}
//     mux.Get("/profile", profileEndpoint.Retrieve)
//     mux.Post("/profile", profileEndpoint.Create)
// }

// messaging layer
userLoggedInSubscription := NewUserLoggedInSubscription(profiles)
// service layer
userInteractor := NewUserInteractor(profiles, accounts)
// start
userLoggedInSubscription.Start()
server.ListenAndServe()
With dependency injection container

You describe types and container decides what dependency are injected in.

container := inject.New(
	// data layer
	inject.Provide(NewDatabaseConnection),
	inject.Provide(NewProfileRepository),
	inject.Provide(NewAccountRepository),
	// api layer
	inject.Provide(NewServeMux),
	
	// func NewServeMux(endpoints []Endpoint) *ServeMux {
	//     mux := &http.ServeMux{}
	//     for _, endpoint := range endpoints {
	//         endpoint.RegisterRoutes(mux)
	//     }
	// }
	
	inject.Provide(NewServer),
	inject.Provide(NewProfileEndpoint, inject.As(new(Endpoint))),
	inject.Provide(NewAccountEndpoint, inject.As(new(Endpoint))),
	
	// type Endpoint interface {
	//     RegisterRoutes(mux *http.ServeMux)
	// }
	
	// messaging layer
	inject.Provide(NewUserLoggedInSubscription),
	// service layer
	inject.Provide(NewUserInteractor),
)

var server *http.Server
container.Extract(&server)
server.ListenAndServe()

Some call it magic, but it's an inversion of control.

Implementation

For a container to know that as an implementation of http.Handler it is necessary to use *http.ServeMux, we use the option inject.As(). The argument of this option must be a pointer to an interface like new(http.Handler). This syntax may seem strange, but I have not found a better way to specify the interface.

inject.Provide(NewServeMux, inject.As(new(http.Handler)))

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/v2"
)

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

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

		// controller interface group
		inject.Provide(NewAccountController,
			inject.As(new(Controller)), // add AccountController to controller group
		),
		inject.Provide(NewAuthController,
			inject.As(new(Controller)), // add AuthController to controller group
		),
	)

	// 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
}

func NewAccountController(logger *log.Logger) *AccountController {
	return &AccountController{Logger: logger}
}

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

	// register your routes
}

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

func NewAuthController(logger *log.Logger) *AuthController {
	return &AuthController{Logger: 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

This section is empty.

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

New creates a new container with provided options.

func (*Container) Cleanup

func (c *Container) Cleanup()

Cleanup

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.

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.dependencyProvider.

type AdminServerProvider struct {
  inject.dependencyProvider

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

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

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 interfaces 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 Prototype

func Prototype() ProvideOption

Prototype

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"))

Directories

Path Synopsis
di

Jump to

Keyboard shortcuts

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