We are currently in the process of removing Scaldi from our application and replaced it with dependency injection based on the Cake Pattern. We have a significant amount of code based on Scaldi, so we didn't make the decision lightly, but ultimately it was worth it. Here is a list of the problems we encountered:
- The wiring isn't verified at compile time. Not only does this carry the risk of introducing bugs that can't easily be found (since the wiring is done lazily by default, so you only notice a bug once you try to use an affected component), but also they're difficult to fix because Scaldi's error messages are useless: you get to read endless stack traces that make it quite hard to figure out what the problem is. Compiler errors on the other hand are usually trivial to fix
- Inter-Module dependencies are implicit. There is no way to know which modules depend on which other modules because these dependencies are resolved at run-time. So it's impossible to remove a Module from an application without risking breakage.
- There is no encapsulation, i. e. it's impossible to define a binding that is valid only within a module. So whenever you want to remove a binding, there's no way to tell where it might still be used. With the cake pattern, this is trivial as there are tools to tell you where a variable is used, and you can simply make your variables private.
- The Binding DSL is so verbose as to make the whole thing pointless. Why write
binding identifiedBy 'foobar to injected[Foobar]
when you can just as well write
lazy val foobar = new Foobar(baz, qux)
As you can see, the DSL is so verbose that normal Scala syntax is terser even when you specify the arguments explicitly! And of course the latter is much easier to understand and maintain: I can see and read the arguments, and if I want to know how they are defined, all I have to do is Ctrl-Click them in my IDE. Ultimately Scaldi fails to achieve its only purpose, the reduction of boilerplate code.
- We hit a bug that caused a deadlock in scaldi-jsr330. We reported it, but the author seems unable to fix it. We had to work around it by making one of our bindings non-lazy (though this is a good idea anyway to detect wiring errors early)
- lifecycle management with destroyWith doesn't work properly in Play-based applications. The callbacks are executed when the JVM shuts down rather than following Play's ApplicationLifecycle, which breaks Play's dev mode. We hacked around this, but that shouldn't be necessary in the first place.
- TypesafeConfigInjector. This is the part of scaldi-play that makes the configuration available via Scaldi, so you can do things like inject[Int]("foo.bar.baz") and it will take the value from the Play configuration. Why is this bad? Because if you bind a class using the injected macro and one day you decide to add an Int parameter, you will get some random value from your configuration rather than an error message.
- broken generics support. Suppose you have class Foobar[A](a: A), and you want to bind a Foobar[Int]. binding to injected[Foobar[Int]] won't work because the injected macro isn't clever enough to substitute A with Int and hence doesn't know what constructor parameters to inject
Summing it up, Scaldi doesn' t solve any problem that I can see (the code isn't even shorter than the equivalent cake pattern based code!), and it will make your codebase much harder to read and maintain. I can thus only recommend everybody to stay away from it.