Why do "static references become a tangled mess"? In my (limited) experience with runtime DI libraries (albeit in Go) they turn clear, IDE- and debugging-friendly code where the compiler tells you at compile time if you got it wrong ... into a hard-to-debug magical soup.
With static, using-the-language dependency injection, isn't the question of "how does ABC component get access to DEF?" answerable with the normal IDE/language tooling, rather than some magical library's way of doing it? You can just find the calls to a constructor and look at the arguments.
My experience is based on my bad experience with runtime DI libraries, and is definitely biased against them, but I must be missing something here.
There are lots of reasons why static references are undesirable, but some of the more serious are:
* Static dependencies make testing harder, no question about it. This is mediated in dynamic languages like Ruby by mocking statics. While you can actually do this in Java with Powermock, avoiding mocks entirely is even better. If you can't use a real object, use a fake that implements the relevant interface.
* Statics mean singleton, and that invariant often changes as a product matures. It's very easy to go from "the database" to "a database", and when you have 500 places getting "the database" it's very hard to make that evolution.
* Statics make it very hard to maintain module boundaries, because every static is its own exported interface. In a long-running project, binding tends to get tighter and tighter as every module reaches out for the statics of other modules.
Sure, folks can write bad code with DI systems too. And I'm no fan of Spring - not because of the DI, which is fine, but because of the need to wrap everything else in the universe and now you have to understand both how the underlying thing works and the way Spring changes it. But something like Guice or Dagger is just the right amount of glue to hold a system together, without getting in your way.
Just a note: I overloaded (excuse the pun) the word "static": I didn't mean the "static" keyword, but "statically compiled/typed". So it doesn't mean singletons, just that you pass dependencies as explicit arguments to constructors and functions.
I do not understand what distinction you're making. In the world of DI, you still have typed constructors and factory methods. It's not like Guice turns Java into Ruby. The only difference is that you don't have to chain together constructor boilerplate - in fact, the static types determine the injections.
Do you object to passing interfaces vs concrete types? That is a wholly orthogonal concern; you make the choice to extract interfaces with or without DI.
As a Java developer for all my career here is my take. In Java world there is this cultish cottage industry of "frameworks" for all sorts of work. Most Java developers are not expected to write plain code with JDK standard + some external libraries. Creating an object via "new Obj()" might cause programing universe to collapse so DI framework is must for enterprise Java developers.
If I were to tell at work that Go has inbuilt http server where we could implement a few handlers and have a basic service running. They would not be shocked that a http listener could be this simple but rather ask "Does Go bundles "weblogic/websphere/tomcat/netty" server with it or else how can it work? Same with testing, no understanding on how, what is to be tested but everything about "JUNIT/Mockito/Mockster/SpringUnit" or whatever.
There is no requirement for understanding basic concepts for testing, client/server, dependency, error management etc. So even basic functionalities are understood in terms of a branded framework. This is their main frame of reference.
Just to echo this is similar to my experience. Java development culture that I have known in the work place is extremely coddled by their frameworks.
It is hard for them to open the terminal and execute their application JAR from the command line--only ever from the IDE. Oh wait--they need Tomcat/Apache & a few hours of dealing with classpath issues.
That's just a problem with developers who do Java as their 'nine to five' job and don't have any interest or passion to really find out how stuff works. I've met a lot of those people and there's no reason they can't contribute if the project is set up to accommodate it.
On the other hand there are the enthousiasts (like you I presume) who like tinkering and using the language to the fullest. Any successful project needs at least a few of these people, but they can also go overboard by building a lot of custom functionality where any standard library could have been used.
While I'm also an advocate for increasing knowledge for the systems you're working with it's no 'sin' to use some libraries. For instance: for your HTTP server example, it's quite easy to just listen on a socket and respond to a request. But you want parallelism, so you need a thread pool. And queues, configuration and error handling. That will escalate quickly so why not pick whatever Java servlet implementation which has most of the complexity and - more important - is already production tested so it won't fall over when you deploy it live. And then there's stuff like OpenID connect or SOAP (yes, still exists) where you can 'plug in' an implementation on some servers so you can get work done instead of worrying about getting all the implementation details right for some complex protocol.
> but they can also go overboard by building a lot of custom functionality where any standard library could have been used.
Well I am kind of recommending using standard libraries. And servlet implementation I am using embedded tomcat as I mentioned in other comment. What I am not doing is generating gratuitous scaffolding of dozen packages and innumerable classes because that is the "best practice".
Just to clarify it is not my opinion. It is the groupthink of enterprise Java programing where reading Java tutorial itself would be obscure thing. Everything has to be looked from "framework" perspective. Framework says 'new' is bad so it is bad, dependency has to be constructor/setter injected by DI container so that's how it has to be.
No framework says `new` is bad. Feel free to grep for new in any framework application. But if one uses DI, than do use it for classes that ought to be injected. But inside methods of course one can and do use `new` many times over.
In my experience most DI just interferes with being able to use the IDE to track down instantiations. Ive worked on these projects that basically have these fancy runtime things to answer questions that could be answered by the IDE if it werent so obscured. I remember one project we had a fancy thing to generate a graphviz graph, and it was like neat, but we could just use find all references if we just called new.
The dumb thing is most of the time only one type is ever injected. Its all hypothetical flexibility which has a cost but no benefit
> I remember one project we had a fancy thing to generate a graphviz graph, and it was like neat, but we could just use find all references if we just called new.
Ha. Calling `new` would either be an absolute enterprise Java sin or an obscure aracana. Some people are quite proud of the fact of converting compile time errors to runtime exceptions. Because you know "best practices" and all.
It definitely has a benefit at scale. I’m not sure what application were you developing, but the amount of time a single instance was changed into an interface because the client wanted the n+234th little change in this special star constellation.. with DI you don’t have to write any more code, you can even use different implementations per environment (@Profile), so this is not accidental complexity in most cases. Sure, if you need a 100 lines web server that prints hello world it is an overkill, but the correct tool for the job..
"static" in java DI can refer to setting global variables with singleton instances. E.g. java logging libraries usually do this so that you don't have to DI everywhere you want to log. In Go, some packages do this like flag and log. In Rails, this is so common that it replaces DI entirely, but I usually didn't feel like rails suffered from it.
I think what you are referring to is just manually doing DI. I.e. you defined constructors that accept dependencies and then call them all in a main function. I think this is tolerable if your codebase is structured for it. In typical java codebases, it gets ugly really fast. IMO this is caused by a general proliferation in the number of classes (due to class-per-file among other reasons), as well as a tendency to never use "static" DI. As an extreme example, if you needed to inject a logging dependency, then almost all code would need to be part of the DI graph. In a typical web backend, you might DI the sql connection pool. This causes basically all code to need DI since it either uses sql or has a transitive dependency that uses sql. IMO injecting the connection pool is not useful since it's not useful to write tests where you inject anything other than a real sql connection.
Ah, that explains the confusion. Yeah, I didn't mean static as in the "static" keyword in Java / C++, but as in specified in the statically-typed code. Defining constructors that accept dependencies -- exactly. Ah yeah, I can see how Java exacerbates things here with the one-class-per-file rule -- ugh.
Go doesn't require one class (well, type or struct in Go) per file, and has much more flexibility in how you build packages as a result. I think it's a good thing that dependencies like the logger and the database are passed around explicitly: I've learned the hard way that "explicit is better than implicit" even when it means a bit more boilerplate.
This, it’s really exhausting to read this never ending wheel reinvention. Sure any one can use simpler non-spring frameworks, and other “non standard” frameworks and libraries for 1/10 or 1/100 of the functionality, and get 10-100x the bugs and much less or zero support. But we need netty! And then when you add thread pools, jdbc, logging, etc? Yep you’ve reimplemented spring. Just use spring, spend the time to learn it and reap the rewards.
As someone who dealt with a ton of Spring in the recent past, I completely disagree.
First of all, thread pools are part of the standard library. Spring adds little to no value on top of it.
Second, reinventing some of that stuff is absolutely worthwhile, because Spring's library design/implementation is not very good.
Finally, when I had the opportunity to start a new Java project, I opted to not use Spring. I finally had a server that started up fast, took less code than a Spring project, was easily navigable in an IDE, and whose code was generally easier to follow. It was also easier to write tests for.
One thing I learned is that people seem to underestimate just how thin Spring's abstractions are over stuff in the library, servlets, etc. Most of what Spring does is wrap things in a bean interface so they can be used with DI (which is something I’ve never found any value in).
Well you are missing one of the great feature of Spring framework: Converting compile time errors in to runtime exceptions.
Jokes apart you are absolutely right about non-spring based services. I did same using plain Java + embedded tomcat for some services. No cargo-cult like endless decorative packages and classes. Exactly same result as you observed. Less code, fast to start and vastly improved error management.
Yeah, please popularize it. In my case I am unable to make management see reason. If more devs become vocal about it make it a trend, it will be a good thing to happen.
Same here. I've gotten rid of all that stuff, it's just layers and layers of indirection that contribute nothing.
One rule that has also helped me a lot to keep my code clean and make it easier to debug is to fail as much as possible in the constructor. So when you call new MyThing(), you will either get a usable object or it'll throw an exception. Further method calls are expected to work. Of course this is not doable for everything, but it sure helps keeping the methods clean and not have them throw various exceptions.
Could you share your Spring/Spring Boot alternatives? Are they Java based? I'm doing backend stuff with Spring Boot and I would like to test alternatives. Spring boot is not that difficult to work with, but I would like to test a "simpler" solution.
I've had similar experiences. A few years ago, I wrote a small service in plain Java, no frameworks as part of a quick change to improve performance. It worked and we all moved on. Later it was converted to Spring Boot and it slowed way down.
> But we need netty! And then when you add thread pools, jdbc, logging, et
java.util.concurrent has threadpools and pretty damn decent at that; jdbc is a part of very standard jdk, logging is part of java.util.logging. Why do you need netty (which is not a part of spring either way)?
In over 23y of working with Java, I have never needed spring.
* Static references become a tangled mess, and you start wanting some structure around that.
* You have to answer "how does ABC component get access to DEF?" for increasingly difficult combinations of ABC and DEF.
Excepting Spring, pretty much all Java DI containers are lightweight.