Comments (15)
@erickskrauch, @fionera I lean towards to implement WithCondition()
provide option:
func NewConfig() *viper.Viper {
// initialization code omitted
return viper.New()
}
func IsServiceA(config *viper.Viper) bool {
return config.GetString("preferred_service") == "A"
}
func IsServiceB(config *viper.Viper) bool {
return config.GetString("preferred_service") == "B"
}
func main() {
c, err := di.New(
di.Provide(NewConfig),
di.Provide(NewServiceA, di.WithCondition(IsServiceA)),
di.Provide(NewServiceB, di.WithCondition(IsServiceB)),
)
}
from di.
When you implement this, you could basically implement it so that di.WithCondition gets executed like a late provide wrapper
func NewService(container di.InjectedContainer, config *viper.Viper) di.Option {
if viper.GetString("preferred_service") == "B" {
return di.Provide(NewServiceB)
}
return di.Provide(NewServiceA)
}
func main() {
c, err := di.New(
di.Provide(NewConfig),
di.Provide(NewService)
)
}
from di.
@erickskrauch Hi! Thanks for your interest =)
In the current implementation, the change of dependency graph after compilation is restricted for a more clear injection mechanism. This means that you can't add a provider of type based on the other providing result. The dependency graph must be formed before it is compiled and it can't change in the future.
In this case, you can create a configuration outside a container and change the list of options:
config := viper.New()
// config initialization code
// ...
var options []di.Option
switch config.GetString("preferred_service") {
case "A":
options = append(options, di.Provide(NewServiceA))
case "B":
options = append(options, di.Provide(NewServiceB))
}
options = append(options,
di.Provide(func() *viper.Viper { return config }), // provide already existing config
)
container, err := di.New(options...)
I'm just thinking about how to make this functionality easy and clear.
Now, we have some ideas:
I will form some proposals for this in the near future.
from di.
Thanks for the reply!
The first idea was to create some sort of reduced container interface (without methods that would modify the dependency graph) that could be accepted as service dependency and will let to call the Invoke
, Resolve
and Has
methods. But I think disclosing the last two methods makes it somehow close to some kind of service locator :)
Updated example:
package main
import (
"github.com/goava/di"
"github.com/spf13/viper"
)
type ServiceInterface interface {
Do()
}
type DependencyA interface {}
type ServiceImplementationA struct {DependencyA}
func (s *ServiceImplementationA) Do() {}
func NewServiceA(a DependencyA) *ServiceImplementationA {
return &ServiceImplementationA{a}
}
type DependencyB interface {}
type ServiceImplementationB struct {DependencyB}
func (s *ServiceImplementationB) Do() {}
func NewServiceB(b DependencyB) *ServiceImplementationB {
return &ServiceImplementationB{b}
}
func NewService(container di.InjectedContainer, config *viper.Viper) ServiceInterface {
if viper.GetString("preferred_service") == "B" {
return container.Invoke(NewServiceB)
}
return container.Invoke(NewServiceA)
}
func main() {
container, _ := di.New(
di.Provide(viper.New),
di.Provide(NewService),
di.Provide(func() DependencyA {
return &struct{}{}
}),
di.Provide(func() DependencyB {
return &struct{}{}
}),
)
var service ServiceInterface
container.Resolve(&service)
}
from di.
The example looks very identical to my late provide idea ^^
from di.
I just thought that this is how the Invoke
method turns out to be the most useless because there are no generics in Go and it will be impossible to recognize the return type (and Invoke
returns only the err
).
Need to think of something else that will allow you to automatically determine the list of dependencies and at the same time not to lose the returned type so that you don't have to perform typecasting.
You need to merge the automatic arguments injection of Invoke
's method with Resolve
's result passing by reference (I hope you were able to understand that "English" 😅).
from di.
Let's call it InvokeWithResult
(the name is temp 😅):
func NewService(config *viper.Viper, container di.InjectedContainer) (result ServiceInterface, err error) {
if config.GetString("preferred_service") == "B" {
err = container.InvokeWithResult(NewServiceB, &result)
} else {
err = container.InvokeWithResult(NewServiceA, &result)
}
}
from di.
Introducing the WithCondition
option looks like an acceptable (but very verbose) solution for some simple conditions of choosing the target dependency. But if the logic becomes more complex, each case will have to be rewritten over and over again, which will be the source of hard debuggable errors.
Introducing some magic interface will be a more natural way to solve this task. Maybe you should introduce some di.LateProvide(NewService)
, which should return di.Option
as @fionera suggested. Although this raises the problem that the dependency graph is modified by the service creator result.
That's why my solution with InvokeWithResult
seems to be more suitable: the dependency graph doesn't change, but the initial task can be reached :)
from di.
@erickskrauch WithCondition()
separates condition and provide logic. Providing already tested. Condition logic will be easy to test. Even complex cases can be tested and the test will be primitive.
@fionera Provide di.Option
needs additional instruments to test on the user side. You must be sure that you have provided the appropriate option.
@erickskrauch Your solution returns interface instead of implementation in the constructor, that doesn't fit the principle "return structs accept interfaces". And does not allow you to use di.As() as it is now. Also, introducing a magic interface is not "go way". Dependency injection already contains a lot of "magic". I wouldn't want to make it more complicated.
And if we are talking about the possibility of container resolving. Then we don't need to add additional methods:
package main
import (
"errors"
"fmt"
"log"
"net"
"github.com/goava/di"
)
type ConnectionType string
// NewConnection
func NewConnection(typ ConnectionType, container *di.Container) (net.Conn, error) {
switch typ {
case "tcp":
var conn *net.TCPConn
return conn, container.Resolve(&conn)
case "udp":
var conn *net.UDPConn
return conn, container.Resolve(&conn)
}
return nil, errors.New("unknown connection type")
}
func NewTCPConn() *net.TCPConn {
return &net.TCPConn{}
}
func NewUDPConn() *net.UDPConn {
return &net.UDPConn{}
}
func NewConnectionType() ConnectionType {
return "udp" // change to "tcp" for tcp connection
}
func main() {
c, err := di.New(
di.WithCompile(),
di.Provide(NewConnectionType),
di.Provide(NewTCPConn),
di.Provide(NewUDPConn),
di.Provide(NewConnection),
)
if err != nil {
log.Fatalln(err)
}
// hack to provide container
if err := c.Provide(func() *di.Container { return c }); err != nil {
log.Fatalln(err)
}
if err := c.Compile(); err != nil {
log.Fatalln(err)
}
var conn net.Conn
if err := c.Resolve(&conn); err != nil {
log.Fatalln(err)
}
switch conn.(type) {
case *net.TCPConn:
fmt.Println("TCP")
case *net.UDPConn:
fmt.Println("UDP")
}
}
from di.
ConnectionType
can be replaced with the configuration type and this will work. But it is the hack =D
from di.
Your solution returns interface instead of implementation in the constructor, that doesn't fit the principle "return structs accept interfaces".
But the target structures, for the creation of which di is necessary, expect interfaces, not concrete implementations. Otherwise, what is the point of all this at all?
I need to define the interface implementation for the application. But its implementation is chosen based on the configuration. That's why I need some layer, which will allow me to make a decision about the final implementation based on the configuration or other dependencies.
Thus, we come to the conclusion that if we ensure the availability of the container when resolving dependencies, then, in general, the problem is solved. Maybe we should then make container available by default?
from di.
Sorry for delay, I had a hard time today.
But the target structures, for the creation of which di is necessary, expect interfaces, not concrete implementations. Otherwise, what is the point of all this at all?
di.As()
binds implementation to the interface. It is bad practice to return the interface from the constructor.
Thus, we come to the conclusion that if we ensure the availability of the container when resolving dependencies, then, in general, the problem is solved. Maybe we should then make container available by default?
This can be true. I create #9 with some improvements:
- removed compile stage, the graph can be modified
- container provided by default
- some internal improvements which I have long wanted to do
Late provide example: https://github.com/goava/di/blob/make-graph-editable/_examples/lateprovide/main.go
It still needs testing, but if it shows up well, we'll release it.
from di.
I still need to reimplement the acyclicity check and di.Prototype()
.
from di.
Glad we could come to a decision :)
I have already started implementing DI in my project. You can have a look at it: https://github.com/elyby/chrly/tree/di/di. I'll update the code to #9's solution when it'll be ready.
from di.
Fixes merged. I need some time to prepare the release.
from di.
Related Issues (20)
- Unable to make interfaces group by using the same funtion interface implenentations HOT 11
- container.Resolve() doesn't create signleton when dependency requested via intermidiate constructor HOT 2
- No documentation for the *Container.Has() method HOT 1
- Using `di.WithName()` breaks when having one entry without a `di.Name()` HOT 7
- Parameter structs need an empty di tag on an entry else it gets ignored HOT 1
- Allow decorating of services in the container HOT 8
- concurrent map read and map write HOT 6
- Duplicate instance with di.As HOT 2
- Singleton HOT 1
- Handle embed unnamed dependency HOT 1
- Improve tracing HOT 2
- Support the ability to chain containers (to form scopes) HOT 3
- Change how Injected structs are tagged HOT 4
- Why should I pass a pointer to the type? HOT 2
- Possibility to disable singleton behavior. HOT 5
- Important: Upcoming Transfer of This Repository on June 5th
- Issue after package name migration HOT 4
- Provide values via custom string types HOT 2
- Overriding a value from a parent di container
- Factory function returning other di.Provide HOT 7
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from di.