Documentation ¶
Overview ¶
Package sting provides a simple and lightweigtht dependency injection for go.
See project overview for more information: https://bitbucket.org/snmed/sting/overview
Example ¶
This file contains only prerequisites for all other examples. This is necessary because of the behavior of godoc. If an example has any type or function definition in it, godoc does not show a separate block for the output of the example.
package main import ( "fmt" "log" "net/http" "strconv" "bitbucket.org/snmed/sting" ) type ( PersonRepository interface { Find(int) *Person FindByName(string) *Person } PersonAccess struct { name string id int } Config struct { DefaultName string DefaultId int } Person struct { Id int Name string } Log struct { logger log.Logger } ) func (p PersonAccess) Find(id int) *Person { return &Person{Name: p.name, Id: id} } func (p PersonAccess) FindByName(name string) *Person { return &Person{Name: name, Id: p.id} } func (l *Log) Info(msg string) { l.logger.Println(fmt.Sprintf("Info: %v", msg)) } func (l *Log) Error(msg string) { l.logger.Println(fmt.Sprintf("Error: %v", msg)) } func createMyConfig() (*Config, error) { return &Config{DefaultId: 11, DefaultName: "Selene"}, nil } type Controller struct { config *Config MyConfig Config `di:"name=myconfig"` ignored PersonRepository `di:"ignore"` repo PersonRepository `di:"func=SetRepo"` Access PersonAccess `di:"func=SetPersonAccess,name=myconfig"` } func (c *Controller) SetConfig(cfg *Config) { c.config = cfg } func (c *Controller) SetRepo(r PersonRepository) { c.repo = r } func (c *Controller) SetPersonAccess(cfg Config) { c.Access = PersonAccess{id: cfg.DefaultId, name: cfg.DefaultName} } func LogMiddleware(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Do some useful logging fmt.Printf("Calling URL %v with method %v ...\n", r.URL, r.Method) handler.ServeHTTP(w, r) }) } func FindPerson(w http.ResponseWriter, r *http.Request, repo PersonRepository, pers Person) { if id, err := strconv.Atoi(r.FormValue("id")); err == nil { w.Write([]byte(fmt.Sprintf("%v", repo.Find(id)))) } else { w.Write([]byte(fmt.Sprintf("%v", pers))) } } func ChangeCfgMiddleware(c sting.Container, handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // If GetService or Inject is used inside a handler func, it's // necessary to pass the request context to those functions. cfg, _, _ := c.GetService((*Config)(nil), r.Context()) cfg.(*Config).DefaultName = "Pumuckel" handler.ServeHTTP(w, r) }) } // This file contains only prerequisites for all other examples. This is // necessary because of the behavior of godoc. If an example has any // type or function definition in it, godoc does not show a separate block // for the output of the example. func main() { // This file contains only prerequisites for all other examples. }
Output:
Example (GetService) ¶
This example shows how to register multiple services with the same type and how to retrieve services from the di container.
container, err := sting.NewBuilder(). // Register a pointer to struct with a container scope Register(&Config{DefaultId: 42, DefaultName: "Judge Dredd"}, sting.ContainerScope()). // Register a creator function with a dependency and container scope Register( func(c Config) (PersonRepository, error) { return PersonAccess{id: c.DefaultId, name: c.DefaultName}, nil }, sting.RequestScope(), ). // Register a named service with an already registered type RegisterNamed( "myconfig", func() (*Config, error) { return &Config{DefaultId: 11, DefaultName: "Selene"}, nil }, sting.RequestScope(), ). Build() if err != nil { log.Fatalln(err) } //Get a service by passing a nil pointer to an interface svc, _, err := container.GetService((*PersonRepository)(nil)) if err != nil { log.Fatalln(err) } // Get a service by passing a nil pointer to a struct cfg, _, err := container.GetService((*Config)(nil)) if err != nil { log.Fatalln(err) } // Get a named service mycfg, ctx, err := container.GetService("myconfig") if err != nil { log.Fatalln(err) } // Change values mycfg.(*Config).DefaultName = "Tom Bombadil" mycfg.(*Config).DefaultId = 9999 // Get the same named service by passing the context cfg2, _, err := container.GetService("myconfig", ctx) if err != nil { log.Fatalln(err) } // Get the named service without a context, same as a new request. cfg3, _, err := container.GetService("myconfig") if err != nil { log.Fatalln(err) } fmt.Println(svc.(PersonRepository).Find(42)) fmt.Println(cfg) fmt.Println(mycfg) fmt.Println(cfg2) fmt.Println(cfg3)
Output: &{42 Judge Dredd} &{Judge Dredd 42} &{Tom Bombadil 9999} &{Tom Bombadil 9999} &{Selene 11}
Example (HandlerFunc) ¶
This example shows how to use the di container in a web application or a microservice. Wrap any InjectionHandler inside a container.HandlerFunc. A InjectionHandler is in form of a http.Handler, but can have as many additional arguments as needed, as far as the argument types are one of the supported di types.
container, err := sting.NewBuilder(). // Register a pointer to struct with a container scope Register(&Config{DefaultId: 42, DefaultName: "Judge Dredd"}, sting.RequestScope()). // Register a creator function with a dependency and container scope Register( func(c Config) (PersonRepository, error) { return PersonAccess{id: c.DefaultId, name: c.DefaultName}, nil }, sting.RequestScope(), ). // Register a named service with an already registered type Register( func() (*Person, error) { return &Person{Id: 11, Name: "Selene"}, nil }, sting.RequestScope(), ). Build() if err != nil { log.Fatalln(err) } handler := ChangeCfgMiddleware(container, container.HandlerFunc(FindPerson)) handler = LogMiddleware(handler) recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, httptest.NewRequest("GET", "/api?id=55", nil)) fmt.Println(recorder.Body) recorder = httptest.NewRecorder() handler.ServeHTTP(recorder, httptest.NewRequest("GET", "/api", nil)) fmt.Println(recorder.Body)
Output: Calling URL /api?id=55 with method GET ... &{55 Pumuckel} Calling URL /api with method GET ... {11 Selene}
Example (Inject) ¶
This example shows how to inject dependencies into structs and functions.
container, err := sting.NewBuilder(). // Register a pointer to struct with a container scope Register(Config{DefaultId: 42, DefaultName: "Judge Dredd"}, sting.ContainerScope()). // Register a creator function with a dependency and container scope Register( func(c Config) (PersonRepository, error) { return PersonAccess{id: c.DefaultId, name: c.DefaultName}, nil }, sting.RequestScope(), ). // Register a named service with an already registered type RegisterNamed( "myconfig", func() (*Config, error) { return &Config{DefaultId: 11, DefaultName: "Selene"}, nil }, sting.RequestScope(), ). Build() controller := &Controller{} // Inject dependencies into a struct _, err = container.Inject(controller) if err != nil { log.Fatalln(err) } var config *Config var repo PersonRepository // Inject dependencies into a function container.Inject(func(c *Config, r PersonRepository) { config = c config.DefaultId += c.DefaultId config.DefaultName += " changed" repo = r }) fmt.Println(controller.config) fmt.Println(controller.MyConfig) fmt.Println(controller.ignored) fmt.Println(controller.repo) fmt.Println(controller.Access) fmt.Println(config) fmt.Println(repo)
Output: &{Judge Dredd 42} {Selene 11} <nil> {Judge Dredd 42} {Selene 11} &{Judge Dredd changed 84} {Judge Dredd 42}
Example (Register) ¶
This example shows how to register services and dependencies. Register requires as first argument a struct, pointer to struct or a creator function and as second argument a scope.
RegisterNamed requires as first argument a string under which the service will be registered, the other two arguments are the same as for the Register function.
container, err := sting.NewBuilder(). // Register a struct with a transient scope Register(Log{}, sting.TransientScope()). // Register a pointer to struct with a container scope Register(&Config{DefaultId: 42, DefaultName: "Judge Dredd"}, sting.ContainerScope()). // Register a creator function with a dependency and container scope Register( func(c Config) (PersonRepository, error) { return PersonAccess{id: c.DefaultId, name: c.DefaultName}, nil }, sting.ContainerScope(), ). // Register a named service with an already registered type RegisterNamed( "myconfig", createMyConfig, sting.RequestScope(), ). Build() if err != nil { log.Fatalln(err) } // Get a service by passing a nil pointer to an interface svc, _, err := container.GetService((*PersonRepository)(nil)) if err != nil { log.Fatalln(err) } fmt.Println(svc.(PersonRepository).Find(42))
Output: &{42 Judge Dredd}
Example (SubContainer) ¶
This example shows how to use the di container in a web application or a microservice. If request scope is used, wrap first handler of a handler chain within the UseHTTPRequestContext middleware. Afterwards wrap any InjectionHandler inside a container.HandlerFunc.
// Create the first builder and register services builder := sting.NewBuilder(). Register(&Config{DefaultId: 42, DefaultName: "Judge Dredd"}, sting.TransientScope()). Register( func(c Config) (PersonRepository, error) { return PersonAccess{id: c.DefaultId, name: c.DefaultName}, nil }, sting.RequestScope(), ). Register( func() (*Person, error) { return &Person{Id: 11, Name: "Selene"}, nil }, sting.RequestScope(), ) // Create a child builder and register services childBuilder := builder.NewBuilder(). Register( func(c Config) (PersonRepository, error) { return PersonAccess{id: 2 * c.DefaultId, name: "Child " + c.DefaultName}, nil }, sting.ContainerScope(), ) // Important build parent builder first container, perr := builder.Build() subcontainer, serr := childBuilder.Build() if perr != nil || serr != nil { log.Fatalln(perr, serr) } // Now use containers in handler chains parentHandler := LogMiddleware(container.HandlerFunc(FindPerson)) subHandler := LogMiddleware(subcontainer.HandlerFunc(FindPerson)) precorder := httptest.NewRecorder() parentHandler.ServeHTTP(precorder, httptest.NewRequest("GET", "/api?id=55", nil)) fmt.Println(precorder.Body) precorder = httptest.NewRecorder() parentHandler.ServeHTTP(precorder, httptest.NewRequest("GET", "/api", nil)) fmt.Println(precorder.Body) srecorder := httptest.NewRecorder() subHandler.ServeHTTP(srecorder, httptest.NewRequest("GET", "/api?id=55", nil)) fmt.Println(srecorder.Body) srecorder = httptest.NewRecorder() subHandler.ServeHTTP(srecorder, httptest.NewRequest("GET", "/api", nil)) fmt.Println(srecorder.Body)
Output: Calling URL /api?id=55 with method GET ... &{55 Judge Dredd} Calling URL /api with method GET ... {11 Selene} Calling URL /api?id=55 with method GET ... &{55 Child Judge Dredd} Calling URL /api with method GET ... {11 Selene}
Index ¶
- Constants
- func DefaultScopeLifeTimeFunc(s Scope) int
- func IsActionFuncReturnedNil(e error) bool
- func IsContainerAlreadyBuilt(e error) bool
- func IsCycleDetected(e error) bool
- func IsInvalidCreatorFunc(e error) bool
- func IsInvalidInjectionHandler(e error) bool
- func IsInvalidInjectionTarget(e error) bool
- func IsInvalidServiceRequest(e error) bool
- func IsLifetimeViolation(e error) bool
- func IsMissingDependency(e error) bool
- func IsParentContainerNotBuilt(e error) bool
- func IsServiceAlreadyRegistered(e error) bool
- func IsServiceNotFound(e error) bool
- func IsUnexpectedError(e error) bool
- type ActionFunc
- type Builder
- type Configure
- type Container
- type ContainerID
- type InjectionHandler
- type Options
- type Scope
- type ScopeLifeTimeFunc
Examples ¶
Constants ¶
const VERSION = "1.0.0"
VERSION is the current sting version
Variables ¶
This section is empty.
Functions ¶
func DefaultScopeLifeTimeFunc ¶
DefaultScopeLifeTimeFunc is the default ScopeLifeTimeFunc for a Builder instance.
func IsActionFuncReturnedNil ¶
IsActionFuncReturnedNil checks if an error is caused by an action function which returns nil.
func IsContainerAlreadyBuilt ¶
IsContainerAlreadyBuilt checks if an error is caused by registering a service on an already built container.
func IsCycleDetected ¶
IsCycleDetected checks if an error is caused by a cycle in the dependency graph.
func IsInvalidCreatorFunc ¶
IsInvalidCreatorFunc checks if an error is caused by an invalid creator function.
func IsInvalidInjectionHandler ¶
IsInvalidInjectionHandler checks if an error is caused by an invalid injection handler.
func IsInvalidInjectionTarget ¶
IsInvalidInjectionTarget checks if an error is caused by a call to Inject with an invalid target.
func IsInvalidServiceRequest ¶
IsInvalidServiceRequest checks if an error is caused by a call to GetService with an invalid argument.
func IsLifetimeViolation ¶
IsLifetimeViolation checks if an error is caused by a lifetime violation.
func IsMissingDependency ¶
IsMissingDependency checks if an error is caused by a missing dependency.
func IsParentContainerNotBuilt ¶
IsParentContainerNotBuilt checks if an error is caused by calling Build on sub container with a not build parent container.
func IsServiceAlreadyRegistered ¶
IsServiceAlreadyRegistered checks if an error is caused by registering a service twice.
func IsServiceNotFound ¶
IsServiceNotFound checks if an error is caused by a unresolved dependency.
func IsUnexpectedError ¶
IsUnexpectedError checks if an error is an unexpected error.
Types ¶
type ActionFunc ¶
ActionFunc executes initialisation work and returns an instance of a dependency object.
type Builder ¶
type Builder interface { // Register registers a creator function and deduces its service name with // reflection from the first output parameter of the creator function. A creator // function has the form: // // func((arg type)*) (type, error) { /* construct return type */ } // // The function can have zero or more arguments, but only two return types whereby the // last one must be the error interface. The 'type' must be one of struct, pointer to struct or // interface. It's also possible to register a struct or pointer to struct directly, see examples // for more information. Caveat: It is not possible to register a pointer to struct and a struct // of the same type, this might be changed in a future release. Register(interface{}, Scope) Builder // RegisterNamed registers a given creator function with a specified name and scope. // It's possible to register the same type with different names. Also it's possible to register // a type that is already registered with the 'Register' function. It's also possible // to register a struct or pointer to struct directly, see examples for more information. RegisterNamed(string, interface{}, Scope) Builder // Build builds and verifies all registered services. It returns an error if any of // this cases occurs: // // - Invalid creator function passed // - Service already registered // - Missing dependency for creator function // - Container already built // - Parent container not built // - If creator function uses dependency with a lower life time level Build() (Container, error) // NewBuilder returns a new child builder, so you can overwrite some // dependencies in its parent builder or register additional dependencies. // It is necesseray to build a parent container first, before building the child // container. The child container lookup dependencies first in its own registry, // if it can't find a requested service or dependency, it will ask its parent container // for any missing item by calling GetService on it. NewBuilder() Builder }
The Builder interface is responsible to register dependencies and to build the di container.
func NewBuilder ¶
NewBuilder returns a preconfigured Builder ready to use.
type Configure ¶
type Configure func(*Options)
Configure changes options of the Builder instance.
func UseLifeTimeCheck ¶
UseLifeTimeCheck enables or disables lifetime checks during container build process.
func UseScopeLifeTimeFunc ¶
func UseScopeLifeTimeFunc(fn ScopeLifeTimeFunc) Configure
UseScopeLifeTimeFunc configures the Builder to use the supplied function to check life time violations. This is useful if a custom scope is used and overwrites the default life time function.
type Container ¶
type Container interface { // ID returns the unique identifier for this Container. ID() ContainerID // Injects dependencies into a struct or function, accepts pointer to struct and functions. // Request scope needs a context as second argument, otherwise it works like a transient scope. // Only the first context arguemnt is used, all additional passed contexts will be ignored. Inject(interface{}, ...context.Context) (context.Context, error) // Gets a service, accepts only pointer to interface with a nil value ex. (*io.Closer)(nil), // pointer to struct or a string for a named service. It returns the interface or a pointer to a struct. // Request scope needs a context as second argument, otherwise it works like a transient scope. // Only the first context arguemnt is used, all additional passed contexts will be ignored. GetService(interface{}, ...context.Context) (interface{}, context.Context, error) // Injects all dependencies into a InjectionHandler. Returns a http.Handler. // Panics if any dependency is missing in the container. // Request scope requires the use of the UseHTTPRequestContext middleware. HandlerFunc(InjectionHandler) http.Handler }
Container is responsible to resolve, construct and inject dependencies.
type ContainerID ¶
type ContainerID string
ContainerID is an unique container identifier which differs for every container.
type InjectionHandler ¶
type InjectionHandler interface{}
InjectionHandler is a HandlerFunc with an arbitrary amount of additional parameters. All additional parameters must be of type struct, pointer to struct or interface. InjectionHandler must be a function in form of:
func(http.ResponseWriter, *http.Request, [additional parameters...] )
type Options ¶
type Options struct {
// contains filtered or unexported fields
}
Options is used to configure the Builder instance.
type Scope ¶
type Scope interface {
Get(context.Context, interface{}, ActionFunc) (interface{}, error)
}
Scope provides a storage for dependency objects and manages their lifetime.
func ContainerScope ¶
func ContainerScope() Scope
ContainerScope executes on first call the ActionFunc and returns the same instance for all subsequent calls.
func RequestScope ¶
func RequestScope() Scope
RequestScope executes on first call the ActionFunc and returns for all subsequent calls in the same request the created instance.
func TransientScope ¶
func TransientScope() Scope
TransientScope executes the ActionFunc for each call and returns the created instance.
type ScopeLifeTimeFunc ¶
ScopeLifeTimeFunc returns the life time level of a scope. Dependencies defined within a certain level must not have any dependencies defined in a higher level.