Documentation ¶
Overview ¶
Package goldi implements a lazy dependency injection framework for go. Goldi is MIT-Licensed
Example ¶
package main import ( "net/http" "github.com/fgrosse/goldi" "github.com/fgrosse/goldi/validation" ) func main() { // create a new container when your application loads registry := goldi.NewTypeRegistry() config := map[string]interface{}{ "some_parameter": "Hello World", "timeout": 42.7, } container := goldi.NewContainer(registry, config) // now define the types you want to build using the di container // you can use simple structs container.RegisterType("logger", &SimpleLogger{}) container.RegisterType("api.geo.client", new(GeoClient), "http://example.com/geo:1234") // you can also use factory functions and parameters container.RegisterType("acme_corp.mailer", NewAwesomeMailer, "first argument", "%some_parameter%") // dynamic or static parameters and references to other services can be used as arguments container.RegisterType("renderer", NewRenderer, "@logger") // closures and functions are also possible container.Register("http_handler", goldi.NewFuncType(func(w http.ResponseWriter, r *http.Request) { // do amazing stuff })) // once you are done registering all your types you should probably validate the container validator := validation.NewContainerValidator() validator.MustValidate(container) // will panic, use validator.Validate to get the error // whoever has access to the container can request these types now logger := container.MustGet("logger").(LoggerInterface) logger.DoStuff("...") // in the tests you might want to exchange the registered types with mocks or other implementations container.RegisterType("logger", NewNullLogger) // if you already have an instance you want to be used you can inject it directly myLogger := NewNullLogger() container.InjectInstance("logger", myLogger) } type LoggerInterface interface { DoStuff(message string) string } type SimpleLogger struct { Name string } func (l *SimpleLogger) DoStuff(input string) string { return input } type NullLogger struct{} func NewNullLogger() *NullLogger { return &NullLogger{} } func (l *NullLogger) DoStuff(input string) string { return input } type AwesomeMailer struct { arg1, arg2 string } func NewAwesomeMailer(arg1, arg2 string) *AwesomeMailer { return &AwesomeMailer{arg1, arg2} } type Renderer struct { logger *LoggerInterface } func NewRenderer(logger *LoggerInterface) *Renderer { return &Renderer{logger} } type GeoClient struct { BaseURL string }
Output:
Index ¶
- func IsParameter(p string) bool
- func IsParameterOrTypeReference(p string) bool
- func IsTypeReference(p string) bool
- func IsValid(t TypeFactory) bool
- type Container
- type ParameterResolver
- type StringSet
- type TypeConfigurator
- type TypeFactory
- func NewAliasType(typeID string) TypeFactory
- func NewConfiguredType(embeddedType TypeFactory, configuratorTypeID, configuratorMethod string) TypeFactory
- func NewFuncReferenceType(typeID, functionName string) TypeFactory
- func NewFuncType(function interface{}) TypeFactory
- func NewInstanceType(instance interface{}) TypeFactory
- func NewProxyType(typeID, functionName string, args ...interface{}) TypeFactory
- func NewStructType(structT interface{}, structParameters ...interface{}) TypeFactory
- func NewType(factoryFunction interface{}, factoryParameters ...interface{}) TypeFactory
- type TypeID
- type TypeReferenceError
- type TypeRegistry
- func (r TypeRegistry) InjectInstance(typeID string, instance interface{})
- func (r TypeRegistry) Register(typeID string, typeDef TypeFactory)
- func (r TypeRegistry) RegisterAll(factories map[string]TypeFactory)
- func (r TypeRegistry) RegisterType(typeID string, factory interface{}, arguments ...interface{})
- type UnknownTypeReferenceError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func IsParameter ¶
IsParameter returns whether the given type ID represents a parameter. A goldi parameter is recognized by the leading and trailing percent sign Example: %foobar%
func IsParameterOrTypeReference ¶
IsParameterOrTypeReference is a utility function that returns whether the given string represents a parameter or a reference to a type. See IsParameter and IsTypeReference for further details
func IsTypeReference ¶
IsTypeReference returns whether the given string represents a reference to a type. A goldi type reference is recognized by the leading @ sign. Example: @foobar
func IsValid ¶
func IsValid(t TypeFactory) bool
IsValid checks if a given type factory is valid. This function can be used to check the result of functions like NewType
Types ¶
type Container ¶
type Container struct { TypeRegistry Config map[string]interface{} Resolver *ParameterResolver // contains filtered or unexported fields }
Container is the dependency injection container that can be used by your application to define and get types.
Basically this is just a TypeRegistry with access to the application configuration and the knowledge of how to build individual services. Additionally this implements the laziness of the DI using a simple in memory type cache
You must use goldi.NewContainer to get a initialized instance of a Container!
Example ¶
registry := goldi.NewTypeRegistry() config := map[string]interface{}{} container := goldi.NewContainer(registry, config) container.Register("logger", goldi.NewType(NewNullLogger)) l := container.MustGet("logger") fmt.Printf("%T", l)
Output: *goldi_test.NullLogger
func NewContainer ¶
func NewContainer(registry TypeRegistry, config map[string]interface{}) *Container
NewContainer creates a new container instance using the provided arguments
func (*Container) Get ¶
Get retrieves a previously defined type or an error. If the requested typeID has not been registered before or can not be generated Get will return an error.
For your dependency injection to work properly it is important that you do only try to assert interface types when you use Get(..). Otherwise it might be impossible to assert the correct type when you change the underlying type implementations. Also make sure your application is properly tested and defers some panic handling in case you forgot to define a service.
See also Container.MustGet
Example ¶
registry := goldi.NewTypeRegistry() config := map[string]interface{}{} container := goldi.NewContainer(registry, config) container.Register("logger", goldi.NewType(NewNullLogger)) l, err := container.Get("logger") if err != nil { fmt.Println(err.Error()) return } // do stuff with the logger. usually you need a type assertion fmt.Printf("%T", l.(*NullLogger))
Output: *goldi_test.NullLogger
type ParameterResolver ¶
type ParameterResolver struct {
Container *Container
}
The ParameterResolver is used by type factories to resolve the values of the dynamic factory arguments (parameters and other type references).
func NewParameterResolver ¶
func NewParameterResolver(container *Container) *ParameterResolver
NewParameterResolver creates a new ParameterResolver and initializes it with the given Container. The container is used when resolving parameters and the type references.
func (*ParameterResolver) Resolve ¶
func (r *ParameterResolver) Resolve(parameter reflect.Value, expectedType reflect.Type) (reflect.Value, error)
Resolve takes a parameter and resolves any references to configuration parameter values or type references. If the type of `parameter` is not a parameter or type reference it is returned as is. Parameters must always have the form `%my.beautiful.param%. Type references must have the form `@my_type.bla`. It is also legal to request an optional type using the syntax `@?my_optional_type`. If this type is not registered Resolve will not return an error but instead give you the null value of the expected type.
type StringSet ¶ added in v1.0.1
type StringSet map[string]struct{}
A StringSet represents a set of strings.
type TypeConfigurator ¶
The TypeConfigurator is used to configure a type after its instantiation. You can specify a function in another type that is known to the container. The type instance is passed to the configurator type, allowing the configurator to do whatever it needs to configure the type after its creation.
A TypeConfigurator can be used, for example, when you have a type that requires complex setup based on configuration settings coming from different sources. Using an external configurator, you can decouple the setup logic from the business logic of the corresponding type to keep it DRY and easy to maintain. Also this way its easy to exchange setup logic at run time for example on different environments.
Another interesting use case is when you have multiple objects that share a common configuration or that should be configured in a similar way at runtime.
func NewTypeConfigurator ¶
func NewTypeConfigurator(configuratorTypeID, methodName string) *TypeConfigurator
NewTypeConfigurator creates a new TypeConfigurator
func (*TypeConfigurator) Configure ¶
func (c *TypeConfigurator) Configure(thing interface{}, container *Container) error
Configure will get the configurator type and ass `thing` its configuration function. The method returns an error if thing is nil, the configurator type is not defined or the configurators function does not exist.
type TypeFactory ¶
type TypeFactory interface { // Arguments returns all arguments that are used to generate the type. // This enables the container validator to check if all required parameters exist // and if there are circular type dependencies. Arguments() []interface{} // Generate will instantiate a new instance of the according type or return an error. Generate(parameterResolver *ParameterResolver) (interface{}, error) }
A TypeFactory is used to instantiate a certain type.
func NewAliasType ¶
func NewAliasType(typeID string) TypeFactory
NewAliasType create a new TypeFactory which just serves as alias to the given type ID.
A call to an alias type will retrieve the aliased type as if it was requested via container.Get(typeID) This method will always return a valid type and works bot for regular type references (without leading @) and references to type functions.
Goldigen yaml syntax example:
type_that_is_aliased: alias: "@some_type" // container.Get("type_that_is_aliased") will now return "some_type" instead
Goldigen yaml syntax example with function reference:
func_type_that_is_aliased: alias: "@some_type::DoStuff"
Example ¶
container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{}) container.Register("logger", goldi.NewStructType(SimpleLogger{})) container.Register("default_logger", goldi.NewAliasType("logger")) container.Register("logging_func", goldi.NewAliasType("logger::DoStuff")) fmt.Printf("logger: %T\n", container.MustGet("logger")) fmt.Printf("default_logger: %T\n", container.MustGet("default_logger")) fmt.Printf("logging_func: %T\n", container.MustGet("logging_func"))
Output: logger: *goldi_test.SimpleLogger default_logger: *goldi_test.SimpleLogger logging_func: func(string) string
func NewConfiguredType ¶
func NewConfiguredType(embeddedType TypeFactory, configuratorTypeID, configuratorMethod string) TypeFactory
NewConfiguredType creates a new TypeFactory that decorates a given TypeFactory. The returned configurator will use the decorated type factory first to create a type and then use the resolve the configurator by the given type ID and call the configured method with the instance.
Internally the goldi.TypeConfigurator is used.
The method removes any leading or trailing whitespace from configurator type ID and method. NewConfiguredType will return an invalid type when embeddedType is nil or the trimmed configurator typeID or method is empty.
Goldigen yaml syntax example:
my_type: package: github.com/fgrosse/foobar type: MyType configurator: [ "@my_configurator", Configure ]
Example ¶
container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{}) // this example configurator accepts a Foo type and will set its Value field to the given value configurator := &MyConfigurator{ConfiguredValue: "success!"} // register the configurator under a type ID container.Register("configurator_type", goldi.NewInstanceType(configurator)) // create the type that should be configured embeddedType := goldi.NewStructType(Foo{}) container.Register("foo", goldi.NewConfiguredType(embeddedType, "configurator_type", "Configure")) fmt.Println(container.MustGet("foo").(*Foo).Value)
Output: success!
func NewFuncReferenceType ¶
func NewFuncReferenceType(typeID, functionName string) TypeFactory
NewFuncReferenceType returns a TypeFactory that returns a method of another type as method value (function).
Goldigen yaml syntax example:
my_func_type: func: "@some_type::FancyAction"
Example ¶
container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{}) logger := new(SimpleLogger) container.Register("logger", goldi.NewInstanceType(logger)) container.Register("log_func", goldi.NewFuncReferenceType("logger", "DoStuff")) f := container.MustGet("log_func").(func(string) string) fmt.Println(f("Hello World")) // executes logger.DoStuff
Output: Hello World
func NewFuncType ¶
func NewFuncType(function interface{}) TypeFactory
NewFuncType creates a new TypeFactory that will return a method value
Goldigen yaml syntax example:
my_func_type: package: github.com/fgrosse/foobar func: DoStuff
Example ¶
container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{}) // define the type container.Register("my_func", goldi.NewFuncType(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "test" { w.WriteHeader(http.StatusAccepted) } })) // generate it result, err := container.Get("my_func") if err != nil { return } // call it f := result.(func(name string, age int) (bool, error)) ok, err := f("foo", 42) if ok != true || err != nil { panic("!!!") }
Output:
func NewInstanceType ¶
func NewInstanceType(instance interface{}) TypeFactory
NewInstanceType creates a new TypeFactory which will return the given instance on each call to Generate. It will return an invalid type factory if the given instance is nil
You can not generate this type using goldigen
Example ¶
container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{}) myInstance := new(SimpleLogger) myInstance.Name = "Foobar" // you can configure the instance in your code // now register this instance as a type container.Register("logger", goldi.NewInstanceType(myInstance)) // each reference to the "logger" type will now be resolved to that instance fmt.Println(container.MustGet("logger").(*SimpleLogger).Name)
Output: Foobar
func NewProxyType ¶
func NewProxyType(typeID, functionName string, args ...interface{}) TypeFactory
NewProxyType returns a TypeFactory that uses a function of another type to generate a result.
Goldigen yaml syntax example:
logger: factory: "@logger_provider::GetLogger" args: [ "My Logger" ]
Example ¶
Let's assume that we have a LoggerProvider type that produces configured instances of a Logger each time we call LoggerProvider.GetLogger(loggerName string).
The example shows how to register a `logger` as proxy for a specific call to this LoggerProvider.
container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{}) // register some type as always container.Register("logger_provider", goldi.NewStructType(LoggerProvider{})) // register a proxy type that references the method of previously defined type and append call arguments if any container.Register("logger", goldi.NewProxyType("logger_provider", "GetLogger", "My logger")) l := container.MustGet("logger").(*SimpleLogger) fmt.Printf("%s: %T", l.Name, l)
Output: My logger: *goldi_test.SimpleLogger
func NewStructType ¶
func NewStructType(structT interface{}, structParameters ...interface{}) TypeFactory
NewStructType creates a TypeFactory that can be used to create a new instance of some struct type.
This function will return an invalid type if:
- structT is no struct or pointer to a struct,
- the number of given structParameters exceed the number of field of structT
- the structParameters types do not match the fields of structT
Goldigen yaml syntax example:
logger: package: github.com/fgrosse/foobar type: MyType
Example ¶
container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{}) // all of the following types are semantically identical container.Register("foo_1", goldi.NewStructType(Foo{})) container.Register("foo_2", goldi.NewStructType(&Foo{})) container.Register("foo_3", goldi.NewStructType(new(Foo))) // each reference to the "logger" type will now be resolved to that instance fmt.Printf("foo_1: %T\n", container.MustGet("foo_1")) fmt.Printf("foo_2: %T\n", container.MustGet("foo_2")) fmt.Printf("foo_3: %T\n", container.MustGet("foo_3"))
Output: foo_1: *goldi_test.Foo foo_2: *goldi_test.Foo foo_3: *goldi_test.Foo
func NewType ¶
func NewType(factoryFunction interface{}, factoryParameters ...interface{}) TypeFactory
NewType creates a new TypeFactory.
This function will return an invalid type if:
- the factoryFunction is nil or no function,
- the factoryFunction returns zero or more than one parameter
- the factoryFunctions return parameter is no pointer, interface or function type.
- the number of given factoryParameters does not match the number of arguments of the factoryFunction
Goldigen yaml syntax example:
my_type: package: github.com/fgrosse/foobar factory: NewType args: - "Hello World" - true
Example ¶
container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{}) // register the type using the factory function NewMockTypeWithArgs and pass two arguments container.Register("my_type", goldi.NewType(NewMockTypeWithArgs, "Hello World", true)) t := container.MustGet("my_type").(*MockType) fmt.Printf("%#v", t)
Output: &goldi_test.MockType{StringParameter:"Hello World", BoolParameter:true}
type TypeID ¶
type TypeID struct {
ID, Raw string
FuncReferenceMethod string
IsOptional bool
IsFuncReference bool
}
TypeID represents a parsed type identifier and associated meta data.
type TypeReferenceError ¶
type TypeReferenceError struct { TypeID string TypeInstance interface{} // contains filtered or unexported fields }
A TypeReferenceError occurs if you tried to inject a type that does not match the function declaration of the corresponding method.
type TypeRegistry ¶
type TypeRegistry map[string]TypeFactory
The TypeRegistry is effectively a map of typeID strings to TypeFactory
func NewTypeRegistry ¶
func NewTypeRegistry() TypeRegistry
NewTypeRegistry creates a new empty TypeRegistry
func (TypeRegistry) InjectInstance ¶
func (r TypeRegistry) InjectInstance(typeID string, instance interface{})
InjectInstance enables you to inject type instances. If instance is nil an error is returned
func (TypeRegistry) Register ¶
func (r TypeRegistry) Register(typeID string, typeDef TypeFactory)
Register saves a type under the given symbolic typeID so it can be retrieved later. It is perfectly legal to call Register multiple times with the same typeID. In this case you overwrite existing type definitions with new once
func (TypeRegistry) RegisterAll ¶
func (r TypeRegistry) RegisterAll(factories map[string]TypeFactory)
RegisterAll will register all given type factories under the mapped type ID It uses TypeRegistry.Register internally
func (TypeRegistry) RegisterType ¶
func (r TypeRegistry) RegisterType(typeID string, factory interface{}, arguments ...interface{})
RegisterType is convenience method for TypeRegistry.Register It tries to create the correct TypeFactory and passes this to TypeRegistry.Register This function panics if the given generator function and arguments can not be used to create a new type factory.
type UnknownTypeReferenceError ¶
type UnknownTypeReferenceError struct { TypeID string // contains filtered or unexported fields }
The UnknownTypeReferenceError occurs if you try to get a type by an unknown type id (type has not been registered).
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
The goldigen binary See https://github.com/fgrosse/goldi#the-goldigen-binary
|
The goldigen binary See https://github.com/fgrosse/goldi#the-goldigen-binary |
Package validation provides simple validation of goldi containers
|
Package validation provides simple validation of goldi containers |