Hacker News new | ask | show | jobs
by nme01 1111 days ago
Possible that I was using Spring in a wrong fashion but for me (project size around 300 KLOC) Spring dependency injection model with so much magic happening (all the auto configurations, guessing which classes to load etc.) was extremely hard to maintain and understand. I think I prefer Guice/Dagger style of defining dependencies which is much more explicit.
4 comments

> Possible that I was using Spring in a wrong fashion but for me (project size around 300 KLOC) Spring dependency injection model with so much magic happening (all the auto configurations, guessing which classes to load etc.) was extremely hard to maintain and understand. I think I prefer Guice/Dagger style of defining dependencies which is much more explicit.

Spring used to be (and still can be) very explicit about defining dependencies, but they've been pushing more towards encouraging the use of "magic" over the last many years. I prefer to be as explicit as possible about everything, unless I have a very good reason not to be, and tend to either specify the literal bean name in the annotations or use the older XML-style configuration (and do the same).

It's a fair point, although you can set it up to only do explicit defined dependencies which I definitely recommend.
We had a similar sized project grow out of a 2-pizza-team and had the same problems. Kinda like how Java devs come to JavaScript and are shocked that this language doesn't have proper numerical types, I came to the Java ecosystem and was shocked that everything pivotally hinged on whether you had the right magic beans and especially if you had different beans vying to be the same sort of magic it was awful.

My favorite bug we ran into was a short little RedisConfig.java file that got copy-pasted into a bunch of our microservices in the test environment. It appeared on a very casual glance to connect to Redis. But our tests did not run a Redis instance, so the configuration should have failed. Instead if you looked carefully at this file you could determine that the connection string was all broken but that didn't matter because it never implemented the Redis Connection Bean logic anyway and in fact you could replace it with a purely empty class and tests still pass.

But, tests break when you delete this unused file, the file needs to exist even if it is the empty `class RedisConfig {}`, because now tests are trying to connect to a nonexistent Redis and not succeeding. Same if you try to rename it to like `class RedisTestConfig {}`, suddenly tests want to reconnect to Redis and they all break!! So, everyone has more important things to do, it gets copied to everyone, nobody understands what on earth it is doing since the code inside the file is clearly broken.

Eventually, we start experimenting with Kotlin. When you copy this pattern over to the Kotlin repo, it starts to complain VERY loudly that you have two objects with the same name in the same namespace and it won't start because it is too confused. And that was the key hint. As far as Spring was concerned, this file, because it has the same class name in the same namespace, basically string-replaces that file. (Nondeterministically? Or maybe it was deterministic that "test" won over "main" because it was lexicographically larger?) So we were string-replacing to a RedisConfig that could never be used as a Redis configuration bean, which causes Spring's Redis Cache logic to say "oh I have no configuration, I should assume that I am unneeded", which disables Redis.

But if you rename it, then Spring Redis says "oh I see two things, I can't use that one but I can use this one, let's connect to Redis with that. oh no!! Redis is DOWN!~ crash all the tests!!"

> Nondeterministically? Or maybe it was deterministic that "test" won over "main" because it was lexicographically larger?)

Java uses classpath ordering for resolution, so a duplicated fully-qualified class name will take the first on the path. This was useful when patching or pre-dependency management, when some library authors would bundle into a fat jar with incompatible versions. It became disallowed by Java 9's modules without command-line flags. It was always considered a bad practices and a natural fallout of the dynamic nature of the JVM. In your case test classes were resolved before main sources and libraries.

Unfortunately there is a broad view that Java == Spring. In over 15 years of full time Java development, I only used Spring Framework 1.x and 2.0 (migrating from pre-DI). Since then my employers and network all used Guice, though I never selected a role where I realized that choice upfront. That framework is far less magical and explicit, making it very easy to debug. I suppose tool popularity is regional, it's just that this type of Spring magic is abnormal and disliked throughout my work experience.

You don't need to use Spring Boot (which does all of the opinionated autoconfig). Just import the Spring components that you need.