Dependency Injection - laforge49/Asynchronous-Functional-Programming GitHub Wiki

If you need to have a number of operations that can be accessed by many actors, dependency injection is the way to go. Dependency injection is done using the SystemServices object, which was previously covered in the discussion on AsyncMailbox. SystemServices is a composite actor, so we just need to add the operations we need as additional components. Lets start by looking at a component which supplies an "common" operation and its factory:

case class Today()

class Sayings(actor: Actor) extends Component(actor) {
  bind(classOf[Today], today)

  def today(msg: AnyRef, rf: Any => Unit) {
    rf("Today is the first day of the rest of your life.")
  }
}

class SayingsFactory extends ComponentFactory {
  override protected def instantiate(actor: Actor) = new Sayings(actor)
}

Now, assuming that the Sayings component is part of the SystemServices object, lets see how we would send a Today message to it:

case class SaySomething()

class SayIt extends Actor {
  bind(classOf[SaySomething], saySomething)

  def saySomething(msg: AnyRef, rf: Any => Unit) {
    systemServices(Today())(rf)
  }
}

Now for a bit of test code which creates and configures the SayIt actor and then sends a SaySomething message to it:

val systemServices = SystemServices(new SayingsFactory)
try {
  val sayIt = new SayIt
  sayIt.setExchangeMessenger(systemServices.newSyncMailbox)
  println(Future(sayIt, SaySomething()))
} finally {
  systemServices.close
}

There are two ways of providing an actor with access to the SystemServices object. (1) You can do it explicitly by calling the Actor.setSystemServices method or (2) you can assign a mailbox to the actor by calling Actor.setExchangeMessenger. In the latter case, the actor gets the default SystemServices object from the mailbox. If you call both methods, the value passed in the call to setSystemServices is always used.

When we created the SystemServices composite, we added the Sayings component by passing its factory as an parameter. But what if we want to add more than one component? We have already seen in Factories and Components that the addDependency method can be used in a component factory to ensure the inclusion of other components. So at worst, we can just create a top-level component factory which creates multiple dependencies on the component factories of all the components that should be included in the SystemServices composite.

IOCTest

##Subsystems

Large programs are often not homogenous, but are divided into subsystems. And just as it is helpful to provide easy access to operations that are used by many actors, it is also helpful to provide easy access to operations specific to a subsystem to all the actors in that subsystem. AsyncFP supports this.

The SystemServices class has two subclasses, RootSystemServices and Subsystem. The SystemServices companion object creates and initializes a RootSystemServices object; the Subsystem companion object creates and initializes a Subsystem object. Here is a sample test code which creates a subsystem object:

val systemServices = SystemServices(new SomeComponentFactory)
try {
  val aSubsystem = Subsystem(systemServices, new FactoryRegistryComponentFactory)
  val driver = new Driver
  driver.setSystemServices(aSubsystem)
  driver.setExchangeMessenger(systemServices.newSyncMailbox)
  Future(driver, DoIt())
} finally {
  systemServices.close
}

Subsystem objects are composite actors and as you can see above it is easy to add additional service(s). Subsystem objects also carry a reference to their superior systemServices--any message which can not be handled by a subsystem object is passed up to its superior systemServices object, recursively.

For a subsystem to be used as an actor's systemServices, you must call the Actor.setSystemServices method. A call to Actor.setExchangeMessenger only sets the actor's default systemServices to the RootSystemServices object.

SystemServices