Search code examples
scaladependency-injectionakkascaldi

Scaldi dependency injection and Akka Actors


I'm currently exploring using Scaldi for Dependency Injection in a Play2.2 application.

I have read the documentation on Scaldi's website, but what is unclear to me is how to use it with Akka.

What I have so far in my project:

Models/ (Daos and case classes)
   User.scala
Services/  (Akka Actors)
   UserService.scala
   ProfileService.scala
Managers/  (Regular Manager Classes)
   UserManager.scala (The Trait Interface)
   UserManagerImpl.scala (An actual implementation)
   UserManagerMock.scala (Mocked version)
   etc..

In UserService.scala I would use an instance of the UserManager to do the work:

class UserService extends ServiceActor with Injection
{
    val userManager = inject[UserManager]

    def receive = {
        case Register(email: String, password: String)
    }
}

object UserService extends Service
{
    case class Register(email: String, password: String)

    override protected val actorRef = Akka.system.actorOf(Props[UserService].withRouter(SmallestMailboxRouter(resizer = Some(resizer))))
}

Then depending on the injected manager, the actor can be sort of mocked if it delegate all the work to the manager?

However, what if the managers needs to call other Services, which are just companion objects? Or Services calling other services that are also referenced via companion objects?

Does anyone have some pointers on how to integrate Akka with Scaldi?


Solution

  • You mentioned, that you are using companion objects as a services. I also noticed, that you are creating actors inside of the objects. In general I will discourage you from doing this. Scala (companion) objects are just singletons. While they can be useful and appropriate in some circumstances, in general they are considered to be an anti-pattern rather than a pattern, especially if you want to do dependency injection or inversion of control in your application. There are a lot of reasons for this, but the most important ones in this case are: it's hard to mock them, it's hard to control their instantiation, and in general they represent an opposite of inversion of control.

    Another problem, is that you are creating actors inside of these singleton objects. Very important aspect of actor model is supervision hierarchy. By creating this actor (UserService in your case) in isolation, you most probably let guardian actor to be it's supervisor, which in most case is not what you want. So I would recommend to create most of the actors within another actors, except few, that need to be top-level actors. This will make sure that they have proper supervision hierarchy.

    These ideas also remain the same if you are using Scaldi. scaldi-akka provides convenient way to inject an ActorRef or Props for some particular actor. Here is a small example of how you can inject normal bindings and ActorRefs:

    class ProfileManager (implicit inj: Injector) extends Injectable
    
    trait UserManager {
      def register(email: String, password: String): User
    }
    
    class UserManagerImpl(implicit inj: Injector) extends UserManager with Injectable {
      val profileManager = inject [ProfileManager]
    
      def register(email: String, password: String) = ???
    }
    
    class UserService(implicit inj: Injector) extends Actor with AkkaInjectable {
      val userManager = inject [UserManager]
    
      import UserService._
    
      def receive = {
        case Register(email, password) =>
          userManager
      }
    }
    
    object UserService {
      case class Register(email: String, password: String)
    }
    
    class ReceptionistService(implicit inj: Injector) extends Actor with AkkaInjectable {
      val userManager = injectActorRef [UserService]
      def receive = ???
    }
    

    Please note, that injectActorRef creates and actor within the context of current actor. So the equivalent would be:

    val userManager = context.actorOf(injectActorProps[UserService])
    

    Now you need to create binding for the ActorSystem (it's optional, and if you are using Play, you probably need to get ActorSystem from the play application, which already has one), services (which are actors in your case) and managers:

    implicit val module = new Module {
        bind [ActorSystem] to ActorSystem("MySystem")
    
        binding toProvider new UserService
        binding toProvider new ReceptionistService
    
        bind [UserManager] to new UserManagerImpl
        binding to new ProfileManager
    }
    

    It is important to bind Actors with toProvider. This will make sure, that each time Akka asks Scaldi for some particular Actor, it will always get the new instance of it.

    Now, if you want ReceptionistService to be your top-level actor, you can use it like this:

    implicit val system = inject [ActorSystem]
    
    val receptionist = injectActorRef [ReceptionistService]
    
    receptionist ! DoStuff
    

    In this case, systems guardian actor would be it's supervisor.