Live data from Hacker News

Dependency injection in Go with Uber-go/fx

vincent.composieux.fr

81–90 of 96 posts

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

#81
post #32

Earlier quoted context omitted.

Same. It’s nice to publish an fx module that implements a middleware that plugs into all of the 12000+ microservices we run. One example is adding a debug/flame graph endpoint to your service- it’s a one line import into your app.

The Go convention for adding features like that is import _ "example.com/feature" Á la https://pkg.go.dev/net/http/pprof Reads a heck of a lot easier!

If the complaint about dependency injection is that it’s magic, brittle, and difficult to debug - globals mutation via init() is much worse. In FX codebases this is heavily discouraged. The concepts of constructors and lifecycle hooks in an FX graph are not that hard to grok; you can pretty readily figure out what’s going on if you want to.

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

#83
post #9

For a slightly different take on this problem, check out Wire ( https://github.com/google/wire ).

Having used both, I very much prefer wire's compile-time satisfaction vs fx's runtime satisfaction. If I miss something, I'd much rather know when I try to compile instead of waiting 'til a specific code path that uses a missing dependency fails.

That's a very fair perspective, and build-time verification of your dependency graph is great. That said, fx's approach allows for a more dynamic dependency graph. (For example, fx.Replace[0].)

The documentation for Fx, along with nearly all the applications I saw internally, use the dependency injection container only in main - once the application starts successfully, there's no more interaction with the container. For Uber at the time, this struck a useful balance between safety and the difficulty of distributing yet another versioned code gen tool to thousands of repositories.

[0]: https://github.com/uber-go/fx/pull/837

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

#84
post #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 -…

Warms my heart to hear that people still find some value in fx :)

Does fx.As[0] help match structs to interfaces? It was added long after my time, but seems to target this problem.

[0]: https://pkg.go.dev/go.uber.org/fx#As

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

#85

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.

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.

As someone who's liked using DI frameworks, I'm curious: if you do it manually, as your codebase gets large, doesn't it get harder to "plumb" a new object that needs to get used somewhere at a deep level? This seems like it'd get more unwieldy when refactoring.

I've never worked in a large codebase that did this manually so I'm not sure what it looks like. The large codebases I've seen that don't use DI have used something like a service locator, singletons, or constructed everything where it was needed (and used extensive mocking framework functionality for testing).

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

#86

Earlier quoted context omitted.

The Go convention for adding features like that is import _ "example.com/feature" Á la https://pkg.go.dev/net/http/pprof Reads a heck of a lot easier!

If the complaint about dependency injection is that it’s magic, brittle, and difficult to debug - globals mutation via init() is much worse. In FX codebases this is heavily discouraged. The concepts of constructors and lifecycle hooks in an FX graph are not that hard to grok; you can pretty readily figure out what’s going on if you want to.

Dependency injection happens all the time in Go. It's just a lot more elegantly expressed in idiomatic Go than in FX.

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

#87

Earlier quoted context omitted.

If the complaint about dependency injection is that it’s magic, brittle, and difficult to debug - globals mutation via init() is much worse. In FX codebases this is heavily discouraged. The concepts of constructors and lifecycle hooks in an FX graph are not that hard to grok; you can pretty readily figure out what’s going on if you want to.

Dependency injection happens all the time in Go. It's just a lot more elegantly expressed in idiomatic Go than in FX.

The “import _” approach you recommend is idiomatic Go but it is inherently a global mutable state approach and not a DI approach.

I suppose elegance is in the eye of the beholder. FX is very elegant in my opinion: just specify your constructors and your needs, let the computer do the topo sort. But you’re right it is not idiomatic Go. Idiomatic Go is always to maximize the volume of rote, low-information-density code to accomplish any given task. Any time you are being clever and automating grunt work you are certainly violating the spirit of Go.

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

#88
post #21

I'd like someone who knows more about software engineering than I do to tell me why I'd use this over some big globals-but-not-really App struct that holds all of the various dependencies of my app that I can just pass into the different parts of it so they can all access what they need. If those struct members are typed to interfaces and not types, then it's just as testable, too, because you can drop in mocks as re…

It's a scale thing. A bag of globals works, until the scale of what you have gets too big, and the big bag of globals becomes a 500 variable super object, and then your build graph gets bottlenecked on the bag of globals and a bunch of other pain.

If your project scale is small, you don't need DI frameworks, but you should still use IoC so you don't have implicit singleton access and your tests get flaky and stupid.

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

#89

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…

5000 lines doesn't tell anything important. I preferred a well organize 5000 lines than 250 files of 20 lines spread randomly in various directories.

If it's 5000 lines and messy it is indeed a problem, but only because it's messy.

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

#90
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 mostly agree. I think there are two cases where using a DI framework make sense:

1. Complex lifecycle management, maybe. For example say you need to restart a set of go routines after loading a new configuration, without killing the process. I’m on the fence about this one.

2. When there is a combinatorially large number of components that need to be combined arbitrarily at runtime. The only examples I’ve seen for this in the wild are games and simulations using an ECS (entity-component-system) and queries to discover components that fit a certain criteria.

Post reply on HN