Nearly all applications need some sort of configuration injected which is stored in configuration files. The configuration files are usually environment specific and they are packaged by the build along side the application code. This means that applications must parse out these files and initialize components based on the  settings contained in them. Given that this is a recurring task for most applications, we would like to use some standard mechanism for this process so that we wouldn’t have to code this configuration loading and injection code from scratch each time we create an application.

There are many options out there to accomplish this task. The big dependency injection engines provide some viable alternatives. Class constructors, such as the constructors of long lived components like services, can have their constructor parameters decorated with the @Named annotation. Third party configuration injection tools such as Spring or Guice then can inject the appropriate setting from the correct configuration file. This means that the DI framework will do most of the work and the components will get automatically initialized by the injection framework.

For the following example let’s assume we are trying to initialize two components from a configuration file, a logging component and a user repository component. Each has a configuration setting as follows in ConfigFactory’s light weight JSON-like file format. Here’s how the components and their corresponding configuration file would look:

class LDAPUserRepository(
   @Named("userRepository.encryptionKey") encryptionKey: String)
  (implicit audit: AuditLog) extends UserRepository
class OffSiteAuditLog(
  @Named("auditLog.locationDir")locationDir: String)
  extends AuditLog

class App{
  implicit val audit = new OffSiteAuditLog
  implicit val users = new UserRepository
  [..other stuff..]
}

myApp {
  auditLog {
    locationDir: "someDir"
  }
  userRepository {
    encryptionKey: "xyz123!"
  }
}

When using Spring, the classes will get initialized from the corresponding setting in the configuration file by name at the time of class construction. This offers a simple mechanism to load configuration files but it has its drawbacks. One of the problems is that the initialization is lazy and that the main runtime code (as opposed to some initialization code) in each component validates the configuration setting. If the component does not get created until a later point in the application, then any errors in the configuration will not get discovered until that later time. So if the encryptionKey is missing from the configuration file, that won’t be discovered until the application runs and encounters the logic in user repository which needs that setting. This is what leads to emergency support calls at 2am! It would be better to fast fail in these situations, find out if there are any problems at deployments time, and sleep well knowing that configuration related surprises are unlikely once the app is up and running.

But there are other problems as well. We shouldn’t have to drag a heavy injection framework into our app just to load simple configuration. These frameworks usually take over and the entire app has to be organized around them. They usually have their preferred way to do every single thing leaving little room to customize our application structure to suit the task at hand well.

ConfigFactory with configuration case classes

ConfigFactory comes out of the box with Scala. It is one of Lightbend’s core components so it is well tested and is used by many developers. In combination with case classes it offers a light weight Scala friendly alternative to the Spring and Juice frameworks. So how does this pattern work?

We can separate the configuration code from the component main code by creating case classes for each component that encapsulate the settings for that component. The configuration case classes would look something like this:

import com.typesafe.config.ConfigFactory

trait AuditLog
trait UserRepository

class LDAPUserRepository(implicit userRepositoryConf: UserRepositoryConf, audit: AuditLog) extends UserRepository{
  println(s"userRepositoryConf=$userRepositoryConf")
}
class OffSiteAuditLog(implicit auditLogConf: AuditLogConf) extends AuditLog{
  println(s"auditLogConf=$auditLogConf")
}

case class UserRepositoryConf(encryptionKey: String)
case class AuditLogConf(locationDir: String)

class AppConf{
  private val appConf = ConfigFactory.load().getConfig("myApp")

  implicit val auditLogConf = AuditLogConf(appConf.getString("auditLog.locationDir"))
  implicit val userRepositoryConf = UserRepositoryConf(appConf.getString("userRepository.encryptionKey"))
}

object MyApp extends App{

  val conf = new AppConf
  import conf._

  implicit val audit = new OffSiteAuditLog
  implicit val users = new LDAPUserRepository
}

When using this method for initializing from configuration files, we can fast fail configuration errors. When the application starts up it can immediately run validation code on the configuration and crash with a fatal error if certain settings are missing. The above code uses Scala’s type based injection mechanism to initialize every component based on their type. Since each configuration case class is just another type for Scala, the implicits based injection will inject them into their appropriate components as well, by type. This code produces the following output:

auditLogConf=AuditLogConf(someDir)
userRepositoryConf=UserRepositoryConf(xyz123!)

This is just one of the many variants of this technique. The technique would work well without implicits as well. Then we would have to pass in the the correct config object to each class by hand. The point is to fast fail and not to pull in a giant framework to do something trivially simple, that pretty much come out of the box with Scala.

This works fine as long as there are not many settings. But as soon as we start adding numerous setting keys for each component, injecting them all one by one in AppConf will become hard to manage. In addition to that, we usually want to add code to validate the configuration. For instance we might want to validate that the configuration setting for the locationDir of AuditLog is in fact a valid existing directory. This will make the code even harder to read unless we start breaking it up. It is also a good idea to keep the configuration extraction code close to the code of the rest of the component, although still separate. Keeping all these objectives in mind, we might do the following: Put the configuration loading and validation code into the companion object of each component. So the relevant code changes now would look like this:

object LDAPUserRepository{
  def loadConfig(config: Config): UserRepositoryConf ={
    println("LDAPUserRepository.loadConfig is building config")
    UserRepositoryConf(config.getString("encryptionKey"))
  }
}

object OffSiteAuditLog{
  def loadConfig(config: Config): AuditLogConf ={
    val dir = config.getString("locationDir")
    if(!new File(dir).exists()){
      throw new RuntimeException(s"""locationDir $dir doesn't exist""")
    }
    println("OffSiteAuditLog.loadConfig is building config")
    AuditLogConf(dir)
  }
}

class AppConf2{
  private val appConf = ConfigFactory.load().getConfig("myApp")

  implicit val auditLogConf = OffSiteAuditLog.loadConfig(appConf.getConfig("auditLog"))
  implicit val userRepositoryConf = LDAPUserRepository.loadConfig(appConf.getConfig("userRepository"))
}

object MyApp2 extends App{

  val conf = new AppConf2
  import conf._

  implicit val audit = new OffSiteAuditLog
  implicit val users = new LDAPUserRepository
}

This leaves AppConf code nice and clean. It also keeps the configuration loading code close to the component code, which helps when dealing with a lot of components. But it is still nicely separated out into the loadConfig() method, and most importantly it still runs separately at startup to assure fast failure! This allows us to add sophisticated configuration validation to loadConfig and still not make a mess. To show how we would fast fail in case of a config error, here we are simply checking if the directory exist. Because it doesn’t our output will look like this:

Exception in thread "main" java.lang.RuntimeException: locationDir someDir doesn't exist
    at com.kornelcsaszar.examples.OffSiteAuditLog$.loadConfig(MyApp.scala:50)
...

Conclusion

Using ConfigFactory with case classes corresponding to components is a clean way of managing configuration injection in Scala applications. We can validate and fast fail on errors, and reduce boilerplate by using Scala’s type based injection construct, implicits, to inject configuration objects into our components without having to rely on heavy third party injection frameworks.