Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I found fx(https://github.com/uber-go/fx) to be a super simple yet versatile tool to design my application around.

All the advice in the article is still helpful, but it takes the "how do I make sure X is initialized when Y needs it" part completely out of the equation and reduces it from an N*M problem to an N problem, ie I only have to worry about how to initialize individual pieces, not about how to synchronize initialization between them.

I've used quite a few dependency injection libraries in various languages over the years (and implemented a couple myself) and the simplicity and versatility of fx makes it my favorite so far.



>All the advice in the article is still helpful, but it takes the "how do I make sure X is initialized when Y needs it" part completely out of the equation and reduces it from an N*M problem to an N problem, ie I only have to worry about how to initialize individual pieces, not about how to synchronize initialization between them.

I gotta say, I hate these dependency injection frameworks.

In a well designed system this should be trivial. Making sure something is initialised when you want to use it is just a matter of it being available to pass in a constructor as a parameter.

  stockService := NewStockService()
  orderService := NewOrderService()
  orderProcessor := NewOrderProcessor(stockService, orderService)
There shouldn't be any sort of "synchronisation" of initialisation needed because your code won't compile if you do something wrong. If you add a cyclic dependency you will clearly see that because you won't be able to construct things in the right order without an obvious workaround.


If you have ever topologically sorted 100 components connected in a complex graph by hand or found the right spot to insert the 101st, you'd quickly appreciate more help than a compiler check.


I’m not against DI, but I don’t find your argument convincing: having dependencies modelled directly with the simplest language constructs (variables and arguments) and validated by the compiler makes “debugging” a ton simpler than dealing with DI errors, even in a good DI framework. Having an error just means I wrote invalid code: even a junior can easily figure it out.

DI still has other advantages, but that’s not one


I don't disagree with you, I've argued against the usage DI frameworks plenty of times on projects I was working on. Many are not well made, are overly complicated and do much more than one single thing.

Especially in Go, where you don't have destructors to help with shutdown, having common structure in place to help tear down components has always been a net benefit for me.


I’m sure there’s a place for them.

But when micro-services are so common, it seems like people use them (Spring) because everyone else does, not because they actually provide needed value.


Spring is an overly complicated mess. I use DI when it makes things simpler, I don't see how Spring would ever do that.


Your dependency structure should just be a tree.

It should be inserted literally right next to it's first use case. Your IDE will literally point it to you with red squigglys because the places where you've added a dependency will be missing a parameter. Go to the highest one and add it on the line above.


I've never seen a tree graph, not without lots of global mutable state to cheat around DI. Your logger is just going to be needed almost everywhere.

What do you do on shutdown? In languages with destructors, that can automatically give you a call order in reverse of the construction order, but in Go you end up manually ordering things or just not having panicless shutdowns.


Okay, it's not a tree. Because multiple objects will depend on something like a logger. But it's an acyclic graph if designed properly. Which is incredibly simple to setup and teardown.

If your loggers are needed everywhere, then you just pass them as a constructor to the objects that need them. You're literally doing this with fx anyway.

Like, a logger is probably the first thing you new up in main(). So now you can pass it down as a dependency in constructors.

For shutdown you just defer your shutdown functions. Have a basic interface where your services have a Shutdown() method and then you can push them onto a stack and pop them off during shutdown.

There's no manual ordering involved. Your initialisation is a linear top down process, your shutdown is bottom up. It can't be any simpler. If you keep code as close to usage sites then there's only 1 possible order.


I agree with you on all of this. fx is not doing much more for shutdown than what you describe (calling a handler pushed to a stack created during initialization). Instead of implementing this for every app, I just prefer to use a library with great documentation and tests.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: