1650068400
Dingo
Dependency injection for go
Dingo works very similar to Guice
Basically one binds implementations/factories to interfaces, which are then resolved by Dingo.
Given that Dingo's idea is based on Guice we use similar examples in this documentation:
The following example shows a BillingService with two injected dependencies. Please note that Go's nature does not allow constructors, and does not allow decorations/annotations beside struct-tags, thus, we only use struct tags (and later arguments for providers).
Also, Go does not have a way to reference types (like Java's Something.class
) we use either pointers or nil
and cast it to a pointer to the interface we want to specify: (*Something)(nil)
. Dingo then knows how to dereference it properly and derive the correct type Something
. This is not necessary for structs, where we can just use the null value via Something{}
.
See the example folder for a complete example.
package example
type BillingService struct {
processor CreditCardProcessor
transactionLog TransactionLog
}
func (billingservice *BillingService) Inject(processor CreditCardProcessor, transactionLog TransactionLog) {
billingservice.processor = processor
billingservice.transactionLog = transactionLog
}
func (billingservice *BillingService) ChargeOrder(order PizzaOrder, creditCard CreditCard) Receipt {
// ...
}
We want the BillingService to get certain dependencies, and configure this in a BillingModule
which implements dingo.Module
:
package example
type BillingModule struct {}
func (module *BillingModule) Configure(injector *dingo.Injector) {
// This tells Dingo that whenever it sees a dependency on a TransactionLog,
// it should satisfy the dependency using a DatabaseTransactionLog.
injector.Bind(new(TransactionLog)).To(DatabaseTransactionLog{})
// Similarly, this binding tells Dingo that when CreditCardProcessor is used in
// a dependency, that should be satisfied with a PaypalCreditCardProcessor.
injector.Bind(new(CreditCardProcessor)).To(PaypalCreditCardProcessor{})
}
Every instance that is created through the container can use injection.
Dingo supports two ways of requesting dependencies that should be injected:
For every requested injection (unless an exception applies) Dingo does the following:
Example: Here is another example using the Inject method for private fields
package example
type MyBillingService struct {
processor CreditCardProcessor
accountId string
}
func (m *MyBillingService) Inject(
processor CreditCardProcessor,
config *struct {
AccountId string `inject:"config:myModule.myBillingService.accountId"`
},
) {
m.processor = CreditCardProcessor
m.accountId = config.AccountId
}
Dingo allows to request the injection of provider instead of instances. A "Provider" for dingo is a function that return a new Instance of a certain type.
package example
type pizzaProvider func() Pizza
func (s *Service) Inject(provider pizzaProvider) {
s.provider = provider
}
If there is no concrete binding to the type func() Pizza
, then instead of constructing one Pizza
instance Dingo will create a new function which, on every call, will return a new instance of Pizza
.
The type must be of func() T
, a function without any arguments which returns a type, which again has a binding.
This allows to lazily create new objects whenever needed, instead of requesting the Dingo injector itself.
You can use Providers and call them to always get a new instance. Dingo will provide you with an automatic implementation of a Provider if you did not bind a specific one.
Use a Provider instead of requesting the Type directly when:
Example 1: This is the only code required to request a Provider as a dependency:
MyStructProvider func() *MyStruct
MyStruct struct {}
MyService struct {
MyStructProvider MyStructProvider `inject:""`
}
Example 2:
package example
func createSomething(thing SomethingElse) Something{
return &MySomething{somethingElse: thing}
}
injector.Bind(new(Something)).ToProvider(createSomething)
type somethingProvider func() Something
type service struct {
provider somethingProvider
}
will essentially call createSomething(new(SomethingElse))
everytime SomethingProvider()
is called, passing the resulting instance through the injection to finalize uninjected fields.
An injection struct tag can be marked as optional by adding the suffix ,optional
to it. This means that for interfaces, slices, pointers etc where dingo can not resolve a concrete type, the nil
-type is injected.
You can check via if my.Prop == nil
if this is nil.
Dingo uses bindings to express dependencies resolutions, and will panic if there is more than one binding for a type with the same name (or unnamed), unless you use multibindings.
Bind creates a new binding, and tells Dingo how to resolve the type when it encounters a request for this type. Bindings can chain, but need to implement the correct interfaces.
injector.Bind(new(Something))
By default a binding is unnamed, and thus requested with the inject:""
tag.
However, you can name bindings to have more concrete kinds of it. Using AnnotatedWith
you can specify the name:
injector.Bind((*Something)(nil)).AnnotatedWith("myAnnotation")
It is requested via the inject:"myAnnotation"
tag. For example:
struct {
PaypalPaymentProcessor PaymentProcessor `inject:"Paypal"`
}
To defines which type should be created when this type is requested. This can be an Interface which implements to one it is bound to, or a concrete type. The type is then created via reflect.New
.
injector.Bind(new(Something)).To(MyType{})
If you want a factory to create your types then you rather use ToProvider
instead of To
.
ToProvider
is a function which returns an instance (which again will go through Dingo to fill dependencies).
Also, the provider can request arguments from Dingo which are necessary to construct the bounded type. If you need named arguments (e.g. a string instance annotated with a configuration value) you need to request an instance of an object with these annotations, because Go does not allow to pass any meta-information on function arguments.
func MyTypeProvider(se SomethingElse) *MyType {
return &MyType{
Special: se.DoSomething(),
}
}
injector.Bind(new(Something)).ToProvider(MyTypeProvider)
This example will make Dingo call MyTypeProvider
and pass in an instance of SomethingElse
as it's first argument, then take the result of *MyType
as the value for Something
.
ToProvider
takes precedence over To
.
For situations where you have one, and only one, concrete instance you can use ToInstance
to bind something to the concrete instance. This is not the same as a Singleton! (Even though the resulting behaviour is very similar.)
var myInstance = new(MyType)
myInstance.Connect(somewhere)
injector.Bind(new(Something)).ToInstance(myInstance)
You can also bind an instance it to a struct obviously, not only to interfaces.
ToInstance
takes precedence over both To
and ToProvider
.
If really necessary it is possible to use singletons
.AsEagerSingleton() binds as a singleton, and loads it when the application is initialized
.In(dingo.Singleton) makes it a global singleton
.In(dingo.ChildSingleton) makes it a singleton limited to the current injector
In
allows us to bind in a scope, making the created instances scoped in a certain way.
Currently, Dingo only allows to bind to dingo.Singleton
and dingo.ChildSingleton
.
injector.Bind(new(Something)).In(dingo.Singleton).To(MyType{})
The dingo.Singleton
scope makes sure a dependency is only resolved once, and the result is reused. Because the Singleton needs synchronisation for types over multiple concurrent goroutines and make sure that a Singleton is only created once, the initial creation can be costly and also the injection of a Singleton is always taking more resources than creation of an immutable new object.
The synchronisation is done on multiple levels, a first test tries to find the singleton, if that is not possible a lock-mechanism via a scoped Mutex takes care of delegating the concrete creation to one goroutine via a scope+type specific Mutex which then generates the Singleton and makes it available to other currently waiting injection requests, as well as future injection requests.
By default, it is advised to not use Singletons whenever possible, and rather use immutable objects you inject whenever you need them.
The ChildSingleton is just another Singleton (actually of the same type), but dingo will create a new one for every derived child injector.
This allows frameworks like Flamingo to distinguish at a root level between singleton scopes, e.g. for multi-page setups where we need a wide scope for routers.
Since ChildSingleton is very similar to Singleton you should only use it with care.
Singleton creation is always costly due to synchronisation overhead, therefore Dingo bindings allow to mark a binding AsEagerSingleton
.
This makes sure the Singleton is created as soon as possible, before the rest of the Application runs. AsEagerSingleton
implies In(dingo.Singleton)
.
injector.Bind(new(Something)).To(MyType{}).AsEagerSingleton()
It is also possible to bind a concrete type without To
:
injector.Bind(MyType{}).AsEagerSingleton()
Binding this type as an eager singleton inject the singleton instance whenever MyType
is requested. MyType
is a concrete type (struct) here, so we can use this mechanism to create an instance explicitly before the application is run.
In rare cases you might have to override an existing binding, which can be done with Override
:
injector.Override(new(Something), "").To(MyBetterType{})
Override
also returns a binding such as Bind
, but removes the original binding.
The second argument sets the annotation if you want to override a named binding.
MultiBindings provide a way of binding multiple implementations of a type to a type, making the injection a list.
Essentially this means that multiple modules are able to register for a type, and a user of this type can request an injection of a slice []T
to get a list of all registered bindings.
injector.BindMulti(new(Something)).To(MyType1{})
injector.BindMulti(new(Something)).To(MyType2{})
struct {
List []Something `inject:""` // List is a slice of []Something{MyType1{}, MyType2{}}
}
MultiBindings are used to allow multiple modules to register for a certain type, such as a list of encoders, subscribers, etc.
Please note that MultiBindings are not always a clear pattern, as it might hide certain complexity.
Usually it is easier to request some kind of registry in your module, and then register explicitly.
Similar to Multibindings, but with a key instead of a list
MyService struct {
Ifaces map[string]Iface `inject:""`
}
injector.BindMap(new(Iface), "impl1").To(IfaceImpl{})
injector.BindMap(new(Iface), "impl2").To(IfaceImpl2{})
Dingo allows binding values to int
, string
etc., such as with any other type.
This can be used to inject configuration values.
Flamingo makes an annotated binding of every configuration value in the form of:
var Configuration map[string]interface{}
for k, v := range Configuration {
injector.Bind(v).AnnotatedWith("config:" + k).ToInstance(v)
}
In this case Dingo learns the actual type of v
(such as string, bool, int) and provides the annotated injection.
Later this can be used via
struct {
ConfigParam string `inject:"config:myconfigParam"`
}
Dingo allows modules to bind interceptors for interfaces.
Essentially this means that whenever the injection of a certain type is happening, the interceptor is injected instead with the actual injection injected into the interceptor's first field. This mechanism can only work for interface interception.
Multiple interceptors stack upon each other.
Interception should be used with care!
func (m *Module) Configure(injector *dingo.Injector) {
injector.BindInterceptor(new(template.Engine), TplInterceptor{})
injector.BindInterceptor(new(template.Function), FunctionInterceptor{})
}
type (
TplInterceptor struct {
template.Engine
}
FunctionInterceptor struct {
template.Function
}
)
func (t *TplInterceptor) Render(context web.Context, name string, data interface{}) io.Reader {
log.Println("Before Rendering", name)
start := time.Now()
r := t.Engine.Render(context, name, data)
log.Println("After Rendering", time.Since(start))
return r
}
func (f *FunctionInterceptor) Name() string {
funcname := f.Function.Name()
log.Println("Function", funcname, "used")
return funcname
}
At the topmost level the injector is created and used in the following way:
package main
import "flamingo.me/dingo"
func main() {
injector, err := dingo.NewInjector()
if err != nil {
panic(err)
}
// The injector can be initialized by modules:
injector.InitModules(new(BillingModule))
// Now that we've got the injector, we can build objects.
// We get a new instance, and cast it accordingly:
instance, err := injector.GetInstance(new(BillingService))
if err != nil {
panic(err)
}
billingService := instance.(BillingService)
//...
}
Recently https://github.com/google/go-cloud/tree/master/wire popped out in the go ecosystem, which seems to be a great choice, also because it supports compile time dependency injection. However, when Dingo was first introduced wire was not a thing, and wire still lacks features dingo provides.
https://gocover.io/github.com/i-love-flamingo/dingo
Dingo has a wrapper for func(*Injector)
called ModuleFunc
. It is possible to wrap a function with the ModuleFunc
to become a Module
. This is similar to the http
Packages HandlerFunc
mechanism and allows to save code and easier set up small projects.
Author: i-love-flamingo
Source Code: https://github.com/i-love-flamingo/dingo
License: MIT License
1599854400
Go announced Go 1.15 version on 11 Aug 2020. Highlighted updates and features include Substantial improvements to the Go linker, Improved allocation for small objects at high core counts, X.509 CommonName deprecation, GOPROXY supports skipping proxies that return errors, New embedded tzdata package, Several Core Library improvements and more.
As Go promise for maintaining backward compatibility. After upgrading to the latest Go 1.15 version, almost all existing Golang applications or programs continue to compile and run as older Golang version.
#go #golang #go 1.15 #go features #go improvement #go package #go new features
1665483559
Wire is a code generation tool that automates connecting components using dependency injection. Dependencies between components are represented in Wire as function parameters, encouraging explicit initialization instead of global variables. Because Wire operates without runtime state or reflection, code written to be used with Wire is useful even for hand-written initialization.
For an overview, see the introductory blog post.
Install Wire by running:
go install github.com/google/wire/cmd/wire@latest
and ensuring that $GOPATH/bin
is added to your $PATH
.
As of version v0.3.0, Wire is beta and is considered feature complete. It works well for the tasks it was designed to perform, and we prefer to keep it as simple as possible.
We'll not be accepting new features at this time, but will gladly accept bug reports and fixes.
For questions, please use GitHub Discussions.
This project is covered by the Go Code of Conduct.
Author: Google
Source Code: https://github.com/google/wire
License: Apache-2.0 license
1650075840
🦄 Fx
An application framework for Go that:
func init()
.We recommend locking to SemVer range ^1
using go mod:
go get go.uber.org/fx@v1
This library is v1
and follows SemVer strictly.
No breaking changes will be made to exported APIs before v2.0.0
.
This project follows the Go Release Policy. Each major version of Go is supported until there are two newer major releases.
Author: uber-go
Source Code: https://github.com/uber-go/fx
License: MIT License
1626141180
Short refactoring session with a program where we go through Dependency Injection and testing in Go (Golang)
Dependency Injection Wiki - https://en.wikipedia.org/wiki/Dependency_injection
Source Code - https://play.golang.org/p/NSPYooZxGqL
💼 Golang Cafe - https://golang.cafe
📬 Golang Cafe Jobs Newsletter - https://golang.cafe/newsletter
🐦 Golang Cafe Twitter - https://twitter.com/golangcafe
📣 Telegram Channel - https://t.me/golangcafe
#golang #go #dependency injection
1591888061
I’ve lately been feeling my way around getting an actual, production-ready ASP.NET Core app developed, and one of the features I’ve really been digging (one of many) is native support for Dependency Injection (DI). DI is huge in modern web apps, so making it the default for ASP.NET
#asp.net core #asp.net core dependency injection #dependency #injection #programming