4 ms·
Why is it a pain to inject dependencies manually? I think this is because people assume for some reason that a class isn't allowed to instantiate its own depend
by loevborg 1y ago
Why is it a pain to inject dependencies manually? I think this is because people assume for some reason that a class isn't allowed to instantiate its own dependencies.
If you lift that assumption, the problem kind of goes away. James Shore calls this Parameterless Instantiation: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#instantiation https://www.jamesshore.com/v2/projects/nullables/testing-wit...
It means that in most cases, you just call a static factory method like create() rather than the constructor. This method will default to using Instance.now() but also gives you a way to provide your own now() for tests (or other non-standard situations).
At the top of the call stack, you call App.create() and boom - you have a whole tree of dependencies.
- pdpi 1y agoIf the class instantiates its own dependencies then, by definition, you're not injecting those dependencies, so you're not doing dependency injection at all!
- loevborg 1y agoThat seems too dogmatic. Does the fact that Foo has a static function that knows how to create its dependencies disqualify the class from being a case of DI? class Foo { private nowFn: () => Date; constructor(nowFn: () => Date) { this.nowFn = nowFn; } doSomething() { console.log(this.nowFn()); } static create(opts: { nowFn?: () => Date } = {}) { return new Foo(opts.nowFn ?? (() => new Date())); } }
- physPop 1y agoYes because injecting nowFn is trivial and not a case for DI. Consider a database handle, or network socket, or http response payload. Clearly each class shouldn't be making its own version of those.
- cyanydeez 1y agoYou're nitpicking for no good reason. You can create global handles to each of those items, and let them instantiate with the class or override them with a create function. Dependency injection boils down the question of whether or not you can dynamically change a dependency at runtime.
- loevborg 1y agoYou're right, stateful dependencies like DB handles need to be passed in manually, and that's a bit of extra legwork you need to do.
- Charon77 1y agoJust use a static variable to the DB instance / pool, maybe using singletons, so everyone needing it have access.
- deleted 1y ago[deleted]
- loevborg 1y agoIn other words, despite all the noise to the contrary, hard-coded dependencies are fine. James explains this a lot better than I can: https://www.jamesshore.com/v2/blog/2023/the-problem-with-dependency-injection-frameworks https://www.jamesshore.com/v2/blog/2023/the-problem-with-dep...
- dkkergoog 1y ago[dead]
- ptx 1y ago> James Shore calls this Parameterless Instantiation Mark Seemann calls it the Constrained Construction anti-pattern: https://blog.ploeh.dk/2011/04/27/Providerisnotapattern/#4c7b89305e744b22b9cadb8fd4f1d74e https://blog.ploeh.dk/2011/04/27/Providerisnotapattern/#4c7b...
- almostdeadguy 1y agoIt often is not enough. Singletons frequently need to be provided to things (a connection pool, etc.).
- lgas 1y agoMaybe I'm misunderstanding what you're saying but a connection pool seems like almost a canonical example of something that shouldn't be a singleton. You might want connection pools that connect to different databases or to the same database in read-only vs. read/write mode, etc.
- stickfigure 1y agoThen these different pools can be separate singletons. You still don't want to instantiate multiple identical pools. You can use the type system to your advantage. Cut a new type and inject a ReadOnlyDataSource or a SecondDatabaseDataSource or whatnot. Figure out what should only have one instance in your app, wrap a type around it, put it in the singleton scope, and inject that.
- MrJohz 1y agoI think the point is that you can already do all of that with hand-wired dependency injection. I wrote about an example of that a couple of years ago here: https://jonathan-frere.com/posts/how-i-do-dependency-injection-with-closures/ https://jonathan-frere.com/posts/how-i-do-dependency-injecti... This has the advantage that you don't need an extra framework/dependency to handle DI, and it means that dependencies are usually much easier to trace (because you've literally got all the code in your project, no metaprogramming or reflection required). There are limits to this style of DI, but in practice I've not reached those limits yet, and I suspect if you do reach those limits, your DI is just too complicated in the first place.
- almostdeadguy 1y agoI think most people using these frameworks are aware that DI is just automated instantiation. If your program has a limited number of ways of composing instantiations, it may not be useful to you. The amount of ceremony reduced may not be worth the overhead.
- bluGill 1y agomany of the things I dependeon are shared services where there should only be one instance. Singleton means a global variable. a di framework lets me have on without a global - meaning if I need a second service I can do it with just a change to how the di works. there is no right answer of course. Time should be a global so that all timers/clocks advance in lock step. I hav a complex fake time system that allows my tests to advance minutes at a time without waiting on the wall clock. (If you deal with relativity this may not work - for everyone else I encourage it)