воскресенье, 20 марта 2011 г.

Dependency injection with Lift

While migrating my web app to Lift 2.2 and CSS selectors i've noticed the dependency injection section in Simply Lift. It looked interesting and i decided to try it. Here is what i've finally got:

The Factory:
object DependencyFactory extends Factory {
  // The following trait has become necessary to avoid name clashes 
  // between a FactoryMaker and its contents during implicit conversion
  // (e.g. FactoryMaker has method 'find' so if contained type has a 
  // 'find' too it will not trigger the implicit conversion and you'll
  // get error instead)
  trait VendorMarker[T] {
    this: FactoryMaker[T] =>
    // since our fields will be visible as Vendor (to avoid name clashes),
    // we need a method to get back to FactoryMaker (e.g. to use its 
    // 'doWith' method)
    def asFactoryMaker: FactoryMaker[T] = this
  }
  // factory method to make FactoryMakers visible as Vendors with a 
  // marker trait
  private def newVendor[T : Manifest](v: T): Vendor[T] with VendorMarker[T] =
    new FactoryMaker[T](v) with VendorMarker[T]

  implicit def vendorToValue[T](vendor: Vendor[T]): T = vendor.vend

  val props = newVendor[AppProperties](new AppPropertiesImpl)
  val db = newVendor[DbAccess](Model)
  val roomStore = newVendor(new RoomStore)
  val quizFactory = newVendor(new QuizFactory)
  // etc
}

Usage examples:
class RoomStore {
  // Dependency requirements expressed with imports
  import lib.DependencyFactory.db

  // now we are using db as DbAccess via the implicit conversion
  def updateRoom(room: Room): Room = db.merge(room)
  def getRoom(id: Long): Option[Room] = db.find(classOf[Room], id)
}

// This one has a lot of dependencies
import lib.DependencyFactory.{chatFactory, currentTime, roomStore, persistentMessageStore, questionStore}
class Quiz(_room: Room) extends AbstractQuiz(chatFactory.newChat(), currentTime) {
// ...
}

class QuizFactory {
  def newQuiz(room: Room) = new Quiz(room)
  // note the asFactoryMaker.doWith usage to temporarily replace a dependency
  def newQuiz(room: Room, msgs: List[Message]) =
    lib.DependencyFactory.chatFactory.asFactoryMaker.doWith(new ChatFactory {
        override def newChat(msgs: List[Message], capacity: Int) = new Chat(msgs, 100)
      }) { new Quiz(room) }
}


Quite cool feature to use!