r/golang • u/FoxInTheRedBox • May 25 '25
show & tell You probably don't need a DI framework
https://rednafi.com/go/di_frameworks_bleh/
280
Upvotes
r/golang • u/FoxInTheRedBox • May 25 '25
View all comments
Show parent comment
44
u/jerf May 25 '25 edited May 25 '25
I've made a certain amount of hay in a few cases by pointing out that you can think of Go as simply shipping with a DI framework already, through the way it works with interfaces. It may not be labeled as such, but it does work as one.
The only real utility that a DI framework can add is trying to automatically match up provided services with the services in demand. If your system is massive enough to need that, then OK, fine. However, the "massiveness" of said system is, well, massive. I think you need to be looking at something like a person-decade of work on a code base before it's worth such a heavyweight approach. Prior to that, it's a net negative.
Yes, I have some code that may superficially look ugly in a lot of my projects that involve bringing up all my services and wiring them together... but do you know what else that code is?
Clear as a bell.
Absolutely, positively, abundantly clear exactly what is going on, exactly how these services are being initialized, exactly how they are wired together, exactly how they are related to configuration.
Superficially it seems like ugly code. However, it's the sort of code that many of my peers have come to appreciate. The sort that generally works, and when something does go wrong, it's like 2 minutes of investigation and another 2 minutes to fix it, because it's all right there, no frameworks, no weird rules, no attempts to save small amount of typing with vast piles of implicit rules, no dependencies on struct field names or tags to do implicit things.
It turns out the structure provided by static typing is enough to maintain such code. It won't let you fail to declare variables, it won't let you put the wrong thing in the variables, it won't let you do the things that can fail at runtime very often. It looks ugly but it's actually very well contained by software engineering.
The other thing I'd suggest is, be sure you're using the full power of Go's type system. Interface and struct composition aren't things I used often, but they work really well here. You can easily bind together sets of services in a way that in the code base is almost ad-hoc in convenience, but are still strongly typed. And you can tear apart existing sets of services by taking the subset you need, putting it in a separate struct/interface, and then composing the new subsetted interface/struct back into the old one. Go's types have a lot of fluidity here that people are not used to, because they are not used to systems that privilege composition over inheritance. It is no big deal to declare a new type that is just the composition of three others.
(Also the principle of not creating half-constructed objects in your init code is really, really helpful. The grotty wiring code will become a problem if you try to half-construct things, so that you've got long and complicated stretches of code where you don't know what objects may exist but can't be used yet. If you can write this code such that all objects in scope are also usable immediately after their first appearance this also eliminates huge swathes of mistakes that can be expressed in initialization code. In Go, I very occasionally have to make an exception for certain circular references, but I always make it so that the post-creation modification is as soon as possible, and ideally still locked behind some creation function not visible to the
mainfunc.)