I use dependency injection with Guice and most of the time, I'm rather happy with it. But sometimes, it's difficult to get the dependencies to where they're needed and sometimes, it's plain impossible.
An extreme example is making
MyResponse.toString() return some JSON, where
MyResponse contains either a list of DTOs or an Exception. There are requirements, that the stacktrace should only be shown to testers, which means that some information about the logged-in user needs to be provided somehow. Additionally, when testing locally, I obviously always want to see the stacktrace, even when logged-out. And there are more such rules.
Obviously, this dependency can be included in
MyResponse or provided as arguments (though not to the very
toString() method, but I agree that
toString() should usually be used for debugging only). This gets pretty ugly when a new such dependency gets added. For example, I just had to add a localized end-user message to the JSON representation of the exception. The number of such second-class dependencies grew slowly over the number of dependencies relevant for the core functionality.
Sometimes, they can be aggregated nicely, e.g., I can provide a properly configured
Gson, which takes care about the conditional stacktrace hiding and the localized end-user message generation.(*) But oftentimes, it doesn't work well and I end up passing lots of arguments, or passing needlessly big dependency aggregates, or creating single-use aggregates. In the end, it feels like I'm overusing DI as it starts to make the code more ugly rather then nicer.
I wonder, if there's a point where I should simply give up and say "these boring details will be available from a thread-local (or even a singleton)"? I don't think it'd hamper testing as these dependencies hardly ever matter and when they do, they can be set as needed.
(*) This may sound like putting too many responsibilities to the poor
Gson instance, however, it's the thing responsible for converting objects to JSON, so it should do it exactly like needed. The
Gson is composed from a bunch of adapters and making the
ThrowableAdapter be aware of the stacktrace hiding and localized user-message generation sounded like a good idea. The only problem is that the needed information (1. Should the stacktrace be shown? 2. What's the language for the message?) has to be passed somehow to it.
Thinking about it more, I could wrap the
MyThrowableWithUserInfo and write an adapter for it (I can always do this wrapping before needing Gson). In this particular case it seems to be the perfect DI solution.
In general, it's not that good as there could be throwables somewhere deep in the to be serialized object and adding this information to them may be impossible. I'm afraid, it'd need a thread-local hack, but this hack can be constrained to the serialization process only, which makes it much less ugly.