The problem
While projects evolve, the lines of code increase and the dependencies between the classes are becoming more and more complicated. With the absence of some Dependency Injection (DI) framework/library, this ends up quickly in having to pass instances here and there through constructors of classes. The constructors may end up having something like 10+ arguments. To make things worse, for some cases, these arguments are not used by the logic of the classes themselves, but instead, they are just used to create subclasses!
This situation is especially common for projects that are built using a service-oriented architecture internally and of course, makes things tight and difficult during Unit Testing.
Dependency Injection may help keeping the code (mostly) clean and can facilitate the implementation of Unit Tests. However, it is not the choice for everyone and some teams may have decided not to use any DI framework.
The thought for a way out
Scala is a very expressive language and it is pretty amazing how beautiful things someone can achieve when using it. We can see this everywhere: in blogs, SO and gists to name a few. So, maybe we could find some delicate and easy way to keep the code cleaner, even without the use of any specific DI libraries.
A kind of DI
As Jonas Bonér (founder and CTO of Typesafe) says in his blog: “Scala is a very rich and deep language that gives you several ways of doing DI solely based on language constructs”.
In the same blog, he describes a way to perform DI using the Cake Pattern. Searching around the blogs and answers/proposals, one may find out that indeed, DI in Scala can be achieved with several ways.
What we will try to do here, is a kind of DI using Scala-only constructs, in order to be able to create some class instance once and “inject” it wherever is needed.
Use case to drive the solution
Let’s say that we have a class that represents a Configuration Service. This service provides a file path to its users. The service should be used by any class in our application that needs it and it may look like this:
class ConfigurationService(filePath: String) {
def getFilePath(): String = filePath
}
Who will instantiate the service?
In a real DI framework, the container/framework itself should instantiate the ConfigurationService, as being dictated by some XML configuration or annotations, like in Spring.
In this case however, the service will be created by our code, during the startup of the application and before any class asks for it.
A package object in the solution
A package object is really helpful because any definition that is done inside the package object is actually member of the package itself. A package object (and a Scala object in general) can provide access to its fields and methods from anywhere in our code.
So, someone could think to create an object like following:
package object di {
var configurationService: ConfigurationService = _
}
However, there are some things that need our attention. Firstly, we need to obey to the functional principle that dictates the use of vals instead of vars.
So, the service should be available to the users immutably, via a val. But how this can be done, since the object will be already created by the time that our application performs the initialization of the service? Well, a small hack including a lazy val and a var could do the trick:
package object di {
private var configurationServiceVar: ConfigurationService = _
lazy val configurationService: ConfigurationService = configurationServiceVar
}
Now the di object offers the ConfigurationService publicly via a val. All we need now is a def that allows the mutation of the var. This mutation should be done early in out Application initialization, before someone asks for the val. If not, the configurationService val will be null during the lifetime of our application.
Let’s try to avoid that by using Options and Exceptions:
package object di {
private var configurationServiceVar: Option[ConfigurationService] = None
lazy val configurationService: ConfigurationService = {
configurationServiceVar.getOrElse(throw new RuntimeException("Configuration is not yet initialized"))
}
def initService(cs: ConfigurationService) = configurationServiceVar = Some(cs)
}
With the above code, we are making sure that if someone asks for the ConfigurationService before it gets initialized, he/she will get a nice RuntimeException. Moreover, the RuntimeException will prevent setting the val with something wrong like None.
Our lazy val will stay uninitialized until someone actually initializes it.
Making it generic
Until now, the solution handles only the ConfigurationService class. Let’s try to make it more useful, by handling more services generically:
package object di {
private var readyToInject: Map[Class[_], Any] = TrieMap.empty
private def key[T](implicit classTag: ClassTag[T]): Class[_] = classTag.runtimeClass
def initService[T: ClassTag](c: T) {
val k = key[T]
readyToInject.get(k) match {
case Some(initialized) => throw new RuntimeException("The service already initialized")
case None => readyToInject = readyToInject += (k -> c)
}
}
def inject[T: ClassTag]: T = {
readyToInject.get(key[T]).getOrElse(throw new RuntimeException("The service is not yet initialized")).asInstanceOf[T]
}
}
We created a concurrent Map that has a Class as key and Any as value. The potential is that the Map will hold the instances that will be later injected into fields of classes that need them.
We also changed the name of the initService method to inject. This is just to add a bit more “beauty”, as it will make the code easier to understand and to look familiar to us (similarly to @inject annotation).
How this kind of DI looks like to its users?
Let’s create a user of the ConfigurationService, a HelpfulClass:
class HelpfulClass {
private val config = inject[ConfigurationService]
def doSomethingUsingTheConfiguration = {
println(config.getFilePath())
}
}
This seems simple and elegant. We expect our config val to have “injected” an instance of the ConfigurationService class, only by issuing:
private val config = inject[ConfigurationService]
Note: For this example and for the sake of simplicity, the ConfigurationService was a concrete class. However, it could also be a trait.
Let’s see what is the result We may test the result by creating a simple Main object and try to see the following:
- We will try to use the HelpfulClass before the ConfigurationService is created in order to get the resulted exception.
- We will initialize and use the ConfigurationService.
- We will try to use the HelpfulClass again in order to get the resulted exception.
object Main extends App {
// Try to use the service before it gets initialized (1)
try {
val helpfulClassShouldFail = new HelpfulClass
} catch {
case error: Exception => println(error.getMessage)
}
// Initialize services (like the DI framework would do)
initService(new ConfigurationService("/my/path"))
// Run the application (2)
val helpfulClass = new HelpfulClass
helpfulClass.doSomethingUsingTheConfiguration
// Try to re-initialize the service (3)
try {
initService(new ConfigurationService("/my/other/path"))
} catch {
case error: Exception => println(error.getMessage)
}
}
Once this runs, we should see the output:
Configuration is not yet initialized /my/path Configuration is already initialized
Drawbacks and possible extensions
The main drawback is that it is dangerous to use this kind of DI to inject values into Scala objects. In the case that someone tries to use the object before the ConfiguratioService gets initialized, the application will crash with an ExceptionInInitializerError.
The other thing that doesn’t allow me to call this proper DI, is that the inversion of control is not applied in a proper way. The control of the services creation is still in our own code.
One way to try to abstract away of explicitly creating the instances of services in the code, could be to create logic inside the di package object that would read some XML file and do the instantiation of services using reflection, during the startup of the Application.
Moreover, we could even try to create scopes and, like in Spring for example, support singleton, prototype etc!
What to takeaway?
The idea, if you like it and the code, of course. If you are interested, I have created a library that builds on the ideas described here and you can find it on GitHub
Thanks for reading!