This article tries to define the best practice for DI in Scala.
It is based on several sources:
- From Dependency Injection to Dependency Rejection: Mark Seemann, the author of the most popular DI book, has something new to show us.
- DI in Scala using MacWire: The autor of MacWire describes how to do Dependency Injection using the Scala language constructs as much as possible, while remaining practical, with the help of the MacWire library where necessary.
- Using Scala Traits as Modules, or the “Thin Cake” Pattern: it shows how to use trait to create dependencies that are mixed into a function.
- Cake Antipattern: avoid the “fat” cake pattern to builder layers of layers of dependencies.
The video of From Dependency Injection to Dependency Rejection provides a new view of DI: DI is a neccessary feature for OO programming while it shouldn’t be exist in FP. In FP, the parameters of a pure function can only be immutable data or pure functions. The functional way is making all parameters explicit, what matters most is to separate the pure and impure functions. The pattern of read data (impure), processing data (pure), and writing data (impure),is called a sandwich pattern. In a pure function, only use values produced by impure functions and return values to be used by impupre functions.
All functions form a dependent tree. Eventually one needs to provides the arguments, implicitly or explicitly, to use a function. At the leaf level, a function usually has a small number of parameters and it is not an issue to create and pass arguments. At leaf level, one should clearly isolate pure functions and impure ones and make as many pure functions as possible. As we move up the tree hierachy, we have more and more impure functions because impure functions are infectious. The middle nodes creates arguments and asseblies lower level functions. For any useful application, the root function must be an impure one. In FP, there is no need to use DI because arguemnts are created explicitly and passed explicitly or implictly.
OO needs DI to manage the dependency for abstraction (hiding the implementation details), simpler code (no wiring code) and testing (mocking dependencies). Because Scala is a multi-paradigm programming language, it still uses OO in many places and DI is still used. With the new FP style, DI can be expressed as curry functions where a dependency can be solved with partially-applied function.
Therefore a good strategy in Scala application is to isolate pure and impure functions to make it a better functional application. The partially-applied function is called thin-cake pattern in Scala. Dependencies are created and bind at compile time for type-safety. In places OO is used, use partially-applied functions. No runtime DI framework should be used to maitain type-safety.
Thin Cake Pattern
In the thin cake pattern, self types/abstract members are used to compose modules, that is traits which define parts of the wired object graph. It is useful for partitioning the object graph creation into multiple small modules, without creating a huge monolithic class/method at “the end of the world”.