Simple dependency injection container for Golang.
With go mod
go get github.com/bassbeaver/gioc
With Dep:
dep ensure --add github.com/bassbeaver/gioc
Container operates with factories and each factory knows how to build it's service. If some service depends on other service - factory have to declare that dependency as it's (factories) dependency and Container will inject it to factory, so the factory will be able to inject it into the service.
Factory (see Factory pattern) is a thing which knows how to create service. Factory can be one of two types:
- Function. This function must return only one parameter and this parameter must be a pointer to new service instance.
- Factory struct from
gioc
package. Factory struct has next definition:
type Factory struct {
Arguments []string
Create interface{}
}
Create
must be a function which knows how to create service (requirements to this function are same as for function from p.1).
Arguments
is an array of definitions for Create
function arguments. N-th element of Arguments
array is for N-th argument of Create
function.
Each argument definition is a string and is interpreted in next ways:
- If first symbol of this string is
@
- this definition is interpreted as service alias, so Container will try to find service with that alias - In other cases definition string is interpreted as value for corresponding argument of
Create
function.
Container tries to cast values of Arguments
to required type, if cast failed - Container panics.
Example:
type Service struct {
Field1 string
Field2 int
}
factory := Factory {
Create: func(f1 string, f2 int) *Service {
return &Service{Field1: f1, Field2: f2}
},
Arguments: []string{"field 1", "123"},
},
Where value of Arguments[0]
("field 1")
will be passed to factory.Create()
as argument f1
and Arguments[1]
("123")
will be passed to factory.Create()
as argument f2
.
Container tries to cast values of Arguments
to required type ("field 1"
will be cast to string
and "123"
will be cast to int
). If cast fails - Container panics.
To create container you should use gioc.NewContainer()
method.
To register a service factory you can use next methods of Container:
RegisterServiceFactoryByAlias(serviceAlias string, factory interface{}, enableCaching bool)
Where:
serviceAlias
is string alias (name) which will be used to retrieve this service from containerfactory
is the factory which knows how to create that service (see Factory).enableCaching
flag to control service caching after instantiation. IfenableCaching = true
- Container will instantiate this service only once, and save instantiated instance to cache. For all further requests (GetByAlias
orGetByObject
calls) Container will return cached service. If IfenableCaching = false
- Container will instantiate service for every request (GetByAlias
orGetByObject
call).
RegisterServiceFactoryByObject(serviceObj interface{}, factory interface{}, enableCaching bool)
Where:
serviceObj
is instance of type of service (really, it should be a pointer to that type)factory
andenableCaching
- seeRegisterServiceFactoryByAlias
Service can have multiple aliases. To add new alias for registered service you can use:
AddServiceAlias(existingAlias, newAlias string)
To get service from Container you can use:
GetByAlias(alias string) interface{}
GetByObject(serviceObj interface{}) interface{}
For now all instantiated services are cached, so for the first call of GetByAlias
or GetByObject
service is instantiated
and putted into cache and for every next call you will get service from cache.
It is important to avoid cycles in service dependencies. Container has CheckCycles() method to check dependency cycles.
First returning parameter of CheckCycles() is a flag showing cycle presence: true for "no cycles", false for "cycles detected".
Second returning parameter of CheckCycles() contains name of service with detected dependency cycle, if no cycles detected it is empty string.
You can run this check manually or Container will run it automatically at first attempt of service retrieval. In case of automatic run panic will be thrown in case if cycle detected.
import "github.com/bassbeaver/gioc"
type Service struct {
Field1 string
Field2 int
}
container := gioc.NewContainer()
container.RegisterServiceFactoryByAlias(
"service",
func() *Service {
return &Service{Field1: "Field1", Field2: 5,}
},
true,
)
service := container.GetByAlias("service").(*Service)
In this example we registered and retrieved service by string alias.
import "github.com/bassbeaver/gioc"
type Service struct {
Field1 string
Field2 int
}
container := gioc.NewContainer()
container.RegisterServiceFactoryByObject(
(*Service)(nil),
gioc.Factory {
Create: func(f1 string, f2 int) *Service {
return &Service{Field1: f1, Field2: f2,}
},
Arguments: []string{"field 1", "5"},
},
true,
)
service := container.GetByObject((*Service)(nil)).(*Service)
In this example we registered service by object (well, actually, pointer to object).
import "github.com/bassbeaver/gioc"
type Service1 struct {
Field1 string
Field2 int
}
type Service2 struct {
Field1 string
ServiceInstance1 *Service1
}
container := gioc.NewContainer()
container.RegisterServiceFactoryByObject(
(*Service1)(nil),
func() *Service1 {
return &Service1{Field1: "Field1-1", Field2: 5,}
},
true,
).RegisterServiceFactoryByObject(
(*Service2)(nil),
func(s1 *Service1) *Service2 {
return &Service2{Field1: "Field2-1", ServiceInstance1: s1,}
},
true,
).AddServiceAliasByObject((*Service2)(nil), "service2")
service2 := container.GetByObject((*Service2)(nil)).(*Service2)
service2ByAlias := container.GetByAlias("service2").(*Service2)
In this example Container see that Service2
depends on Service1
(factory for Service2
have argument of type *Service1
)
and injects it into that factory.
Also, this example shows how to add alternative alias for service and get service by that alias.
import "github.com/bassbeaver/gioc"
type Service1 struct {
Field1 string
Field2 int
}
type Service2 struct {
Field1 string
ServiceInstance1 *Service1
}
container := gioc.NewContainer()
container.RegisterServiceFactoryByObject(
(*Service1)(nil),
func() *Service1 {
return &Service1{Field1: "Some value", Field2: 0,}
},
true,
).RegisterServiceFactoryByAlias(
"anotherService1",
func() *Service1 {
return &Service1{Field1: "Field1-1", Field2: 5,}
},
true,
).RegisterServiceFactoryByObject(
(*Service2)(nil),
Factory {
Create: func(f1 string, s1 *Service1) *Service2 {
return &Service2{Field1: f1, ServiceInstance1: s1,}
},
Arguments: []string{"field 2-1", "@anotherService1"},
},
true,
)
service2 := container.GetByObject((*Service2)(nil)).(*Service2)
This example shows usage of gioc.Factory
type as the service factory. You can see that two services
with type Service1
was registered - they are different services and can be used separately from each other.
Factory for Service2
configured to use second variant of Service1
(registered with alias "anotherService1"),
so Create
function of Service2
factory will get s1
as: &Service1{Field1: "Field1-1", Field2: 5}