11 ms·
The purpose of DI is to allow the use of a DSL to instantiate and connect objects, with configuration for those objects embedded into the DSL so that the setup
by permille42 6y ago
The purpose of DI is to allow the use of a DSL to instantiate and connect objects, with configuration for those objects embedded into the DSL so that the setup and the way things work can be changed quickly without altering code.
Mocking objects for testing purposes and swapping them for the real objects is also something commonly done that is helpful.
There is no "real" DI for Golang as far as I've seen. The only DI I've seen that effectively do the sort of thing I am describing are for Java.
- lostcolony 6y agoBy DSL...you mean XML/JSON? Because that's literally the only thing I've ever seen used for DI purposes. And then invariably there's still just two versions of any given injectable interface; the one used in production, and the one used in testing.
- jolux 6y agoDI is a decoupling technique. You might only have two interfaces to begin with, but the rough idea is that writing to interfaces and using IoC allows you to make many changes by adding code without having to change old code.
- sagichmal 6y agoSure, but you don't need a framework or DSL for this in Go.
- virmundi 6y agoI don't need them in Java or Typescript. The benefit is that I don't have to write these boring, but necessary pieces. As applications grow larger, especially with Go's desire to have an interface with only one method/function, DI requires a lot of boilerplate. If there was a DI for go that used generate, then I would have compile time checking of dependencies. This would satisfy the community's sense of purity while satisfying my sense of annoyance at having to write this same process for every project.
- skjfdoslifjeifj 6y ago> The benefit is that I don't have to write these boring, but necessary pieces. The "boring, but necessary pieces" are usually just a call to a constructor. In my opinion it's almost never worth using a DI system that obfuscates dependency resolution and usually can't be checked at compile time just to avoid that.
- throwaway894345 6y agoAgreed. It seems silly to call "invoking constructors" "boring" but doing all of the same work in XML is somehow more interesting? It seems like you're just adding in a layer of indirection that does nothing besides exchange Go/Java/etc for XML/Groovy/etc at the expense that one must be familiar with the DI framework to understand how the DI files are loaded, linked together, and mapped to your application code.
- permille42 6y agoThe main benefit of constructing objects and tying them together via DI is to allow polymorphic handling of responsibilities. It is more complex than simply "I just make an interface and make everyone agree on that interfere". Everyone agreeing on the interface to use is very unlikely. The underlying data in different implementations will be different. This is something Golang fails terribly at because it doesn't have polymorphism. This is, I believe, why there are so few DI systems for Golang, and most of them are of the sort you are referring to as silly. The way Golang works, and the reccomended patterns for Golang are somewhat anti-DI.
- throwaway894345 6y ago> The main benefit of constructing objects and tying them together via DI is to allow polymorphic handling of responsibilities. This is already provided by interfaces, as previously discussed. To be clear, dependency injection makes sense; however, dependency injection frameworks don’t make sense to me. > The underlying data in different implementations will be different. This is something Golang fails terribly at because it doesn't have polymorphism. Go definitely has always had polymorphism; that’s the whole point of interfaces. > The way Golang works, and the reccomended patterns for Golang are somewhat anti-DI. DI (assemble your object graph in main() instead of distributing it across dozens of constructors a la OOP) is idiomatic Go; DI frameworks are not.
- jolux 6y agoYou don't need it, strictly speaking, in any language. But as applications scale, often an IoC container makes managing lots of dependencies easier.
- jamra 6y agoIn Go, you can just use an interface which would make things mockable for testing.
- ptr 6y agoBut you still need to send around the object that implements the interface, right? That’s “DI”.
- jamra 6y agoI don't think you need to do that. Assuming a web application... You can instantiate the object in your main func and then attach it to the server struct so that it is available in every request. When mocking, you can create a mock server that instantiates mock items that implement the interface. If you need something that is contextual, you attach it to the context. In that case, you can still use mock objects, however, you would have to use a different middleware that handles the mock context objects instead of the normal middleware.
- permille42 6y agoBy DSL I mean something like how HTML is used to make websites. HTML was derived from SGML, and is related loosely to XML, but does not contain a lot of the complexities of XML. I do not mean JSON, as JSON is not a good generalized meta-programming notation. People use it that way but it ends up ugly and hard to read. JSON can be used alright as a configuration notation, but does not represent itself well as a metaprogramming language, since it does not preserve order or allow mixed content. You can get order by using an array of course, but it is much more verbose than the equivalent content in XML or HTML. You might say, by DSL, I mean "custom markup languages".
- omginternets 6y agoWhat makes something a "real" DI framework? Wouldn't go.uber.org/fx qualify?
- permille42 6y agoI use "real" in quotes to indicate I am meaning something other than the general meaning of the word "real". What I mean by this is a specific type of DI that I view as effective and a type of meta-programming. Such a DI system does more than just auto-instantiate objects. It also provides a DSL that allows for configuration and ordering of the object instantiation during different phases of system execution. fx doesn't do that. It is primarily just a convenience way for tying things together in the single intended way. You can provide some configuration but it is not via a DSL; it is by making calls to the fx library. Realistically to do the sort of DI I am referring to with Golang there would need to be a preprocessing step during compilation that generates Golang code.
- throwaway894345 6y agoWhy do you need a DSL. Go already does the things you describe with so little boilerplate that I don’t see what value a DSL could provide to justify its own complexity.
- omginternets 6y agoI see. Echoing the sibling comment: can you provide a bit more context for the kind of programming that you do? When does such a 'scriptable' DI framework become necessary/preferable? My initial reaction is one of aversion, which makes me think I haven't encountered the kind of problem where scriptable DI solves more problems than it creates. The closest thing that comes to mind is writing software with some sort of global configuration. i.e.: the user provides some options (CLI flags, environment variables, configuration files, etc.) to change the runtime behavior of the software (e.g. use filesystem storage vs S3 storage, etc.). In Go, I've always solved this pretty straightforwardly by combining Fx and functional options [1]. [1] https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis https://dave.cheney.net/2014/10/17/functional-options-for-fr...
- sagichmal 6y agoThe things you describe are pretty strongly understood as antipatterns in Go.
- virmundi 6y agoI hope to spur conversation on this. I've seen many in the Go community argue against abstractions. Many say, "Pass the database connection in the params." Or "make the DB pool a global variable". How can you write tests for logic without having to instantiate a DB? Many gophers appear to say I should have essentially a giant transaction script for each handler (https://martinfowler.com/eaaCatalog/transactionScript.html https://martinfowler.com/eaaCatalog/transactionScript.html). The handler opens the DB, makes it at least function scoped, and all my business logic goes in the handler, or some similar method where the DB connection is passed. Now my tests are functionally integration testings. This makes them both slow, and hard to test for proper error handling. When I write code in most OOP like languages, I follow the Clean Architecture model. The use case is an actual struct with the interfaces defining the repositories/services as members. I am now free to test the use case in isolation. I can test failure cases to since I return an error, which can be easily mocked. I write a factory to create my use cases. The handlers get the factory passed in as an argument to the constructor for server. I can now test handlers in isolation by passing a factor with mocks.
- sagichmal 6y ago> Many say, "Pass the database connection in the params." ... How can you write tests for logic without having to instantiate a DB? You define and use an interface to model the DB, and use that as the mock point. Basic stuff.
- throwaway894345 6y agoTo add to your answer for those who aren't familiar with the basics of mocking, here's a StackOverflow answer that I've shared with others--maybe it will help elucidate things: https://stackoverflow.com/questions/19167970/mock-functions-in-go https://stackoverflow.com/questions/19167970/mock-functions-...
- throwaway894345 6y agoI guess maybe this would be useful for a language like C or old-school C++ where you had to imperatively build your maps and lists and allocate memory and explicitly type your variables and so on, but Go memory allocation and typing are implicit and maps and lists have a nice literal syntax so I don't see any value in a DSL. As for testing, you can already swap out real objects for test objects--that's a property that interfaces provide; I don't see how a DI framework (e.g, the DSL) helps you here. I will say that when I've had to operate a Java application, trying to do even the smallest bit of debugging had me digging through all sorts of layers of XML nonsense (Spring, various Spring plugins, and who knows what else) to figure out where the logs were being written. Note that my objection isn't "XML" (versus some other format/DSL), but rather the pointless indirection. I'm sure someone proficient in Java would have no problem, but now your sysadmins need to be Java developers in addition to system administrators (maybe not such a big deal if your shop is already a Java shop and you have people you can ask, but for people operating third party software this is a real pain point). And again, all of that tedium for no apparent value.
- permille42 6y agoThe argument for and desire for DI is closely tied to the low code movement. It is tied to configuration as code. It is certainly a different mentality from straight out coding everything. I think it is important and will grow bigger in time, because low code is a type of metaprogramming. You don't "need" to use low code stuffs or do metaprogramming, but if you know how and learn it well you can get much more complex things done quicker than coding everything. The stuff you are objecting to is due to Spring and such seeking to be a type of generalized metaprogramming instead of focusing on custom DSLs. A custom DSL will have less verbosity, not more. In a good low-code setup, all your logs should go to the "log" module, and there should be very simple configuration indicating where the logs go.