In my last AkkaOfEmpires blog post, I started to use dependency injection (DI) for my various actors, but it was made “by hand”. So today I will show how to do it with Akka.DI, which is a plugin bringing DI to Akka.NET.
My goal is to be able to ask the actor system for VillagerActor
instances without having to specify their dependencies, I’ll let Akka.DI handle this job.
Here is the VillagerActor
constructor having the dependencies:
public VillagerActor(IActorRef resourcesSupervisor, SubroutinesFactory subroutinesFactory) { _resourcesSupervisor = resourcesSupervisor; _subroutinesFactory = subroutinesFactory; Profession = Profession.Idle; }
And here is the use case:
// Program.cs var system = AkkaOfEmpiresSystem.Start(); var supervisorProps = Props.Create<ConsoleResourcesSupervisorActor>(); var supervisor = system.ActorOf(supervisorProps); var consoleSubroutinesFactory = new ConsoleSubroutinesFactory(); var villagerProps = Props.Create<ConsoleVillagerActor>(supervisor, consoleSubroutinesFactory); var gatherer = system.ActorOf(villagerProps, "Gatherer"); gatherer.Tell(new GatherFruits());
Using Akka.DI
Now I will change this last part in order to use dependency injection. First there is a nuget to install, in my case I will use StructureMap for the Inversion of Control (IoC) container.
Install-Package Akka.DI.StructureMap
Now, if you want to use another one you can see the list of available Akka.DI extensions here.
First I will rework the SubroutinesFactory
by extracting an interface.
public interface ISubroutinesFactory { IActorRef CreateResourceHarvesterActor(IActorContext actorContext, IActorRef villagerActor); } public class SubroutinesFactory : ISubroutinesFactory { public IActorRef CreateResourceHarvesterActor(IActorContext actorContext, IActorRef villagerActor) { var props = Props.Create<ResourceHarvesterActor>(actorContext.System.Scheduler, villagerActor); var resourceHarvesterRoutine = actorContext.ActorOf(props); return resourceHarvesterRoutine; } } public class ConsoleSubroutinesFactory : ISubroutinesFactory { public IActorRef CreateResourceHarvesterActor(IActorContext actorContext, IActorRef villagerActor) { var props = Props.Create<ConsoleResourceHarvesterActor>(actorContext.System.Scheduler, villagerActor); var consoleActor = actorContext.ActorOf(props); return consoleActor; } }
Like before, I have two factories: a “classic” one and another one for the Console GUI. I will use the latter in my DI configuration.
Akka.DI uses a dependency resolver to link the IoC container with the actor system, there is one avaialble in the nuget package. I can now update the code:
// Program.cs static void Main(string[] args) { var system = AkkaOfEmpiresSystem.Start(); var container = GetDependencyContainer(); var resolver = new StructureMapDependencyResolver(container, system); var villagerProps = system.DI().Props<ConsoleVillagerActor>(); var gatherer = system.ActorOf(villagerProps); gatherer.Tell(new GatherFruits()); system.AwaitTermination(); } static IContainer GetDependencyContainer() { var container = new Container(c => { c.For<ISubroutinesFactory>().Use<ConsoleSubroutinesFactory>(); }); return container; }
I have specified that every instance of ISubroutinesFactory
will be automatically resolved as ConsoleSubroutinesFactory
instances and that is what will happen when requesting a new VillagerActor
from the system.
I have taken care of one of the two dependencies for the actor. The other one is tricky because it is a IActorRef
instance which can refer to every type of actor in the system.
The easy way
Well, at the moment the only IActorRef I need to inject through DI is for the ResourcesSupervisorActor
, so I can have the following workaround:
static void Main(string[] args) { var system = AkkaOfEmpiresSystem.Start(); var container = GetDependencyContainer(system); var resolver = new StructureMapDependencyResolver(container, system); var villagerProps = system.DI().Props<ConsoleVillagerActor>(); var gatherer = system.ActorOf(villagerProps); gatherer.Tell(new GatherFruits()); system.AwaitTermination(); } static IContainer GetDependencyContainer(ActorSystem system) { var container = new Container(c => { c.For<ISubroutinesFactory>().Use<ConsoleSubroutinesFactory>(); c.ForSingletonOf<IActorRef>().Use(system.ActorOf<ConsoleResourcesSupervisorActor>()); }); return container;
I defined the dependency as a singleton because I want the resources supervisor to be the same for all the villagers. But now every classes that will need a IActorRef dependency will have a supervisor actor when constructed through DI.
With some IoC containers it is possible to play with named instance and conditions over the constructor parameters to provide the correct instance. But… meh… it is painful to set up and I think it can be dangerous. Let’s try something else.
Another way
What I can also do to fix my problem is removing the dependency to the IActorRef
. Instead I will inject a service that will provide the reference to the supervisor actor, like I did for the subroutine actor.
public interface ISupervisorsFactory { IActorRef GetResourcesSupervisor(); } public class SupervisorsFactory : ISupervisorsFactory { public SupervisorsFactory(ActorSystem system) { _resourcesSupervisor = system.ActorOf<ResourcesSupervisorActor>(); } private readonly IActorRef _resourcesSupervisor; public IActorRef GetResourcesSupervisor() { return _resourcesSupervisor; } } public class ConsoleSupervisorsFactory : ISupervisorsFactory { public ConsoleSupervisorsFactory(ActorSystem system) { _resourcesSupervisor = system.ActorOf<ConsoleResourcesSupervisorActor>(); } private readonly IActorRef _resourcesSupervisor; public IActorRef GetResourcesSupervisor() { return _resourcesSupervisor; } }
Now this factory can be used by the VillagerActor
like this:
public VillagerActor(ISupervisorsFactory supervisorsFactory, ISubroutinesFactory subroutinesFactory) { _resourcesSupervisor = supervisorsFactory.GetResourcesSupervisor(); _subroutinesFactory = subroutinesFactory; Profession = Profession.Idle; }
And I can update my IoC container configuration to link everything.
static IContainer GetDependencyContainer(ActorSystem actorSystem) { var container = new Container(c => { c.For<ISubroutinesFactory>().Use<ConsoleSubroutinesFactory>(); c.ForSingletonOf<ISupervisorsFactory>() .Use<ConsoleSupervisorsFactory>() .Ctor<ActorSystem>() .Is(actorSystem); }); return container; }
Here is the end of my introduction to Akka.DI for Akka.NET. It allows the instantiation of actor references without having to declare all their dependencies every time a new actor is needed. Yet using DI with Akka.NET can become tricky when these dependencies are others IActorRef
. At the moment I prefer avoiding them as much as possible.
Feel free to share your own experience with Akka.DI when it comes to IActorRef
.
See you next time!