Skip to main content

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:

MapKeyPurpose
beansClass nameDirect class name lookup
qualifiersQualifier stringQualified bean lookup
superTypesSuperclass/interface nameType 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 type
  • primary: Marks this bean as the default choice when multiple candidates exist

Lookup

getBean supports multiple lookup strategies:

  1. By class name: Direct lookup in beans map
  2. By qualifier + class: Lookup in qualifiers map
  3. By superclass/interface: Lookup in superTypes map, returns matching beans
  4. Primary bean: When multiple candidates exist, the one marked primary is preferred

Bean

Bean holds:

  • clz: Class[?] — The actor's class
  • address: Address[Call] — The actor's Address
  • primary: 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.