3

What I am trying to do is to pass the two actors (mummy actor and daddy actor) to kid actor. As it's the best practice to use the actor reference instead of actor, I used IActorRef for both mummy actor and daddy actor to be injected thru DI with named parameter. But I am getting "mummyActor is not unique" error. Any idea how to solve it?

using System;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.DI.AutoFac;
using Akka.DI.Core;
using Autofac;
using Autofac.Core;


namespace Akka.DI.AutoFac.ExampleConsole {

    public class DaddyActor : ReceiveActor {
        public DaddyActor() {
            Receive<DoneEatingMessage>(m => {
                Console.WriteLine("Kid finished eating. So what? ~ Dad");
            });
        }
    }


    public class MummyActor : ReceiveActor {
        public MummyActor() {
            Receive<DoneEatingMessage>(m => {
                Console.WriteLine("Kid finished eating. Time to clean up! ~Mummy");
            });
        }
    }

    public class KidActor : ReceiveActor {

        private IService _service;

        private IActorRef _mummyActor;
        private IActorRef _daddyActor;

        public KidActor(IService service, IActorRef mummyActor, IActorRef daddyActor) {
            this._service = service;
            this._mummyActor = mummyActor;
            this._daddyActor = daddyActor;

            Receive<EatMessage>(m=>{
                var food = service.GetFood();
                Console.WriteLine("Kid eat this food {0}", food);
                _mummyActor.Tell(new DoneEatingMessage());
            });
        }


    }

    public class EatMessage{    }
    public class DoneEatingMessage { }

    public interface IService {
        string GetFood();
    }
    public class FoodService : IService {

        public string GetFood() {
            return "banana";
        }
    }
    class Program {
        static ActorSystem _actorSystem;
        static void Main(string[] args) {

            var builder = new Autofac.ContainerBuilder();
            builder.RegisterType<FoodService>().As<IService>();
            builder.RegisterType<MummyActor>().InstancePerDependency();
            builder.RegisterType<DaddyActor>().InstancePerDependency();

            builder.Register(c => _actorSystem.ActorOf(_actorSystem.DI().Props<DaddyActor>(), "daddyActor"))
                .Named<IActorRef>("daddyActorRef")
                .AsSelf();

            builder.Register(c => _actorSystem.ActorOf(_actorSystem.DI().Props<MummyActor>(), "mummyActor"))
                .Named<IActorRef>("mummyActorRef")
                .AsSelf();



            builder.RegisterType<KidActor>()
                .WithParameter(
                  new ResolvedParameter(
                       (pi, ctx) => pi.ParameterType == typeof(MummyActor),
                       (pi, ctx) => ctx.ResolveNamed<IActorRef>("mummyActorRef")
                    )
                )
                .WithParameter(
                  new ResolvedParameter(
                       (pi, ctx) => pi.ParameterType == typeof(DaddyActor),
                       (pi, ctx) => ctx.ResolveNamed<IActorRef>("daddyActorRef")
                    )
                )
                .InstancePerDependency();

            var container = builder.Build();

            _actorSystem = ActorSystem.Create("ActorDISystem");
            var propsResolver = new AutoFacDependencyResolver(container, _actorSystem);


            var kidActorProps = _actorSystem.DI().Props<KidActor>();
            var kidActor = _actorSystem.ActorOf(kidActorProps, "kidActor");

            kidActor.Tell(new EatMessage());

            Console.WriteLine("Holah");
            Console.ReadLine();
        }
    }
}
jbafford
  • 5,068
  • 24
  • 34
Michael Sync
  • 4,454
  • 8
  • 35
  • 53

3 Answers3

4

The thing is that the MummyActor and DaddyActor types are not instances of IActorRef. So you cannot use these types when creating the KidActor.

I am not very familiar with AutoFac, but I was able to make it work like this:

builder.RegisterType<KidActor>()
.WithParameter(
  new ResolvedParameter(
       (pi, ctx) => pi.Name == "mummyActor",
       (pi, ctx) => ctx.ResolveNamed<IActorRef>("mummyActorRef")
    )
)
.WithParameter(
  new ResolvedParameter(
       (pi, ctx) => pi.Name == "daddyActor",
       (pi, ctx) => ctx.ResolveNamed<IActorRef>("daddyActorRef")
    )
)
.InstancePerDependency();

I used the name of the parameter for the check. Yet I think this solution can be quite dangerous, especially if you rename the parameters.

One other thing you can do is delegating the creation of these instances to a service/factory with specific methods, and this service is injected through DI.

Here is what I got after a bit of refactoring:

public class DaddyActor : ReceiveActor
{
    public DaddyActor()
    {
        Receive<DoneEatingMessage>(m =>
        {
            Console.WriteLine("Kid finished eating. So what? ~ Dad");
        });
    }
}
public class MummyActor : ReceiveActor
{
    public MummyActor()
    {
        Receive<DoneEatingMessage>(m =>
        {
            Console.WriteLine("Kid finished eating. Time to clean up! ~Mummy");
        });
    }
}
public class KidActor : ReceiveActor
{
    private IService _service;
    private IActorRef _mummyActor;
    private IActorRef _daddyActor;

    public KidActor(IService service, IParentFactory parentFactory)
    {
        this._service = service;
        this._mummyActor = parentFactory.CreateMother(Context.System);
        this._daddyActor = parentFactory.CreateFather(Context.System);

        Receive<EatMessage>(m =>
        {
            var food = service.GetFood();
            Console.WriteLine("Kid eat this food {0}", food);
            _mummyActor.Tell(new DoneEatingMessage());
            _daddyActor.Tell(new DoneEatingMessage());
        });
    }
}
public class EatMessage { }
public class DoneEatingMessage { }

public interface IService
{
    string GetFood();
}
public class FoodService : IService
{
    public string GetFood()
    {
        return "banana";
    }
}

public interface IParentFactory
{
    IActorRef CreateMother(ActorSystem actorSystem);
    IActorRef CreateFather(ActorSystem actorSystem);
}
public class ParentFactory : IParentFactory
{
    public IActorRef CreateFather(ActorSystem actorSystem)
    {
        return actorSystem.ActorOf(actorSystem.DI().Props<DaddyActor>(), "daddyActor");
    }

    public IActorRef CreateMother(ActorSystem actorSystem)
    {
        return actorSystem.ActorOf(actorSystem.DI().Props<MummyActor>(), "mummyActor");
    }
}

class Program
{
    static ActorSystem _actorSystem;
    static void Main(string[] args)
    {
        var builder = new Autofac.ContainerBuilder();
        builder.RegisterType<FoodService>().As<IService>();
        builder.RegisterType<ParentFactory>().As<IParentFactory>();
        builder.RegisterType<MummyActor>().InstancePerDependency();
        builder.RegisterType<DaddyActor>().InstancePerDependency();
        builder.RegisterType<KidActor>().InstancePerDependency();

        var container = builder.Build();

        _actorSystem = ActorSystem.Create("ActorDISystem");
        var propsResolver = new AutoFacDependencyResolver(container, _actorSystem);

        var kidActorProps = _actorSystem.DI().Props<KidActor>();
        var kidActor = _actorSystem.ActorOf(kidActorProps, "kidActor");

        kidActor.Tell(new EatMessage());

        Console.WriteLine("Holah");
        Console.ReadLine();
        _actorSystem.AwaitTermination();
    }
}

I hope it will help you.

  • Using the factory to inject the actors hides the actual dependencies of the actor itself, i.e you have to read the constructor logic to find out the actual actor refs the KidActor depends on. Keeping them in the constructor is better, and to protected against the rename of constructor arguments, I generally have a set of unit tests against the container validating that actors can be resolved – nrjohnstone Apr 06 '16 at 22:04
  • The other problem with using the "factory" is it makes it look like the KidActor is actually creating the mother/father actors and thereby becoming their supervisor (parent) which I don't believe is the case – nrjohnstone May 28 '16 at 05:37
1

An alternate solution would be to inject the path of both Mummy and Daddy and use Context.ActorSelection inside of Kid to find them in the system. This fits better in a remote/cluster situation, and in cases where you have a cyclical reference chain.

Graham
  • 11
  • 1
0

The mummy and daddy actors are being created per kid actor. The mummy and daddy actors either need to be uniquely named or registered as singletons.

Telavian
  • 3,562
  • 6
  • 32
  • 53
  • Yes. I think this code that I use with "WithParameter" is wrong. I gave an unique name (ref: Named("daddyActorRef") ) but I am not sure how to reference them back from parameter. What do I need to change? – Michael Sync Sep 02 '15 at 03:35