I think there are a lot of Java developers, that have just never worked without a DI framework, and just don't have a grasp on just how simple it can be to write code without one.
As someone who hated Java, used it for a few years, and now occasionally misses it...
I only miss DI. I miss being able to say "this system depends on these external things" and having a consistent, convenient way of sharing/swapping/testing those components and dependencies.
The solution in other languages? Unstructured globals, deep argument passing, or monkey patching with mocks?!
Yea, I can write simpler code without DI... By ignoring a bunch of stuff.
When you have lots of things-that-create-things-that-create-things, this gets tedious really fast. DI frameworks exist because they result in a lot less code that does nothing but pass dependences along.
This reminds me of SQL/ORM debate. "Just use SQL!" Sure, until you get tired of typing the same SQL over and over and realize you can cut out most of that crap by adding an ORM.
If you take the single responsibility principle even as much as half-seriously, the problem domain more or less decides which things will create which things. If your software platform can't support that, you get spaghetti mess when programmers inevitably build workarounds.
You know, you hear Java repeat things like that a lot, while Go programs just tend to stay simple and readable. It's either the culture or the language causing the problem. shrug
Why? With such a well-known framework like Spring, you will get the benefit of any Spring-developer knowing instantly the conventions (which is not true with your in-house conventions where I will have to hunt down where does this class come from, oh this ugly abstraction which is buggy as well), less code is less opportunity to introduce bugs, less thing to maintain. Annotations are basically just a declarative DSL for a significant chunk of your code base.
I really don’t see any cons, other than a slight learning curve (and yeah sure, “developers” that just bash keys will have trouble with understanding what does an annotation do and blindly copy-pasting them can be dangerous but they will also fk-up regular code as well..)
How is that worthy? You pretty much only have to look at the topmost exception, or at worst the causing one. Whether it has 100 lines after or 3 doesn’t matter, not the slightest.
But how do you handle configuration then ? At some point you want a user-facing UI where the available features (which are generally classes) are listed and the user can choose the feature, say which log backend is enabled, without having to change code - that's the whole point of it. (And the most tedious code to write by hand - a complete waste of time)
In the main method, then you can pass the configured values wherever you need to when new-ing classes.
> At some point you want a user-facing UI where the available features (which are generally classes) are listed and the user can choose the feature, say which log backend is enabled, without having to change code - that's the whole point of it. (And the most tedious code to write by hand - a complete waste of time)
I consider DI a valuable pattern, but I've never experienced anything close to this need.
What happens with proxied classes? My ClassWithTransactions is actually a subclass of the written one auto-generated by Spring. I can’t inject a new instance of that manually.
And you may say that you don’t need Aspect Oriented programming, but the usual handling of transactions in many other languages without some meta-programming is.. to not handle transactions. Putting a single annotation over a method is imo a very elegant way to handle this needed functionality.
This is all considerably more abstraction than I have wanted or needed when writing Java. When handling transactions, I’ve passed around the same connection before committing.
But, I would still think that those are not big deals (what do you have, 40 parameters or something?) and that the explicitness can be helpful. Isn't it good to know that the top level service depends on your email-sender dependency from just looking at its code instead of needing to analyze its code and every single object under it?
Which is the standard way to do DI in Spring as well? It will be just called by reflection instead.
But frankly, how will you call that new if it depends on a class which is a singleton, another which has some more complicated scope so it may or may not have to be reused? DI is not only about calling new..
Is what you're thinking of equivalent to deep argument passing? I've seen it done where you pass around a global Factory object that can provide dependencies. It's basically rudimentary DIY DI.
It's really very simple, no you don't need to pass around a factory object.
You just have a class/classes that construct/wire all of your singleton objects and passes the required dependencies into their respective constructors as necessary.
Here is a contrived example of what the wiring code might look like for a web app that uses a database.
public static void main(String[] args) {
MyConfig config = readConfigFile();
DatabaseConnection dbConn = new DatabaseConnection(config.dbHost(), config.dbPort());
UserDao userDao = new UserDao(dbConn);
UserController userController = new UserController(userDao);
List<Controller> controllers = List.of(userController);
WebServer webServer = new WebServer(config.listenPort(), controllers);
webServer.runAndBlock();
}
So if you are making any kind of reusable design, you cannot annotate your classes with @Bean anymore. Instead you will make an @Configuration (like spring boot auto configuration) that by discretion may pull in some more general (not @Configuration annotated) reusable configuration. Since some classes will be considered implementation details, you won't want to expose those into the dependency injection container of spring (since that is equivalent to making them public, people will inject them and depend on them!). So instead you will only create them inside your own @Configuration and pass them directly when generating an @Bean from a method.
Congratulations, your @Configuration is manual dependency injection. That is easy enough. Why did we need inversion of control over the dependency injection in the first place? It isn't immediately obvious to new engineers what aspect of the @Autowired is dependency injection and which aspect is inversion of control. Many of us don't see much of a benefit to the inversion of control if you are taking care of your application's hygiene in the first place.
Unless WebServer is the only class that needs dependencies you're either going to have to pass those dependecies repeatedly from class to class or you're going to have a global factory that provides the dependencies to everybody.
Yep. Once you get too many arguments what you do is usually to create some kind of Context class that bundles all of them and just pass that on everywhere.
I'd say that the only one of your listed solutions-in-other-languages that is actually a valid solution is deep argument passing.
And I fail to see why it's a problem. If your FooService depends on a BarService, which depends on a BazService, and BazService needs a database connection, then that means your FooService really does also depend on a database connection. Hiding that information, to me, seems like a mistake. Can you articulate why one would prefer not to have FooService explicitly require that database connection, or am I inadvertently arguing against a straw man? If so, please correct me, because I'm asking sincerely.
Of all the time I spend thinking about my code and writing code, I truly can't say that adding a dependency and having the compiler complain until I fix a bunch of constructors has really caused me that much grief. And I'm not going to pretend that it has never been the case that I've had to fix 20 constructors.
Ultimately, I think thisnis going to come down to preference.
I would prefer not to have to fix 20 constructors.
It's tedious and time consuming. The intermediate classes that _do technically depend on FooService because BarService does_ - the intermediate classes don't care! It clutters the code everywhere else for minimal benefit.
Manually, you see all your dependencies just shy of main where the binary initializes them all and starts passing things down. In DI, you have a module file somewhere with them all.
(As a clarification, in case it's needed: I obviously didn't LOVE it when I had to update 20 ctors after changing a somewhat fundamental "service" to need a new dep. My point was that, even as painful as that was, it wasn't that bad and it's usually much less bad than that.)
I guess the (philosophical) difference comes to this statement:
> The intermediate classes that _do technically depend on FooService because BarService does_ - the intermediate classes don't care!
I can definitely understand what you're saying there, but it's interesting to me that I don't see it that way. I think I'm just less pragmatic and more... "academic" (?) about how I read and understand my own code. If X depends on Y and Y depends on Z, I'm comfortable with X explicitly depending on Z because I imagine "inlining" Y's functionality in X. Either that or you turn Y into an interface and then X only depends on IY. But, my brain just likes the explicit continuity I guess.
The solution in other languages is to use a DI framework written for them. Which one doesn't have any? In .NET, the basic DI interface (imports/exports etc) is even part the standard library as System.Composition.
You say that, and I usually agree, I mean, constructor args are the simplest form of DI.
But then, working in a complex codebase, I introduce a new dependency that is instantiated early in the tree, used two disparate classes rather deep in the tree, suddenly I'm changing 10 different constructors just to get the new dependency where it needs to be.
The tree of constructors is where DI shines as an alternative.
That REALLY depends on the size of your codebase. When it’s small, no need for a DI framework. But when it grows large, it becomes quite a pain, and a DI framework is nice, eliminates a bunch of boilerplate with every code change.
I only miss DI. I miss being able to say "this system depends on these external things" and having a consistent, convenient way of sharing/swapping/testing those components and dependencies.
The solution in other languages? Unstructured globals, deep argument passing, or monkey patching with mocks?!
Yea, I can write simpler code without DI... By ignoring a bunch of stuff.