Git Product home page Git Product logo

Comments (15)

defval avatar defval commented on June 16, 2024 1

@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.

fionera avatar fionera commented on June 16, 2024 1

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.

defval avatar defval commented on June 16, 2024

@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:

  • Late provides (#3 @fionera)
  • WithCondition() provide option

I will form some proposals for this in the near future.

from di.

erickskrauch avatar erickskrauch commented on June 16, 2024

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.

fionera avatar fionera commented on June 16, 2024

The example looks very identical to my late provide idea ^^

from di.

erickskrauch avatar erickskrauch commented on June 16, 2024

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.

erickskrauch avatar erickskrauch commented on June 16, 2024

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.

erickskrauch avatar erickskrauch commented on June 16, 2024

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.

defval avatar defval commented on June 16, 2024

@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.

defval avatar defval commented on June 16, 2024

ConnectionType can be replaced with the configuration type and this will work. But it is the hack =D

from di.

erickskrauch avatar erickskrauch commented on June 16, 2024

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.

defval avatar defval commented on June 16, 2024

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.

defval avatar defval commented on June 16, 2024

I still need to reimplement the acyclicity check and di.Prototype().

from di.

erickskrauch avatar erickskrauch commented on June 16, 2024

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.

defval avatar defval commented on June 16, 2024

Fixes merged. I need some time to prepare the release.

from di.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.