Reputation: 2955
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?
Upvotes: 3
Views: 1776
Reputation: 26586
You mentioned, that you are using companion object
s as a services. I also noticed, that you are
creating actors inside of the object
s. In general I will discourage you from doing this. Scala (companion) object
s
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 Actor
s 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, system
s guardian actor would be it's supervisor.
Upvotes: 4