[HN Gopher] Dependency injection frameworks add confusion
___________________________________________________________________
Dependency injection frameworks add confusion
Author : ingve
Score : 93 points
Date : 2025-05-25 07:41 UTC (15 hours ago)
(HTM) web link (rednafi.com)
(TXT) w3m dump (rednafi.com)
| sureglymop wrote:
| I highly agree. I especially believe that manual DI should always
| be the starting point. Eventually one can evaluate if there
| really is a need for a framework. It's already dangerous if I
| have to change the code significantly just to satisfy the
| framework.
| epolanski wrote:
| Isn't that true for every framework/library out there to some
| extent?
| srvaroa wrote:
| Looking forward to someone writing the Spring equivalent this on
| the JVM
| brabel wrote:
| Why? It would be nearly identical, just changing the names of
| the frameworks.
| superdisk wrote:
| It always blew my mind that "dependency injection" is this big
| brouhaha and warrants making frameworks, when dynamic vars in
| Lisp basically accomplish the same task without any fanfare or
| glory.
| silvestrov wrote:
| Because "big brouhaha" is what people really want.
|
| They don't want simple and easy to read code, then want to seem
| smart.
| mattmanser wrote:
| Because in statically typed languages they require a bit more
| scaffolding to get working.
|
| And it is a bit magic, and then when you need something a bit
| odd, it suddenly becomes fiddly to get working.
|
| An example is when you need a delayed job server to have the
| user context of different users depending who triggered the job
|
| They're pretty good in 95% of cases when you understand them,
| but a bit confusing magic when you don't.
| TeMPOraL wrote:
| > _when you need a delayed job server to have the user
| context of different users depending who triggered the job_
|
| I feel this is just a facet of the same confusion that leads
| to creating beautiful declarative systems, which end up being
| used purely imperatively because it's the only way to use
| them to do something useful in the real world; or, the
| "config file format lifecycle" phenomenon, where config files
| naturally tend to become ugly, half-assed Turing-complete
| programming languages.
|
| People design systems too simple and constrained for the job,
| then notice too late and have to hack around it, and then you
| get stuff like this.
| mattmanser wrote:
| Yeah, I get where you're coming from.
|
| For the standard web page lifecycle it's fine, but for
| instances like this it really does become fiddly.
|
| But often it's possible, but often a ideological stance the
| framework team have taken that leads to a poor
| documentation issue.
|
| The asp.net core team have some weird hills they die on,
| and some incredibly poor designs that stem from an over
| adherence to trendy patterns. It often feels they don't
| understand why those patterns exists.
|
| This results in them hardly documentating how to use the DI
| outside of their 'ideal' flow.
|
| They also try and push devs to use DI for injecting config.
| Which no other language does and is just unnecessarily
| complicated. And it's ended up with a system no-one really
| understands while the old System.Configuration, while
| clunky, at least automatically rebooted the app when you
| edited the config. Which is the 95% use case most devs
| would want.
| kazinator wrote:
| There is absolutely fanfare and glory, even more than about
| dependency injection.
|
| And "dynamic scope" is also a lofty-sounding term, on par with
| "dependency injection".
| danielovichdk wrote:
| Mark Seemann has written extensively about the subject.
|
| He a tremendous source of knowledge in that regard.
|
| https://blog.ploeh.dk/2017/01/27/from-dependency-injection-t...
| edvardas wrote:
| His AutoFixture C# NuGet takes away so much pain from unit test
| maintenance. It does have a learning curve.
|
| https://github.com/AutoFixture/AutoFixture
| wewewedxfgdf wrote:
| Strangely I seem to have built all of my software without
| dependency injection. I must be a terrible programmer.
| whattheheckheck wrote:
| Can you expand on that?
| wewewedxfgdf wrote:
| Yeah I once got a job and after I got the job when they found
| out I'd never done dependency injection they said "we'd never
| have hired you if we knew that." Mind you that same manager
| also believed no code should ever be written if it doesn't
| have a test written first - real code is only ever an outcome
| of writing something to match what a test expects - poof -
| all the fun and creativity went out of programming there in
| an instant.
|
| My philosophy of programming is "there's no right way to do
| it, do what works for you and makes you happy and if someone
| tell you you're doing it wrong, pay no attention - they're a
| bully and a fool".
| nssnsjsjsjs wrote:
| Which is rediculous as a taxi driver not getting the job if
| they have never taken a passenger with a trombone.
| sfn42 wrote:
| More like a carpenter not getting the job because he
| doesn't know how to frame a house.
| lmm wrote:
| That only works if if what you're doing actually works -
| not just in terms of producing code that works once, but in
| terms of producing code that's maintainable. I don't know
| for sure that you're a "terrible programmer", but you're
| saying all the things that the terrible programmers I've
| worked with tended to say.
| jaza wrote:
| I think I can understand the boat you're in, bro. Both of
| the things that you don't do, I also didn't do for quite a
| long time, and I didn't particularly see the value in doing
| them (once upon a time); but I've been on a journey to make
| them part of how I code, and I'm pretty sure that I'm a
| better coder now than I was back then.
|
| Writing tests for nearly all my code, in particular, is
| these days the only way I roll - and as for TDD (i.e. write
| the test and let it fail first, then write the actual code
| and make the test pass), I do it quite often, and I
| guarantee you that - contrary to your opinion - it makes
| coding a whole new kind of fun and creative. Dependency
| injection I still consider myself less of a ninja at, but
| I've done it (and seen it done) enough times now that I get
| it and I see the value in it.
|
| I think it's a bit stupid for an employer to say "we'd
| never have hired you if we knew you had no experience in X"
| (sure, this doesn't apply to all skills, but I'd say it
| applies to quite a few). If you're worth hiring, then
| you'll pick up X within a few months on the job. I'm
| grateful to several past employers of mine, for showing me
| the ropes of TDD and DI (among many other things).
|
| Anyway, I'm not saying that the above things are "the
| (only) right way to do it", and please don't take my above
| ramblings as making a judgement on your coding prowess. I
| agree, do what works for you. I'm just saying that there's
| always more to learn, and that you should always strive to
| be open-minded to new skills and new approaches.
| sureglymop wrote:
| What is there to be a "ninja" about when it comes to DI?
| As the article explains in the beginning it just means
| that you initialize and pass something into whatever
| depends on it instead of initializing it inside that
| thing.
|
| It's too complicated of a term for what it is because we
| generally don't say we inject arguments into a function
| when we call a function.
|
| But maybe you mean patterns building on that, e.g.
| repository/adapter patterns.
| latchkey wrote:
| This isn't about bullying someone into writing tests, it is
| about creating value that lasts over an extended period of
| time.
|
| The value of tests doesn't generally come from when you
| first write them. It comes from when you're working on a
| codebase written by someone else (who has long ago quit, or
| been fired).
|
| It helps me understand and be able to refactor their code.
| It gives me the confidence to routinely ship something to
| production and know that it won't break.
| InsideOutSanta wrote:
| _> Strangely I seem to have built all of my software without
| dependency injection_
|
| I'm going to guess that you've most likely used dependency
| injection without even thinking about it. It's one of those
| things you naturally do because it makes sense, even if you
| don't know it has an actual name, frameworks, and all that
| other stuff that often only makes it more confusing.
| sverhagen wrote:
| You must not work in an object-oriented language, then? (Which
| is very possible.) Or did you mean that you have never built
| software with a dependency injection _framework_?
| IshKebab wrote:
| It just means testing can become a lot harder. I wouldn't say
| you are necessarily a bad programmer because you don't write a
| gazillion tests.
|
| I would say you are a bad programmer for implying that DI is
| useless though.
| nssnsjsjsjs wrote:
| I agree. I had to do what the article says in Node for a project
| for $reasons but secretly I loved not using a framework, and
| having the construction explicit. I've also seen bugs because
| tests may set up DI different to prod.
| rr808 wrote:
| Love this article, Spring is a cancer in Java, its one of the
| reasons the language isn't fashionable.
| devrandoom wrote:
| Cancer? It's poisoned blood and slayer of puppies, hopes and
| dreams. It's the lord of hell.
| Squarex wrote:
| It's still miles better than what was there before in Java
| ecosystem.
| lemagedurage wrote:
| Another downside of DI is how it breaks code navigation in IDEs.
| Without DI, I can easily navigate from an instance to where it's
| constructed, but with DI this becomes detached. This variable
| implements Foo, but which implementation is it?
| rightbyte wrote:
| Ye debugability and grepability is terrible.
|
| DI seems like some sort of job security by obscurity.
| wordofx wrote:
| Not an issue in C#
| rootsofallevil wrote:
| I haven't really done any c# for 5+ years. What has changed?
|
| I remember trying to effectively reverse-engineer a codebase
| (code available but nobody knew how it worked) with a lot of
| DI and it was fairly painful.
|
| Maybe it was possible back then and I just didn't know how
| -\\_(tsu)_/-
| sverhagen wrote:
| If the rules of the dependency injection framework are well
| understood, the IDE can build a model in the background and
| make it navigable. I can't speak for C#, but Spring is
| navigable in IntelliJ. It will tell you which
| implementation is used, or if one is missing.
|
| In a Spring application there are a lot of (effective)
| singletons, the "which implementation of the variable that
| implements Foo is it" becomes also less of a question.
|
| In any case, we use Spring on a daily basis, and what you
| describe is not a real issue for us.
| DanielHB wrote:
| Is it ctrl+click takes you to the main implementation
| directly? If not it is reaaaaaallly annoying
| sverhagen wrote:
| I think so.
|
| Also, what I think is also important to differentiate
| between: dependency injection, and programming against
| interfaces.
|
| Interfaces are good, and there was a while where infant
| DI and mocking frameworks didn't work without them, so
| that folks created an interface for every class and only
| ever used the interface in the dependent classes. But the
| need for interfaces has been heavily misunderstood and
| overstated. Most dependencies can just be classes, and
| that means you can in fact click right into the
| implementation, not because the IDE understands DI, but
| because it understands the language (Java).
|
| Don't hate DI for the gotten-out-of-control "programming
| against interfaces".
| IshKebab wrote:
| In every language/IDE I've ever used ctrl-click would
| take you to the interface definition, then you have a
| second "Show implementations" step that lists the
| implementations (which is usually really slow) and
| finally you can have to select the right implementation
| from the list.
|
| It's technically a flaw of using generic interfaces,
| rather than DI. But the latter basically always implies
| the former.
| Nab443 wrote:
| This is the point, you need an IDE with advanced features
| while a text editor should be all you need to understand
| what the code is doing..
| sverhagen wrote:
| Why, as a professional, would you not use professional
| tooling. Not just for DI, but there are many benefits to
| using an IDE. If you want to hone your skills in your own
| time by using a text editor, why not. But as a
| professional, denying the use of an IDE is a disservice
| to your team. (But hey, everyone's entitled their
| opinion!)
|
| Edit: upon rereading I realize your point was about
| reading code, not writing it, so I guess that could be a
| different use case...
| zo1 wrote:
| Definitely still an issue in C#. C# devs are just comfortable
| with the way it is because they don't know better and are
| held hostage. Everything in C# world after a certain size
| will involve IOC/DI and the entire ecosystem of frameworks
| that has co-evolved with it.
|
| The issues are still there. You can't just "go to definition"
| of the class being injected into yours, even if there is only
| one. You get the Interface you expect (because hey you have
| to depend on Interfaces because of something something unit-
| testing), and then see what implements that interface. And
| no, it will not just point to your single implementation,
| it'll find the test implementation too.
|
| But where that "thing" gets instantiated is still a mystery
| and depends on config-file configured life-cycles, the
| bootstrapping of your application, whether the dependency
| gets loaded from a DLL, etc. It's black-box elephants all the
| way to the start of your application. And all that you see at
| the start is something vague like: var myApp =
| MyDIFramework.getInstance(MyAppClass); Your constructors, and
| where they get called from is in a never-ending abyss of
| thick and unreadable framework code that is miles away from
| your actual app. Sacrificed at the alter of job-creation,
| unit-testing and evangelist's talk-resume padding.
| orphea wrote:
| > You can't just "go to definition" of the class being
| injected into yours, even if there is only one.
|
| Yes, I can? At least Rider can jump to the only
| implementation, no questions asked.
|
| > And no, it will not just point to your single
| implementation, it'll find the test implementation too.
|
| It will, but is it a problem to click the correct class
| from a list of two options?
| Uvix wrote:
| > You can't just "go to definition" of the class being
| injected into yours, even if there is only one.
|
| This situation isn't unique when using DI (although
| admittedly DI does make using interfaces more common).
| However, that's what the "go to implementation" menu option
| is for.
|
| For a console app, you're right that a DI framework adds a
| lot of complexity. But for a web app, you've _already_ got
| all that framework code managing controller construction.
| If you 've got the black box anyways, might as well embrace
| it.
| asp_hornet wrote:
| C# code bases are all about ruining code navigation with
| autofac and mediatr
| surgical_fire wrote:
| In IntelliJ at least this is a non-issue.
| Kiro wrote:
| How?
| mike_hearn wrote:
| The IDE understands the DI frameworks and can show you
| which class or classes will be injected.
| diggan wrote:
| If your IDE starts to decide how you code and what kind of
| architecture/design you can use, I kind of feel like the IDE is
| becoming something more than just an IDE and probably you
| should try to find something else. But I mainly program in
| vim/nvim so maybe it's just par for the course with IDEs and I
| don't know what I'm talking about.
| hx8 wrote:
| Are you not using an LSP with your text editor? If you are
| then you'll run into the same issue because it's the
| underlying technology. If you aren't using an LSP then you're
| probably leaving some workflow efficiency on the table.
| wiseowise wrote:
| Which language? Android Studio, for example, allows you to
| navigate to Hilt injection points.
| SillyUsername wrote:
| Every so often a developer challenges the status quo.
|
| Why should we do it like this, why is the D in SOLID so important
| when it causes pain?
|
| This is lack of experience showing.
|
| DI is absolutely not needed for small projects, but once you
| start building out larger projects the reason quickly becomes
| apparent.
|
| Containers...
|
| - Create proxies wrapping the objects, if you don't centralise
| construction management it becomes difficult.
|
| - Cross cutting concerns will be missed and need to be wired
| everywhere manually.
|
| - Manage objects life cycles, not just construction
|
| It also ensures you code to the interface. Concrete classes are
| bad, just watch what happens when a team mate decides they want
| to change your implementation to suit their own use cases, rather
| than a new implementation of the interface. Multiply that by 10x
| when in a stack.
|
| Once you realise the DI pain is for managing this (and not just
| allowing you to swap implementation, as is often the the poster
| boy), automating areas prone to manual bugs, and enforcing good
| practices, the reasons for using it should hopefully be obvious.
| :)
| timclark wrote:
| The D in SOLID is for dependency INVERSION not injection.
|
| Most dependency injection that I see in the wild completely
| misses this distinction. Inversion can promote good engineering
| practices, injection can be used to help with the inversion,
| but you don't need to use it.
| SillyUsername wrote:
| Agreed, and I conflated the two since I've been describing
| SOLID in ways other devs in my team would understand for
| years.
|
| Liskov substitution for example is an overkill way of saying
| don't create an implementation that throws an
| UnsupportedOperationException, instead break the interfaces
| up (Interface Segregation "I" in SOLID) and use the interface
| you need.
|
| Quoting the theory to junior devs instead just makes their
| eyes roll :D
| layer8 wrote:
| LSP is about much more than not throwing
| UnsupportedOperationException, that's a complete
| mischaracterization.
|
| ISP isn't about avoiding UnsupportedOperationException as
| well, it's about reducing dependencies.
| thiht wrote:
| Honestly inversion kinda sucks because everybody does it
| wrong. Inversion only makes sense if you also create
| adapters, and it only makes sense to create adapters if you
| want to abstract away some code you don't own. If you own all
| the code (ie layered code), dependency inversion is
| nonsensical. Dependency injection is great in this case but
| not inversion.
| layer8 wrote:
| Moreover, dependency inversion is explicitly _not_ about
| construction, which conversely is exactly what dependency
| injection is about.
| exac wrote:
| Agreed. DI Containers / Injectors are so fundamental to writing
| software that will be testable, and makes it much easier to
| review code.
| pydry wrote:
| It's not just not needed for small projects it is actively
| harmful.
|
| It's also actively unhelpful for large projects which have
| relatively more simple logic but complex interfaces with other
| services (usually databases).
|
| DI multiplies the amount of code you need - a high cost for
| which there must be a benefit. It only pays off in proportion
| to the ratio of complexity of domain logic to integration
| logic.
|
| Once you have have enough experience on a variety of different
| projects you should hopefully start to pick up on the trade
| offs inherent in using it to see when it is a good idea and
| when it has a net negative cost.
| ssijak wrote:
| Language and/or library issue. DI helps code be easier to follow,
| more decoupled and read with less boilerplate AND helps testing
| much easier.
|
| If you are on node/ts look at effect-ts.
| ramon156 wrote:
| Can you show a project that effectively uses effect-ts? The
| docs is a tsunami of information that just looks to try to make
| a whole new language out of TS. If someone else had to review
| my code I doubt they knew what was going on
| Kinrany wrote:
| One thing that can motivate a dependency container is a complex
| chain of constructors.
| throwaway21371 wrote:
| > But that reflection-driven magic is also where the pain starts.
| As your graph grows, it gets harder to tell which constructor
| feeds which one. Some constructor take one parameter, some take
| three. There's no single place you can glance at to understand
| the wiring. It's all figured out inside the container at runtime.
|
| That's the whole point. Depdendency Inversions allows you to
| write part of the code in separation, without worrying about all
| the dependencies of each component you create and what creates
| what where.
|
| If your code is small enough that you can keep all the
| dependencies in your head at the same time and it doesn't slow
| you down much to pass them all around all the time - DI isn't
| worth it.
|
| If it becomes an issue - DI starts to shine. There are other
| solutions as well, obviously (mostly in the form of Object-
| Orientified global variables - for example you keep everything in
| GameWorld object and pass it everywhere).
| surgical_fire wrote:
| Dependency Injection has a fancy name that makes some developers
| uncomfortable, but it's really just all about making the code
| easier to test. Basically everything that the class depends upon
| has to be informed during construction.
|
| This can be done manually, but becomes a chore super fast - and
| will be a very annoying thing to maintain as soon as you change
| the constructor of something widely used in your project to
| accept a new parameter.
|
| Frameworks typically just streamline this process, and offers
| some flexibility at times - for example, when you happen to have
| different implementations of the same thing. I find it funny that
| people rally against those Frameworks so often.
|
| To make things more concrete, let's say you have a method that
| gets the current date, and has some logic there (for example, it
| checks if today is EOM to do something). In Java, you could do
| `Instance.now()` to do this.
|
| This will be a pain in the ass to test, you might need to test,
| for example a date when there's a DST change, or February 28th in
| a leap year, etc. With DI you can instead inject an
| `InstantSource` to your code, and on testing you can just mock
| the dependency to have a predictable date on each test.
| ffsm8 wrote:
| You're talking from the perspective of Java, which has been
| designed from the ground up with dependency injection in mind.
|
| Dependency injection is the inversion of control pattern at the
| heart, which is something like oxygen to a Java dev.
|
| In other languages, these issues are solved differently. From
| my perspective as someone whoes day job has been roughly 60+%
| Java for over 10 yrs now... I think I agree with the central
| message of the article. Unless you're currently in Java world,
| you're probably better off without it.
|
| These patterns work and will on paper reduce complexity - but
| if comes at the cost of a massively increased mental overhead
| if you actually need to address a bug that touches more then a
| miniscule amount of code.
|
| /Edit: and I'd like to mention that the article actually only
| dislikes the frameworks, not the pattern itself
| mattmanser wrote:
| DI wasn't around when Java (or .Net) came out. DI is a fairly
| new thing too, relatively speaking, like after ORMs and
| anonymous methods. Like after Java 7 I think. Or maybe 8? Not
| a Java person myself.
|
| I know in .net, it was only really the switch to .net core
| where it became an integral part of the frameworks. In MVC 5
| you had to add a third party DI container.
|
| So how can it have been designed for it from the ground up?
|
| In fact, if you're saying 10 years, that's roughly when DI
| became popular.
|
| You're wrong about other languages not needing it, yes
| statically typed languagess need it for unit testing, but you
| don't seem to realize that from a practical perspective DI
| solves a lot of the problems around request lifetimes too.
| And from an architectural context it solves a lot of the
| problem of how to stop bad developers overly coupling their
| services.
|
| Before DI people often used static methods, so you'd have a
| real mess of heavily interdependent services. It can still
| happen now but.its nowhere near as bad as the mess of
| programming in the 2000s.
|
| DI helped reduce coupling and spaghetti code.
|
| DI also forces you to 'declare' your dependencies, so it's
| easy to see when a class has got out of control.
|
| Edit: I could keep on adding, but one final thing. Java and
| .Net are actually quite cumbersome to use DI, and Go is
| actually easier. Because Go has implicit interfaces, but
| older languages don't and it would really help reduce boiler
| plate DI code.
|
| A lot of interfaces in Java/C# only exist to allow DI tow
| work, and are otherwise a pointless waste of time/code.
| ollysb wrote:
| The Spring framework was released in 2003 when Java was at
| v1.4. From memory it wasn't the first DI framework either.
| layer8 wrote:
| The term was coined in 2004:
| https://www.martinfowler.com/articles/injection.html
|
| It's not correct that Java was designed for it, unless
| you want to call class loading dependency injection. It's
| merely that Java's reflection mechanism happened to
| enable DI frameworks. The earlier Java concept was
| service locaters (also discussed in the article linked
| above).
| thiht wrote:
| Or don't use a DI framework, and DI just becomes a fancy name
| for "creating instances" and "passing parameters". That's what
| we do in Go and there's no way I would EVER use a DI framework
| again. I'd rather be unemployed than work with Spring.
| surgical_fire wrote:
| > DI just becomes a fancy name for "creating instances" and
| "passing parameters".
|
| I literally addressed this in the post you are replying to. I
| think your problem is reading comprehension, not dependency
| injection or framework usage.
|
| Then again, understanding Design Patterns will be difficult
| if you can't even parse you way through a handful of
| paragraphs written in plain English.
|
| I'll bother to repeat myself:
|
| >> This can be done manually, but becomes a chore super fast
| - and will be a very annoying thing to maintain as soon as
| you change the constructor of something widely used in your
| project to accept a new parameter.
|
| >> Frameworks typically just streamline this process, and
| offers some flexibility at times - for example, when you
| happen to have different implementations of the same thing. I
| find it funny that people rally against those Frameworks so
| often.
|
| There. Maybe stripped of the surrounding context will make it
| easier.
|
| > I'd rather be unemployed than work with Spring.
|
| That's really a you problem.
| thiht wrote:
| No need to be aggressive, I just disagree that DI
| frameworks streamline anything, they just make things more
| opaque and hard to trace.
|
| > will be a very annoying thing to maintain as soon as you
| change the constructor of something widely used in your
| project to accept a new parameter.
|
| That for example is just not true. You add a new parameter
| to inject and it breaks the injection points? Yeah that's
| expected, and suitable. I want to know where my changes
| have any impact, that's the point of typing things.
|
| A lot of things deemed "more maintainable" really aren't.
| Never has a DI framework made anything simpler.
| surgical_fire wrote:
| > That for example is just not true. You add a new
| parameter to inject and it breaks the injection points?
|
| Perhaps you never worked in a sufficiently large
| codebase?
|
| It is very annoying when you need to add a dependency and
| suddenly you have to touch 50+ injection points because
| that thing is widely used. Been there, done that, and by
| God I wished I had Dagger or Spring or anything really to
| lend me a hand.
|
| DI frameworks are a tool like any other. When properly
| used in the correct context they can be helpful.
| culturedsystems wrote:
| "It is very annoying when you need to add a dependency
| and suddenly you have to touch 50+ injection points
| because that thing is widely used"
|
| You don't have to update the injection points, because
| the injection points don't know the concrete details of
| what's being injected. That's literally the whole point
| of dependency injection.
|
| Edited to add: Say you have a class A, and this is a
| dependency of classes B, C, etc. Using dependency
| injection, classes B and C are passed instances of A,
| they don't construct it themselves. So if you add a
| dependency to A, you have to change the place that
| constructs A, of course, but you don't have to change B
| and C, because they have nothing to do with the
| construction of A.
| layer8 wrote:
| This is the only reason why DI frameworks exist.
| _However_ , this issue can be largely avoided by working
| with configuration objects (parameter objects [0]) from
| the start, that get passed around. Then you only need to
| apply the change in a small number of parameter-object
| classes, if not just a single one.
|
| [0] https://wiki.c2.com/?ParameterObject
| baq wrote:
| The one thing DI frameworks unarguably and decisively
| solve by design (if accidentally, but it doesn't matter)
| is control over static initialization. I'd say you
| haven't truly lived if your C++ didn't crash before
| calling main(), but it helps in large JS and Python
| projects just the same.
| layer8 wrote:
| How do they solve that? If constructors require certain
| parameters, _someone_ has to pass them. If it's not a
| top-level object, the instantiating code will pass it and
| have a corresponding constructor or method parameter
| itself. At the top level, _main()_ or equivalent will
| pass the parameter. Where is the problem?
| babyent wrote:
| Chill dawg
| IshKebab wrote:
| > Dependency Injection has a fancy name that makes some
| developers uncomfortable, but it's really just all about making
| the code easier to test.
|
| It's not just a fancy name. I'd argue it's a _confusing_ name.
| The "$25 name for a 5c concept" quote is brilliant. The name
| makes it sound like it's some super complicated thing that is
| difficult to learn, which makes it _harder_ to understand. I
| would say "dynamic programming" suffers the same problem.
| Maybe "monads".
|
| How about we rename it? "Generic dependencies" or "Non hard-
| coded dependencies" or even "dependency parameters"?
| kevmo314 wrote:
| "Global variables that can pass code review"
| layer8 wrote:
| The name is confusing because originally [0] it was
| exclusively about dynamic injection by frameworks. Somehow it
| morphed into just meaning suitable constructor parameters, at
| least in some language communities.
|
| I like "dependency parameters". Dependencies in that sense
| are usually what is called _service objects_ , so "service
| parameters" might be even clearer.
|
| [0] https://www.martinfowler.com/articles/injection.html#Form
| sOf...
| kazinator wrote:
| It's not just about testing. When any code constructs its own
| object, and that object is actually an abstraction of which we
| have many implementations, that code becomes stupidly
| inflexible.
|
| For instance, some code which prints stuff, but doesn't take
| the output stream as a parameter, instead hard-coding to a
| standard output stream.
|
| That leaves fewer options for testing also, as a secondary
| problem.
| layer8 wrote:
| That's just parameterization, though. It's overkill to call
| every parameter (or even most parameters) a "dependency".
| loevborg wrote:
| 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.
| pdpi wrote:
| 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!
| loevborg wrote:
| 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()));
| } }
| physPop wrote:
| 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.
| loevborg wrote:
| 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...
| ptx wrote:
| > _James Shore calls this Parameterless Instantiation_
|
| Mark Seemann calls it the Constrained Construction anti-
| pattern: https://blog.ploeh.dk/2011/04/27/Providerisnotapatte
| rn/#4c7b...
| almostdeadguy wrote:
| It often is not enough. Singletons frequently need to be
| provided to things (a connection pool, etc.).
| layer8 wrote:
| > Frameworks typically just streamline this process, and offers
| some flexibility at times - for example, when you happen to
| have different implementations of the same thing.
|
| The very purpose of DI is to allow using a different
| implementation of the same thing in the first place. You
| shouldn't need a framework to achieve that. And my personal
| experience happens to match that.
| shadowgovt wrote:
| I frequently find DI pattern to show up in Java... But I also
| frequently find that Java gives me all the handcuffs of systems
| languages with few of the benefits of more flexible duck-typing
| languages.
|
| If you can't monkey-patch the getDate function with a mock in a
| testing context because your language won't allow it, that's a
| language smell, not a pattern smell.
| DanielHB wrote:
| DI is a very religious concept, people hate it or love it.
|
| I myself am on the dislike camp, I have found that mocking
| modules (like you can with NodeJS testing frameworks) for tests
| gives most of the benefits with way less development hell.
| However you do need to be careful with the module boundaries
| (basically structure them as you would with DI) otherwise you can
| end up with a very messy testing system.
|
| The value of DI is also directly proportional to the size of the
| service being tested, DI went into decline as things became more
| micro-servicy with network-enforced module boundaries. People are
| just mocking external services in these kind of codebases instead
| of internal modules, which makes the boundaries easier.
|
| I can see strict DI still being useful in large monolith
| codebases worked by a lot of hands, if only to force people to
| structure their modules properly.
| grxar wrote:
| DI frameworks add confusion and require more unnecessary memory
| in advance
| Traubenfuchs wrote:
| Don't hate a paradigm because you only experienced one bad
| implementation of it.
|
| In IntelliJ, with the Spring Framework, you can have thorough
| tooling: You can inspect beans, their dependencies, you even get
| a visual bean graph, you can write mocks and test dependencies
| and don't even need interfaces anymore and if a dependency is
| missing, you will receive an IDE warning before runtime.
|
| I do not understand why people are so excited about a language
| and its frameworks where the wheel is still actively being
| reinvented in a worse way.
| KronisLV wrote:
| IoC is nice (or DI as a concept in particular), but DI
| frameworks/libraries sometimes are a mess.
|
| I've had my fair share of Java and Spring Boot projects and it
| breaks in all sorts of stupid ways there, even things like the
| same exact code and runtime environment working in a container
| that's built locally, but not working when the "same" container
| is built on a CI server: https://blog.kronis.dev/blog/it-works-
| on-my-docker
|
| Literally a case where Spring Boot DI just throws a hissy fit
| that you cannot easily track down, where I had to mess around
| with the @Lazy annotation (despite the configuration to permit
| that being explicitly turned on too) in over 100 places to
| resolve the issue, plus then when you try to inject a list of all
| classes that implement an interface with @Lazy it doesn't seem
| like their order is guaranteed either so your DefaultValidator
| needs to be tacked on to that list manually at the end.
|
| Sorry about the Java/Spring rant.
|
| It very much feels like the proper place for most DI is at
| compile time (like Dagger does for Java, seems closer to wire)
| not at runtime, or just keep IoC without a DI framework/library
| and having your code look a bit more like this:
| @Override public void run(final BackendConfiguration
| configuration, final Environment
| environment) throws IOException, TimeoutException { //
| Initialize our data stores mariaDBManager = new
| MariaDBManager(configuration, environment);
| redisManager = new RedisManager(configuration);
| rabbitMQManager = new RabbitMQManager(configuration);
| // Initialize our generic services keyValueService =
| new KeyValueService(redisManager); sessionService = new
| SessionService(keyValueService, configuration);
| queueService = new QueueService(rabbitMQManager);
| // Initialize services needed by resources
| accountService = new AccountService(mariaDBManager);
| accountBalanceService = new
| AccountBalanceService(mariaDBManager); auctionService =
| new AuctionService(mariaDBManager); auctionLotService =
| new AuctionLotService(mariaDBManager);
| auctionLotBidService = new AuctionLotBidService(mariaDBManager);
| // Initialize background processes based on feature configuration
| if (configuration.getApplicationConfiguration().getFeaturesConfig
| uration().isProcessBids()) { bidListener = new
| BidListener(queueService, auctionLotBidService,
| auctionLotService, accountBalanceService); try {
| bidListener.start(); logger.info("BidListener
| started"); } catch (IOException e) {
| logger.error("Error starting BidListener: {}", e.getMessage(),
| e); } } // Register
| resources based on feature configuration if (configurat
| ion.getApplicationConfiguration().getFeaturesConfiguration().isAc
| counts()) { environment.jersey().register(new
| AccountResource(accountService, accountBalanceService,
| sessionService, configuration)); } if (config
| uration.getApplicationConfiguration().getFeaturesConfiguration().
| isBids()) { environment.jersey().register(new
| AuctionResource( auctionService,
| auctionLotService, auctionLotBidService, sessionService,
| queueService)); } ... }
|
| Just a snippet of code from a Java Dropwizard example project,
| not all of its contents either, but should show that it's nothing
| impossibly difficult. Same principles apply to other languages
| and tech stacks, plus the above is unequivocally easier to put a
| breakpoint in and debug, vs some dynamic annotation or convention
| based mess.
|
| Overall, I agree with the article, even across multiple
| languages.
| chvid wrote:
| You probably don't need functional programming. Here is how to do
| it with a for-loop.
|
| You don't see many articles written like that because it kinda
| would be obvious that the author hasn't bothered to understand
| the approach that he is critizing.
|
| Yet when it comes to OO concepts people from "superior" platforms
| like Go or the FP crowd just cannot let go of airing their
| ignorance.
|
| Just leave OO alone unless you are genuinely interested in the
| approach.
| mrkeen wrote:
| What is the OO approach?
| ac130kz wrote:
| DI is fine, if it is fully typed, and objects are explicitly
| initiated by the user, and the DI only does thread-safe
| dependency resolution.
| hyperbolablabla wrote:
| Common sense is in short supply these days. It's a shame we need
| blog posts like these to outline how much you lose when you go
| with the "magic" approach. Devs just seem to be allergic to
| simple but verbose code.
| jaza wrote:
| As a (mainly) Python dev, I'm aware that there are DI frameworks
| out there, but personally I haven't to date used any of them.
|
| My favourite little hack for simple framework-less DI in Python
| these days looks something like this: # The
| code that we want to call def do_foo(sleep_func = None):
| _sleep_func = sleep_func if sleep_func is not None else
| time.sleep for _ in range(10):
| _sleep_func(1) # Calling it in non-test code
| # (we want it to actually take 10 seconds to run) def
| main(): do_foo() # Calling it in test
| code # (we want it to take mere milliseconds to run, but
| nevertheless we # want to test that it sleeps 10 times!)
| def test_do_foo(): mock_sleep_func = MagicMock()
| do_foo(sleep_func=mock_sleep_func) assert
| mock_sleep_func.call_count == 10
| benhoyt wrote:
| Yeah, I use this sometimes too (even though Python makes
| "monkey patching" easy). However, note that it's simpler and
| clearer to use a default value for the argument:
| def do_foo(sleep=time.sleep): for _ in range(10):
| sleep(1)
| kazinator wrote:
| The system is composed of classes which are nicely encapsulated,
| independent and obey Liskov substitution and all that. You can
| connect them in different arrangements and they play along
| nicely.
|
| But then some classes which use other classes hard code those
| classes in their constructor. They then work with those specific
| hard-coded classes. It's like if someone crazy-glued some of our
| Lego blocks together.
|
| We recognize this problem and allow the sister objects to be
| configurable.
|
| Then some opinionated nubmnut comes along and says, "hey, we
| should call this simple correction 'dependency injection'". And
| somehow, everyone listens.
| latchkey wrote:
| I've written a couple large apps using Uber's FX and it was
| great. The reason why it worked so well was that it forced me to
| organize my code in such a way as to make it super easy to test.
| It also had a few features around startup/shutdown and the
| concept of "services" and "logging" that are extremely convenient
| in an app that runs from systemd.
|
| All of the complexity boils down to the fact that you have to
| remember to register your services before you can use them. If
| you forget, the stack trace is pretty hard to debug. Given that
| you're already deep into FX, it becomes pretty natural to
| remember this.
|
| That said, I'd say that if you don't care about unit tests or you
| are good enough about always writing code that already takes
| things in constructors, you probably don't need this.
| wiseowise wrote:
| The article can be summarized by: "I'm using language that is
| stuck in 80s".
|
| Java and Dagger 2 have solved the DI years ago. Fast, compile
| time safe and easy to use.
| jillesvangurp wrote:
| Separating your glue code from your business logic is a good idea
| for several reasons. That's all dependency injection, or
| inversion of control is. It's more of a design pattern than a
| framework thing. And structuring your code right means that
| things are a bit easier to test and understand as well (those two
| things go hand in hand). Works in C, Rust, Kotlin, Javascript,
| Java, Ruby, Python, Scala, Php, etc. The language doesn't really
| matter. Glue code needs to be separate from whatever the code
| does.
|
| Some languages seem to naturally invite people to do the wrong
| thing. Javascript is a great example of this that seems to bring
| out the worst in people. Many of the people wielding that aren't
| very experienced and when they routinely initialize random crap
| in the middle of their business logic executed asynchronously via
| some event as a side effect of a butterfly stirring its wings on
| the other side of the planet, you end up with the typical flaky
| untestable, and unholy mess that is the typical Javascript code
| base. Exaggerating a bit here of course but I've seen some
| epically bad code and much of that was junior Javascript
| developers being more than a little bit clueless on this front.
|
| Doing DI isn't that hard. Just don't initialize stuff in places
| that do useful things. No exceptions. Unfortunately, it's hard to
| fix in a code base that violates that rule. Because you first
| have to untangle the whole spaghetti ball before you can begin
| beating some sense into it. The bigger the code base, the more
| likely it is that it's just easier to just burn it down to the
| ground and starting from scratch. Do it right and your code might
| still be actively maintained a decade or more in the future. Do
| it wrong and your code will probably be unceremoniously deleted
| by the next person that inherits your mess.
| rco8786 wrote:
| What you're describing is generally good coding practice. But
| not, I don't think, what people associate with DI.
| shadowgovt wrote:
| This will vary from firm to firm depending on what you're
| writing, but I generally find DI to be more complexity than
| needed. Granted,
|
| - I'm willing to rewrite some code if we decide that a core
| library needs to get swapped out
|
| - I'm always using languages that allow monkey-patching, so I'm
| not struggling to test my code because, for example, it's hard to
| substitute a mock implementation for `Date.now()`.
|
| DI makes more sense if you're not in that position. But in that
| position, DI adds "you need these three files in your brain at
| the same time to really understand what's going on" complexity
| that I seek to avoid.
|
| (Also, DI overlaps with generics, and to the extent that you can
| make things generic and it makes sense to do so, you should).
| jongjong wrote:
| I don't like DI as a concept because it typically obscures the
| path/source of the file where relevant code is located. DI trades
| off long term readability for short term implementation
| convenience. Maintainability requires strict adherence and
| awareness of conventions which it something that many developers
| are terrible with.
___________________________________________________________________
(page generated 2025-05-25 23:01 UTC)