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

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-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.



If the class instantiates its own dependencies then, by definition, you're not injecting those dependencies, so you're not doing dependency injection at all!


That 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()));
      }
    }


Yes 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.


You'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.


Just use a static variable to the DB instance / pool, maybe using singletons, so everyone needing it have access.


You'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.


In 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-dep...


> James Shore calls this Parameterless Instantiation

Mark Seemann calls it the Constrained Construction anti-pattern: https://blog.ploeh.dk/2011/04/27/Providerisnotapattern/#4c7b...


many 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)


It often is not enough. Singletons frequently need to be provided to things (a connection pool, etc.).


Maybe 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.


I meant "singleton" in the sense of a single value for a type shared by anything that requires one, i.e. a Guice singleton ( https://github.com/google/guice/wiki/scopes#singleton ) not a value in global scope. Or maybe a single value by type with an annotation, the salient point is that there are values in a program that must be shared for correctness. Parameterless constructors prohibit you from using these (unless you have global variables).


Then 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.


I 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-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.


I 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.


This conversation repeats itself ad infinitum around DI, ORMs, caching, security, logging, validation, etc, etc, etc... no, you don't need a framework. You can write your own. There are three common outcomes of this:

* Your framework gets so complicated that someone rips it out and replaces it with one of the standard frameworks.

* Your framework gets so complicated that it turns into a popular project in its own right.

* Your app dies and your custom framework dies with it.

The third one is the most common.


I'm not suggesting a custom framework here, I'm suggesting no DI framework at all. No reflection, no extra configuration, no nothing, just composing classes manually using the normal structure of the language.

At some point this stops working, I agree — this isn't necessarily an infinitely scalable solution. At that point, switching to (1) is usually a fairly simple endeavour because you're already using DI, you just need to wire it together differently. But I've been surprised at the number of cases where just going without a framework altogether has been completely sufficient, and has been successful for far longer than I'd have originally expected.


If you construct an object once, then what is the difference between that and a singleton?

The answer is scope. Singletons exist explicitly for the purpose of violating scoping for the convenience of not having to thread that dependency through constructors.

https://testing.googleblog.com/2008/08/root-cause-of-singlet...


Calling these "singletons" maybe created confusion here, I'm more talking about singleton in the sense of a value that is created once and shared (i.e. injecting a value as a "Singleton" via something like Guice). I'm not arguing that you need to have values in global scope, I'm arguing that parameterless constructors prevent you from using shared values (actually _unless_ you have singletons in global scope).




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

Search: