Search code examples
scaladependency-injectionproduction-environmentservice-locator

Scala service separating by environment (service locator?)


My Scala application supports 2 environments: TEST and PROD. The difference is in using services. For example, production Emailer actualy sends an email, and test Emailer is rather stub or mock. The environment is configured by parameter. How do you implement such a service separating by environment? Do you prefer some solution with DI like Guice?


Solution

  • In addition to Guice, you can keep using the native Scala cake pattern dependency injection if you want.

    // MyService.scala
    trait MyService {
       this: Emailer =>   // Cake pattern: this must be instatiated with an Emailer trait
    
       def doSomething() {
          //...
          this.email(...)
          //...
       }
    }
    
    // Somewhere else
    trait Emailer { def email(args: String): Unit }
    trait MockEmailer { override def email(args: String) = println("Email was sent!") }
    trait RealEmailer { override def email(args: String) = actuallySendAnEmail(args) }
    
    // Application.scala    
    sealed trait Environment
    case object Test extends Environment
    case object Prod extends Environment
    
    object Application {
       private var _environment: Environment = Test // Choose default
       def environment = _environment
    
       def main(args: Array[String) {
           // Determine environment at startup
           if(args.contains("PROD") {
             _environment = Prod
           } else {
             _environment = Test
           }
           // ...
       }
    }
    
    // Configuration.scala
    val myService = Application.environment match {
       case Test => new MyService with MockEmailer
       case Prod => new MyService with RealEmailer
    }
    

    It takes a handful of lines to write this yourself, but it doesn't require any seperate dependency injection framework with its own annotation verbosities. In addition, you won't encounter runtime dependency injection errors - the Scala compiler guarantees this will work.