Live data from Hacker News

Dependency injection in Go with Uber-go/fx

vincent.composieux.fr

61–70 of 96 posts

Re: Dependency injection in Go with Uber-go/fx

#61
post #40

We at Khan Academy are planning a blog post soon about how we've been approaching dependency injection in Go. I touched upon this in my GopherCon talk last year[1]. I like the system because it's pretty straightforward and builds upon Go's context with a bit of reflection: [1]: https://youtu.be/MysHL0XYJeA?t=680 var ktx interface { kacontext.Base log.KAContext datastore.KAContext gqlclient.KAContext web.AuthedService…

Not a fan of that... Feels like an abuse of Context. I admit, I thought about this a long time ago, Context is passed all the way down the stack, it's just so easy to abuse. But it's just one of those things that completely hides this away from anyone who glances at your code.

It makes it incredibly easy to create "God structs" that can do everything. They have access to every service in your application when really you should be limiting the scope of them.

Unsatisfied dependencies are now a runtime issue, not a buildtime issue, one of my biggest gripes when working with IOC containers in .NET.

Not to mention that Context, originally built as a cancellation token, is now doubling as a service locator? It feels like something completely adjacent to what a Context is.

This is the exact sort of reflection magic code that a lot of Go developers dislike. And there's nothing that this gives you that passing dependencies as parameters can't. If you've got too many to pass then you're doing too much and you're not separating concerns.

Re: Dependency injection in Go with Uber-go/fx

#62
post #57

Earlier quoted context omitted.

I think you are confusing dependency injecting with frameworks that facilitate it. You are probably using the former with constructors

What's the difference? Isn't the 'injection' part the part where you don't need to connect point A and point B?

Sorta. The pattern is having standard method of injecting dependencies. Having a framework just saves you having to do the actual injecting, manually.

This is easy to see in frameworks like dagger, which compile time generate the boilerplate you could manually do.

And if everything is a singleton with no lifetime management, the framework doesn't buy you too much. The pattern, though, is kind of nice. I rarely have to question how a dependent section of code is linked to the one I'm at. (Contrast to python, where I don't know what is going to happen if I add that import...)

Re: Dependency injection in Go with Uber-go/fx

#63
post #4
post #2

I've used dependency injection heavily in Java in previous jobs, and, later, spent a few years doing Golang at another job. I never missed it. What does dependency injection give you that a simple combination of Singletons, Constructors, and Factories doesn't? I feel like the only thing you get is the ability to combine multiple independent dependency trees without having to make a sane structure. Kind of like, what…

I came here to ask a similar question. I’ve spent most of my career writing C# where DI is prevalent. Over the Christmas holiday I picked up golang a bit. When I went to learn about DI for golang it seemed very counter to the principles of the language so I simply moved on. What am I missing out on?

Because golang doesn't have constructors, you're forced to implement functions that mimic them yourself. And the language still doesn't prevent you from directly instantiating the struct yourself, meaning it is always possible to bypass the "constructor functions". This is quite terrible and opens up your code to errors.

Furthermore, DI frameworks also usually have lifecycle management, which is quite handy in many cases.

Re: Dependency injection in Go with Uber-go/fx

#64

I've never been convinced by DI frameworks. I've always enjoyed the fact that the Go ecosystem leans away from them and I would hope things stay that way. In my experience they just obfuscate what should be a straightforward, explicit process of setting up your app in main. The less magic happening there the better.

I have (and probably will) never voluntarily use a DI framework in any language. There's zero downside to manually doing it. It's explicit, incredibly easy, and you can easily trace backward to see how the program is constructed.

This is pretty much what I don't get. DI by construction is trivial and has all of the benefits of a DI framework. DI frameworks just let you move some things around, which is mostly confusing.

Re: Dependency injection in Go with Uber-go/fx

#65
post #40

We at Khan Academy are planning a blog post soon about how we've been approaching dependency injection in Go. I touched upon this in my GopherCon talk last year[1]. I like the system because it's pretty straightforward and builds upon Go's context with a bit of reflection: [1]: https://youtu.be/MysHL0XYJeA?t=680 var ktx interface { kacontext.Base log.KAContext datastore.KAContext gqlclient.KAContext web.AuthedService…

Not a fan of that... Feels like an abuse of Context. I admit, I thought about this a long time ago, Context is passed all the way down the stack, it's just so easy to abuse. But it's just one of those things that completely hides this away from anyone who glances at your code. It makes it incredibly easy to create "God structs" that can do everything. They have access to every service in your application when really…

Passing context everywhere is quite ironic in a language that is supposedly "non-colored" when it comes to being able to call any function concurrently. In practice however, passing context becomes that function's color.

Project Loom in Java will solve this problem in a much more superior manner.

Re: Dependency injection in Go with Uber-go/fx

#66

I've never been convinced by DI frameworks. I've always enjoyed the fact that the Go ecosystem leans away from them and I would hope things stay that way. In my experience they just obfuscate what should be a straightforward, explicit process of setting up your app in main. The less magic happening there the better.

I currently have to deal with DI at work. It's a pain. When everything works, it's nice to be able to just ask for something and have it with no extra work, but good luck when things go wrong. If the DI library throws an exception, good luck figuring out what you actually need to do to resolve it. Often it's because there's a cycle in the dependency graph, but the exceptions never give you any information about what the graph looks like. It also makes it stupidly hard to track down the sources of bugs. Figuring out what classes are being used to satisfy the dozen interfaces your class asks for requires either inspecting variables in the debugger or an annoying amount of digging.

Re: Dependency injection in Go with Uber-go/fx

#67

I've never been convinced by DI frameworks. I've always enjoyed the fact that the Go ecosystem leans away from them and I would hope things stay that way. In my experience they just obfuscate what should be a straightforward, explicit process of setting up your app in main. The less magic happening there the better.

I have (and probably will) never voluntarily use a DI framework in any language. There's zero downside to manually doing it. It's explicit, incredibly easy, and you can easily trace backward to see how the program is constructed.

You say this now, but I've had to work on 5000 line (literally, 5k lines) classes in legacy code where there are a huge number of dependencies injected manually because the class is doing initialization for a huge number of components. You could argue that this was bad design but I'm sure it didn't start out like that, but just accumulated cruft over time and was too dangerous to refactor. (if prod went down it was at least a few hundred $k down the drain). DI makes it much simpler to wire things like this up without making mistakes like accidentally initializing the wrong class implementation, etc. In polymorphism heavy situations it reduces cognitive complexity a lot.

Re: Dependency injection in Go with Uber-go/fx

#68

Earlier quoted context omitted.

It's not terrible to have one big struct, but sometimes it can inhibit the reuse of components in more than one system. When you call a function that takes this struct as an argument, how do you know which members of the struct really need to be populated? Its dependencies aren't clearly documented. (They are over-specified. It's like a function with many unused arguments.) If a subsystem only uses some members of th…

> how do you know which members of the struct really need to be populated Interfaces + duck typing? Your function shouldn’t ask for the struct, but rather for an interface describing what it needs. Practically, though, I often just ask for the struct because I’m a slob.

An interface with many methods can have the same problem. If the function doesn't actually use every method in the interface, do you really need to implement them all?

So then it might be better for the function to declare its own interface with just the methods it uses? But then, all the callers need to be changed if you decide to call another method.

There's no principled solution to predicting what dependencies code might need someday. It's a matter of taste.

Re: Dependency injection in Go with Uber-go/fx

#69

Earlier quoted context omitted.

I have (and probably will) never voluntarily use a DI framework in any language. There's zero downside to manually doing it. It's explicit, incredibly easy, and you can easily trace backward to see how the program is constructed.

You say this now, but I've had to work on 5000 line (literally, 5k lines) classes in legacy code where there are a huge number of dependencies injected manually because the class is doing initialization for a huge number of components. You could argue that this was bad design but I'm sure it didn't start out like that, but just accumulated cruft over time and was too dangerous to refactor. (if prod went down it was a…

Dependency Injection frameworks definitely seem like one of those technologies that primarily exists to allow people to get away with bad designs, rather than making it easier to create good designs.

Re: Dependency injection in Go with Uber-go/fx

#70
Full disclaimer: Uber employee using fx daily as well as in hobby projects. Post reflect my personal opinion and is not related to Uber.

It really does work really well in practice:

- It pretty simple and lightweight so it’s blazingly fast even though it happens at runtime (in contrast to e.g. the Java DI frameworks I’ve seen)

- The module concept is extremely powerful to make modules that plug in with zero effort

- Modules have a lot of autonomy (unlike wire, as I understand it - I have no personal experience with it), like being able to do things at various stages of the application lifecycle (startup, shutdown hooks), collaboratively populate dependency groups (e.g. implementing handlers or middlewares independently and injecting them separately using the grouping mechanism), optional dependencies

Once you have a nice standard library of common modules (this is really crucial for it to work well IMO), it’s a huge speedup to make a high-quality service. My biggest issue with it is that it doesn’t match structs with interfaces, so you effectively end up depending on structs/pointers or returning interfaces.

Post reply on HN