Actor Dependency Injection (IoC)
Otavia provides compile-time type-safe dependency injection for Actor addresses using Scala 3's match types. The ActorSystem acts as the IoC container, and BeanManager handles registration and lookup.
BeanManager
BeanManager stores beans in three ConcurrentHashMaps:
| Map | Key | Purpose |
|---|---|---|
beans | Class name | Direct class name lookup |
qualifiers | Qualifier string | Qualified bean lookup |
superTypes | Superclass/interface name | Type hierarchy lookup (returns list) |
Registration
When an actor is created, it can be registered with the BeanManager:
system.buildActor[MyService](factory, qualifier = "primary", primary = true)
Parameters:
qualifier: An optional string to disambiguate multiple beans of the same typeprimary: Marks this bean as the default choice when multiple candidates exist
Lookup
getBean supports multiple lookup strategies:
- By class name: Direct lookup in
beansmap - By qualifier + class: Lookup in
qualifiersmap - By superclass/interface: Lookup in
superTypesmap, returns matching beans - Primary bean: When multiple candidates exist, the one marked
primaryis preferred
Bean
Bean holds:
clz: Class[?]— The actor's classaddress: Address[Call]— The actor's Addressprimary: Boolean— Whether this is the primary bean- Superclass/interface hierarchy computed via reflection for type-based lookup
BeanDefinition and Module
BeanDefinition is a case class specifying how to create and register an actor:
case class BeanDefinition(
factory: ActorFactory[?],
num: Int,
qualifier: String,
primary: Boolean
)
Module is a trait with a definitions: Seq[BeanDefinition] method. When loaded via system.loadModule(module), all definitions are instantiated as actors and registered with the BeanManager.
class MyModule extends AbstractModule {
override def definitions: Seq[BeanDefinition] = Seq(
BeanDefinition(ActorFactory[UserService], 1, "", true),
BeanDefinition(ActorFactory[OrderService], 1, "", true)
)
}
system.loadModule(new MyModule)
autowire
The Actor.autowire[A] method provides type-safe service lookup:
class MyActor extends StateActor[MyCall] {
override protected def afterMount(): Unit = {
val userService: Address[MessageOf[UserService]] = autowire[UserService]()
val orderService: Address[MessageOf[OrderService]] = autowire[OrderService](qualifier = "primary")
}
}
Under the hood, autowire calls system.getAddress(classTag[A].runtimeClass, qualifier, remote), which delegates to BeanManager.getBean.
The MessageOf[A] type alias maps an Actor type to the message type it accepts, ensuring type safety at the Address level.