Scala’s Built-in Dependency Injection

Following: Dead-Simple Dependency Injection
It is:

  • Dead-simple. Just function composition
  • Explicit, type-safe dependencies
  • Lift any function
  • No frameworks, annotations, or XML
  • No initialization step
  • Doesn’t rely on esoteric language features

Did he say “Dead Simple”? 😉


/**
 * dependency "framework"
 */
package object di {

  // Reader Monad
  case class Reader[C, A](g: C => A) {
    def apply(c: C) = g(c)

    def map[B](f: A => B): Reader[C, B] = Reader(c => f(g(c)))

    def flatMap[B](f: A => Reader[C, B]): Reader[C, B] = Reader(c => f(g(c))(c))
  }

  implicit def reader[A, B](f: A => B) = Reader(f)

  def pure[C, A](a: A) = Reader[C, A](con => a)
}

/**
 * Database API mock package
 */
package object db {

  // DB Connection Class
  case class Cxn(driver: String, url: String)

  // mixin to use some DB API functions
  trait Db_API {

    def setUserPwd(id: String, pwd: String): Cxn => Unit =
      c => {
        println("setUserPwd with Connection: " + c)
      }

    def getUserPwd(id: String): Cxn => String =
      c => {
        println("getUserPwd with Connection: " + c)
        "3"
      }
  }

}

/**
 * --------------------------------------------------------------------------------
 */

import di._
import db._


trait CxnProvider {
  def apply[A](f: Reader[Cxn, A]): A
}

object CxnProvider {
  def apply(driver: String, url: String) =
    new CxnProvider {
      def apply[A](f: Reader[Cxn, A]): A = f(Cxn(driver, url))
    }
}


trait MyBusinessLogic extends Db_API {

  // Monad comprehension
  def changePwd(userid: String, oldPwd: String, newPwd: String): Reader[Cxn, Boolean] = {
    for {
      pwd <- getUserPwd(userid)
      eq <- if (pwd == oldPwd) for {_ <- setUserPwd(userid, newPwd)} yield true
      else pure[Cxn, Boolean](false)
    } yield eq
  }


  def businessApplication(userid: String, oldPwd: String, newPwd: String): CxnProvider => Unit =
    r => {
      r(changePwd(userid, oldPwd, newPwd))
    }
}

/**
 * --------------------------------------------------------------------------------
 */

object ExecutingMain extends App with MyBusinessLogic {

  lazy val sqliteTestDB = CxnProvider("org.sqlite.JDBC", "jdbc:sqlite::memory:")
  lazy val mysqlProdDB = CxnProvider("org.gjt.mm.mysql.Driver", "jdbc:mysql://prod:3306/?user=one&password=two")

  def runInTest[A](f: CxnProvider => A): A = f(sqliteTestDB)

  def runInProduction[A](f: CxnProvider => A): A = f(mysqlProdDB)

  runInTest(businessApplication("Some Test", "1", "2"))
  runInProduction(businessApplication("Some Test", "3", "2"))
}

Prints out

getUserPwd with Connection: Cxn(org.sqlite.JDBC,jdbc:sqlite::memory:)
getUserPwd with Connection: Cxn(org.gjt.mm.mysql.Driver,jdbc:mysql://prod:3306/?user=one&password=two)
setUserPwd with Connection: Cxn(org.gjt.mm.mysql.Driver,jdbc:mysql://prod:3306/?user=one&password=two)

Connect with me on Google+

Comments are closed.