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 · 2h ago
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 · 2h ago
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.
thiht · 2h ago
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 · 1h ago
> 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 · 1h ago
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 · 13m ago
> 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.
lemagedurage · 3h ago
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 · 3h ago
Ye debugability and grepability is terrible.
DI seems like some sort of job security by obscurity.
diggan · 3h ago
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.
surgical_fire · 3h ago
In IntelliJ at least this is a non-issue.
Kiro · 2h ago
How?
mike_hearn · 2h ago
The IDE understands the DI frameworks and can show you which class or classes will be injected.
wordofx · 3h ago
Not an issue in C#
rootsofallevil · 3h ago
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 ¯\_(ツ)_/¯
sverhagen · 3h ago
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.
Nab443 · 2h ago
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 · 2h ago
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...
DanielHB · 3h ago
Is it ctrl+click takes you to the main implementation directly? If not it is reaaaaaallly annoying
sverhagen · 2h ago
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".
rr808 · 3h ago
Love this article, Spring is a cancer in Java, its one of the reasons the language isn't fashionable.
devrandoom · 3h ago
Cancer? It's poisoned blood and slayer of puppies, hopes and dreams. It's the lord of hell.
Squarex · 3h ago
It's still miles better than what was there before in Java ecosystem.
DanielHB · 2h ago
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.
wewewedxfgdf · 3h ago
Strangely I seem to have built all of my software without dependency injection. I must be a terrible programmer.
InsideOutSanta · 3h ago
>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 · 3h ago
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?
No comments yet
whattheheckheck · 3h ago
Can you expand on that?
wewewedxfgdf · 3h ago
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".
lmm · 3h ago
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 · 2h ago
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.
nssnsjsjsjs · 3h ago
Which is rediculous as a taxi driver not getting the job if they have never taken a passenger with a trombone.
SillyUsername · 3h ago
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 · 3h ago
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 · 2h ago
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
thiht · 2h ago
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.
exac · 3h ago
Agreed. DI Containers / Injectors are so fundamental to writing software that will be testable, and makes it much easier to review code.
pydry · 3h ago
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.
superdisk · 4h ago
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 · 3h ago
Because "big brouhaha" is what people really want.
They don't want simple and easy to read code, then want to seem smart.
No comments yet
mattmanser · 3h ago
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 · 2h ago
> 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.
jaza · 2h ago
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
No comments yet
danielovichdk · 3h ago
Mark Seemann has written extensively about the subject.
He a tremendous source of knowledge in that regard.
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 · 4h ago
Isn't that true for every framework/library out there to some extent?
ac130kz · 2h ago
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 · 2h ago
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.
throwaway21371 · 3h ago
> 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).
nssnsjsjsjs · 3h ago
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.
ssijak · 3h ago
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 · 3h ago
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
srvaroa · 4h ago
Looking forward to someone writing the Spring equivalent this on the JVM
brabel · 3h ago
Why? It would be nearly identical, just changing the names of the frameworks.
delusional · 3h ago
That's not going to happen. The people using Spring on the JVM generally don't think enough to write about their thoughts.
TeMPOraL · 17m ago
Or maybe they just don't have oversized egos.
The key to using a framework effectively, whether it's Spring in Java or SAP for your business, is to accept that the framework knows better than you - especially when it objectively does not- and when there's a difference between how you or your business think of things, vs. how the framework frames them, it's your thoughts and your business that must change. Otherwise, you're fighting the framework, and that's worse than just not using it.
chvid · 2h ago
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.
grxar · 2h ago
DI frameworks add confusion and require more unnecessary memory in advance
Kinrany · 3h ago
One thing that can motivate a dependency container is a complex chain of constructors.
KronisLV · 2h ago
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().getFeaturesConfiguration().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 (configuration.getApplicationConfiguration().getFeaturesConfiguration().isAccounts()) {
environment.jersey().register(new AccountResource(accountService, accountBalanceService, sessionService, configuration));
}
if (configuration.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.
Traubenfuchs · 2h ago
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.
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.
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
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.
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.
> 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.
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.
DI seems like some sort of job security by obscurity.
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 ¯\_(ツ)_/¯
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.
Edit: upon rereading I realize your point was about reading code, not writing it, so I guess that could be a different use case...
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".
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.
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.
No comments yet
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".
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.
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. :)
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.
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
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.
They don't want simple and easy to read code, then want to seem smart.
No comments yet
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.
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.
My favourite little hack for simple framework-less DI in Python these days looks something like this:
No comments yet
He a tremendous source of knowledge in that regard.
https://blog.ploeh.dk/2017/01/27/from-dependency-injection-t...
https://github.com/AutoFixture/AutoFixture
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).
If you are on node/ts look at effect-ts.
The key to using a framework effectively, whether it's Spring in Java or SAP for your business, is to accept that the framework knows better than you - especially when it objectively does not- and when there's a difference between how you or your business think of things, vs. how the framework frames them, it's your thoughts and your business that must change. Otherwise, you're fighting the framework, and that's worse than just not using it.
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.
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:
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.
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.