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+