Saturday, December 10, 2016

Injecting Akka actors in Play with Guice

Actors are created by passing a Props instance into the actorOf factory method of the ActorSystem. This factory method will not work if an actor constructor has arguments, which should be injected.
Akka way for actors dependency injection is with IndirectActorProducer interface (see Akka documentation.).
If the dependency injection framework is Guice, Play makes some things under the veil and suggests another pattern for injecting Akka actors.

Explicit actor creation basic

The constructor of the SaySomethingActor receives a single argument - string, which is send via a factory method props().
 public class SaySomethingActor extends UntypedActor {  
      //Protocol  
      public static class Someone {  
           private final String name;  
           public Someone(String name) {  
                this.name = name;  
           }  
           public String getName() {  
                return name;  
           }  
      }  
      // The factory method for explicit creation  
      public static Props props(String something) {  
           return Props.create(SaySomethingActor.class, something);  
      }  
      private String something;  
      public SaySomethingActor(String something) {  
           this.something = something;  
      }  
      private void sayHello(Someone someone) {  
           String helloString = something + " " + someone.getName();  
           System.out.println(helloString);  
           sender().tell(helloString, self());  
      }  
      @Override  
      public void onReceive(Object msg) throws Exception {  
           if (msg instanceof Someone) {  
                sayHello((Someone)msg);  
           }            
           else {  
                unhandled(msg);  
           }  
      }  
 }  
A controller has the injected member ActorSystem system. The controller creates an actor with the system factory method actorOf:
 public class ActorExamplesController extends Controller {  
      private ActorSystem system;  
      @Inject  
      public ActorExamplesController(ActorSystem system) {  
           this.system= system;  
      }  
      public CompletionStage<Result> saySomething(String name, String something) {   
           CompletionStage<ActorRef> actor = system.actorOf(SaySomethingActor.props(something), "actor-" + String.valueOf(System.currentTimeMillis()));  
           long timeoutMillis = 100L;  
           return FutureConverters.toJava(ask(actor, new SaySomethingActor.Someone(name), timeoutMillis))  
                     .thenApply(response-> ok((String)response));  
      }  
 }  

Actor dependency injecting basic


To be available for injection an actor should be implemented with injection annotation, most likely @Inject:
 public class MyActor extends UntypedActor {  
   // Protocol
   public static final String MSG = "message";
   private final Configuration configuration;
   @Inject  
   public MyActor(Configuration configuration) { 
     this.configuration = configuration;
   }  
   @Override  
   public void onReceive(Object message) throws Exception {  
     if (message instanceof String) {
        onMessage((String)message;
     }
     else {
        unhandled(message);
     }
   }
   void onMessage(String message) {
       ...
   }  
 }  
or Lambda actor:
 public class MyActor extends AstractActor {  
   private final Configuration configuration;
   @Inject  
   public MyActor(Configuration configuration) {  
     this.configuration = configuration;
     receive(ReceiveBuilder
        .matchEquals(MSG, this::onMessage)
        .matchAny(m -> unhandled(m))
        .build());
   }  
   void onMessage(String message) {
       ...
   }  
 }  
The constructor argument configuration will be injected by Guice prior to the actor creation.

The actor binding is done with the actor class name and a string identifier. The module with bindings should implement AkkaGuiceSupport interface:
 import com.google.inject.AbstractModule;  
 import play.libs.akka.AkkaGuiceSupport;  
 public class ActorsModule extends AbstractModule implements AkkaGuiceSupport {  
      @Override  
      protected void configure() {  
           bindActor(MyActor.class, "myActor");  
      }  
 }  
The string identifier, specified in the binding, is used for injecting the actor instance:
 public class ActorExamplesController extends Controller {  
      private ActorRef myActor;  
      @Inject  
      public ActorExamplesController(ActorSystem system, @Named("myActor") ActorRef myActor) {  
           this.myActor = myActor;  
      }
      ...
}
Call to the actor with Akka ask pattern:
 public CompletionStage<Result> sendAndReceiveMessage(String message) {  
    return FutureConverters.toJava(ask(myActor, MyActor.MSG, 1000)).thenApply(response-> ok((String)response));  
 }  
Or just sending a message to it:
 public void sendMessage(String message) {  
    myActor.tell(MyActor.MSG, ActorRef.noSender());  
 }  

Actor AssistedInject


AssistedInject is used when a constructor of injected class has not-injected arguments. You can read how it works in this earlier post.
AssistedInject of actors is different. An actor can be assisted injected only with a help of a parent actor. Let's see an example.

The constructor of the SaySomethingActor receives two arguments: configuration and a string. Configuration argument is injected, while a string should be send explicitly, which is marked with the annotation @Assisted.
The nested Factory interface specifies the factory method to be used for the actor instance creation (instead of Props). The factory method receives all not-injected arguments of the actor constructor:
  public class SaySomethingActor extends UntypedActor {  
      //Protocol  
      public static class Someone {  
           private final String name;  
           public Someone(String name) {  
                this.name = name;  
           }  
           public String getName() {  
                return name;  
           }  
      } 
      // The factory method for injection 
      public interface Factory {  
           Actor create(String something);  
      }  
      // The factory method for explicit creation  
      public static Props props(Configuration configuration, String something) {  
           return Props.create(SaySomethingActor.class, configuration, something);  
      }  
      private static final String SIGNATURE_PROPERTY = "signature";  
      private String something;       
      private final Configuration configuration;  
      @Inject  
      public SaySomethingActor(Configuration configuration, @Assisted String something) {  
           this.something = something;  
           this.configuration = configuration;  
      }  
      private void sayHello(Someone someone) {  
           String helloString = something + " " + someone.getName() + ", signature:" + configuration.getString(SIGNATURE_PROPERTY);  
           System.out.println(helloString);  
           sender().tell(helloString, self());  
      }  
      @Override  
      public void onReceive(Object msg) throws Exception {  
           if (msg instanceof Someone) {  
                sayHello((Someone)msg);  
           }            
           else {  
                unhandled(msg);  
           }  
      }  
 }
The parent actor SaySomethingParentActor implements InjectedActorSupport interface. It's constructor receives an argument SaySomethingActor.Factory childFactory for child actors creation.
The protocol message class CreateChild encapsulates all parameters needed for a child creation: something contains an argument to be send to the factory method and id is to be used as a unique child actor name.
The SaySomethingParentActor injects a child actor upon receiving of a CreateChild message. After injecting of a child actor, the parent sends the ActorRef of the injected child to the caller:
 public class SaySomethingParentActor extends AbstractActor implements InjectedActorSupport {  
      //Protocol  
      public static class CreateChild {  
           private final String something;  
           private final String id;  
           public CreateChild(String something, String id) {  
                this.something = something;  
                this.id = id;  
           }            
      }  
      private SaySomethingActor.Factory childFactory;  
      public static Props props(SaySomethingActor.Factory childFactory) {  
           return Props.create(SaySomethingParentActor.class, childFactory);  
      }  
      @Inject  
      public SaySomethingParentActor(SaySomethingActor.Factory childFactory) {  
           this.childFactory = childFactory;  
           receive(ReceiveBuilder  
                .match(CreateChild.class, this::injectHelloActor)  
                .matchAny(this::unhandled)  
                .build());  
      }  
      private void injectHelloActor(CreateChild injectMsg) {  
           ActorRef child = injectedChild(() -> childFactory.create(configuration, injectMsg.something), "child-" +injectMsg.id);  
           System.out.println("Injected " + "child-" +injectMsg.id);  
           sender().tell(child, self());  
      }  
 }  
Injection bindings of a parent actor is done by the actor class name and a string identifier. A child actor binding is done by specifying the actor class name and and the factory interface class name instead of a string identifier:
 public class ActorsModule extends AbstractModule implements AkkaGuiceSupport {  
      @Override  
      protected void configure() {  
           bindActor(SaySomethingParentActor.class, "parentActor");  
           bindActorFactory(SaySomethingActor.class, SaySomethingActor.Factory.class);  
      }  
 }  
The controller has injected parent actor. The parent is called for injecting of the child actor:
 public class ActorExamplesController extends Controller {  
      private ActorRef parentActor;  
      @Inject  
      public ActorExamplesController(ActorSystem system, @Named("parentActor") ActorRef parentActor) {  
           this.parentActor = parentActor;  
      }  
      public CompletionStage<Result> saySomething(String name, String something) {  
           CompletionStage<ActorRef> childActor = createChildActor(something);  
           return childActor.thenComposeAsync(actorRef -> sayHelloByRef(actorRef, name));  
      }  
      private CompletionStage<ActorRef> createChildActor(String param) {  
           // Use guice assisted injection to instantiate and configure the child actor.  
           long timeoutMillis = 100L;  
           return FutureConverters.toJava(ask(parentActor, 
               new SaySomethingParentActor.CreateChild(param, String.valueOf(System.currentTimeMillis())),
               timeoutMillis))
              .thenApply(response -> (ActorRef) response);  
      }  
      private CompletionStage<Result> sayHelloByRef(ActorRef actorRef, String name) {  
           long timeoutMillis = 100L;  
           return FutureConverters.toJava(ask(actorRef, new SaySomethingActor.Someone(name), timeoutMillis))
               .thenApply(response-> ok((String)response));  
      }  
 }  
Assisted inject of Akka actor demands a lot of boilerplate code. Implementation of SaySomethingActor without injected arguments in the constructor, which was shown in the fist example, is much shorter, than the option with assisted injection.

See working examples on Git.

No comments :

About the author

My Photo
I trust only simple code and believe that code should be handsome. This is not a matter of technology, but professional approach, consolidated after years of software development. I enjoy to cause things working and feel very happy, when I manage to solve a problem.
Back to Top