Git Product home page Git Product logo

Comments (11)

zombiezen avatar zombiezen commented on September 27, 2024 1

Discussed with @vangent offline. For this and #406, we want to develop a way of specifying a subset of fields. (Off-hand, I think this could be done by specifying the types of fields, since we're guaranteeing they have to be unique.) Once that's in place, you could imagine:

package wire

// FieldsOf specifies that the given field types will be provided by extracting them
// from a struct type, which must also be provided in the same provider set. Each
// of the arguments must be a pointer to the type they wish to reference.
//
// For example:
//
//    type S struct {
//        MyFoo *Foo
//    }
//    func NewStruct() *S { /* ... */ }
//    var Set = wire.NewSet(wire.FieldsOf(new(S), new(*Foo)))
func FieldsOf(structType interface{}, fieldTypes ...interface{}) StructFields

(The semantics of struct versus pointer-to-struct likely have to be worked out.)

from wire.

vangent avatar vangent commented on September 27, 2024

Thanks for checking out wire!

Think of a struct with fields as being created by a function that takes all of the fields' types;

type FooBar struct {
  foo *Foo
  bar *Bar
}

requires the same injection data to create FooBar as

func newFooBar(foo *Foo, bar *Bar) FooBar {...}

In either case, the values can be injected using wire.Value, but also by other kinds of providers. Here is an example of fields of a struct (Foo and Bar in struct FooBar) being injected via provider functions (provideFoo and provideBar) instead of via wire.Value.

But yes, each field in the struct / each argument to the function needs to be provided.

I'm not sure wire.FieldValues() is necessary, since wire automatically matches up the provided types from all of the providers given to wire.Build to the required injection data. If want to group the providers, you could use wire.ProviderSet to do that; here is an example of that.

Hope that helps!

from wire.

e-nikolov avatar e-nikolov commented on September 27, 2024

Maybe I should provide a bit more context about what I'm trying to achieve.

There is an existing project which uses dependency injection, but the injection code was written manually. I wanted to see if it can be replaced with Wire.

In this project each component is inside its own package and has its own Config type.

For instance there is package mongo which contains a service that stores some data inside mongo (simplified snippet):

// mongo.go
package mongo

type Config struct {
    URL          string
    User         string
    Password     string
}

type Service struct {
    cfg *Config
}

func NewService(cfg *Config) {
    return &Service {
       cfg: cfg,
    }
}

// ... service methods omitted for brevity

There are also a bunch of other packages, containing other components, also having their own Config types.

Inside our main.go, we also have a Config type, which is composed of all Configs from the components:

// main.go
package main

type Config struct {
    Mongo    *mongo.Config
    Redis    *redis.Config
    //  ......
}

type MainService struct {
    mongo  *mongo.Service
    redis  *redis.Service
    //....
}

func main() {
    var cfg *Config
    // ... load cfg from somewhere

    svc := InitializeMainService(cfg)
    // ... use svc for something
}

func InitializeMainService(cfg *Config) *MainService {
    mongoService := mongo.NewService(cfg.Mongo)
    redisService := redis.NewService(cfg.Redis)

    return &MainService {
        mongo: mongoService,
        redis: redisService,
    }
}

So here, each component's constructor is called with only the config relevant to it. With wire, it seems like I would have to make a provider function for each sub-config which takes the main config and returns the relevant sub-config:

// injectors.go
package main

func ProvideMongoConfig(cfg *Config) *mongo.Config {
    return cfg.Mongo
}

func ProvideRedisConfig(cfg *Config) *redis.Config {
    return cfg.Redis
}

func InitializeMainService(cfg *Config) *MainService {
    panic(wire.Build(ProvideMongoConfig, mongo.NewService, ProvideRedisConfig, redis.NewService))
}

In our case we have ~15 such separate components, some of which depend on others, so it would be nice to have the wiring auto-generated, but each of them has its own Config type and having to write a separate provider for each sub-config from the main config seems like a lot of repetition. Is there a better way that I am missing? If not, I think it would be nice if I could somehow tell wire to use the sub-fields of the Config struct as injectors instead of the whole config itself:

// injectors.go
package main

func InitializeMainService(cfg *Config) *MainService {
    panic(wire.Build(wire.FieldsOf(cfg), mongo.NewService, redis.NewService))
}

Would something like that be possible and does what I am trying to achieve make sense?

from wire.

vangent avatar vangent commented on September 27, 2024

No, I don't think there's a better way right now. Maybe @zombiezen knows of one, and could comment on whether wire.FieldsOf() is a reasonable feature request.

I thought that something like this would work:

func InitializeMainService(cfg *Config) *MainService {
  wire.Build(
    mongo.NewService,
    wire.Value(cfg.Foo),
    redis.NewService,
    wire.Value(cfg.Redis),
    ...
  )
}

which doesn't seem too bad, but I'm getting an error. I filed #378 to look into it.

from wire.

e-nikolov avatar e-nikolov commented on September 27, 2024

I also tried this and got the same error, but I assumed that I had misunderstood the purpose of wire.Value. It seemed that wire.Value only accepts values that are known at compile time and not at runtime.

from wire.

e-nikolov avatar e-nikolov commented on September 27, 2024

For now I ended up doing something like this:

//main.go 

package main

func main() {
    var cfg *Config
    // ... load cfg from somewhere

    svc := InitializeMainService(transformArgs(cfg))
    // ... use svc for something
}
// injectors.go

package main

func transformArgs(cfg *Config) (*mongo.Config, *redis.Config) {
    return cfg,
         cfg.Mongo,
         cfg.Redis,
}

func InitializeMainService(cfg *Config, mongoCFG *mongo.Config, redisCFG *redis.Config) *MainService {
    panic(wire.Build(mongo.NewService, redis.NewService))
}

This way the workaround logic is all inside the transformArgs function rather than having to create a bunch of providers.

from wire.

vangent avatar vangent commented on September 27, 2024

Thanks, that looks reasonable. If nothing else we could add that to the Best Practices section of the wire documentation for this use case.

from wire.

zombiezen avatar zombiezen commented on September 27, 2024

(Just returned from Gophercon, thank you for your patience.)

@e-nikolov Your understanding is correct: struct providers in wire allow you to group a bunch of related types, but there is no corresponding way to "ungroup" in Wire right now. I feel that this would be a good feature, but we would need to iron out a way to be selective about what fields are being extracted. Ideas for syntax here would be welcome.

@vangent Yes, documentation in the interim would be good. gcp.CredentialsTokenSource is a function that exists purely for this reason.

from wire.

zombiezen avatar zombiezen commented on September 27, 2024

(This is also tangentially related to #406)

from wire.

vangent avatar vangent commented on September 27, 2024

@zombiezen per your request, assigning to you to think about. Let me know when you are ready to discuss.

from wire.

shantuo avatar shantuo commented on September 27, 2024

Discussed with @zombiezen offline a bit more. We could add a little bit more of flexibility by matching the "names" of the fields, not types. But the uniqueness of the types that can be used in FieldsOf remains unchanged.

package wire

// FieldsOf extracts the structType into a list of types specified by fieldNames.
// All types should be provided in the same provider set. The structType argument
// must be a pointer to the struct it wishes to reference.
//
// Example:
//
//  type S struct {
//  	MyFoo *Foo
//  	MyBar *Bar
//  }
//  func NewStruct() *S { /* ... */ }
//  var Set = wire.NewSet(wire.FieldsOf(new(S), "Foo", "Bar"))
func FieldsOf(structType interface{}, fieldNames ...string) StructFields {
	return StructFields{}
}

from wire.

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.