Modern Akka DI with Guice

后端 未结 6 1050
时光取名叫无心
时光取名叫无心 2021-02-13 19:41

Java 8, Guice 4.0 and Akka 2.3.9 here. I am trying to figure out how to annotate my actor classes with JSR330-style @Inject annotations, and then wire them all up v

相关标签:
6条回答
  • 2021-02-13 20:17

    In case anyone found this question, you need to use IndirectActorProducer, I referred to the Spring example and changed it to use Guice instead.

    /**
     * An actor producer that lets Guice create the Actor instances.
     */
    public class GuiceActorProducer implements IndirectActorProducer {
        final String actorBeanName;
        final Injector injector;
        final Class<? extends Actor> actorClass;
    
        public GuiceActorProducer(Injector injector, String actorBeanName, Class<? extends Actor> actorClass) {
            this.actorBeanName = actorBeanName;
            this.injector = injector;
            this.actorClass = actorClass;
        }
    
        @Override
        public Actor produce() {
            return injector.getInstance(Key.get(Actor.class, Names.named(actorBeanName)));
        }
    
        @Override
        public Class<? extends Actor> actorClass() {
            return actorClass;
        }
    }
    

    In the module

    public class BookingModule extends AbstractModule {
    
        @Override
        protected void configure() {               
            // Raw actor class, meant to be used by GuiceActorProducer.
            // Do not use this directly
            bind(Actor.class).annotatedWith(
                    Names.named(BookingActor.ACTOR_BEAN_NAME)).to(
                    BookingActor.class);
        }
    
        @Singleton
        @Provides
        @Named(BookingActor.ACTOR_ROUTER_BEAN_NAME)
        ActorRef systemActorRouter(Injector injector, ActorSystem actorSystem) {
          Props props = Props.create(GuiceActorProducer.class, injector, BookingActor.ACTOR_BEAN_NAME, actorClass);
          actorSystem.actorOf(props.withRouter(new RoundRobinPool(DEFAULT_ROUTER_SIZE)), BookingActor.ACTOR_ROUTER_BEAN_NAME);
        }
    }
    
    0 讨论(0)
  • 2021-02-13 20:21

    Use Creator to create ActorRefs in provider methods of your guice module. To distinguish between the different ActorRefs, which are untyped, use annotations on your provider methods and injection points as you would any guice system. For example,

    In your guice module:

    @Override
    protected void configure() {
        bind(ActorSystem.class).toInstance(ActorSystem.apply());
        bind(FizzService.class).toInstance(new FizzServiceImpl());
    }
    
    @Provides @Singleton @Named("fizzActor")
    ActorRef serviceActorRef(final ActorSystem system, final FizzService fizzService) {
        return system.actorOf(Props.create(new Creator<Actor>() {
            @Override
            public Actor create() throws Exception {
                return new FizzActor(fizzService);
            }
        }));
    }
    

    Then to use the actor service, inject a specific ActorRef:

    class ClientOfFizzActor {
        @Inject
        ClientOfFizzActor(@Named("fizzActor") ActorRef fizzActorRef) {..}
    }
    

    It looks cleaner if the Props.create(..) clause is a static factory method in your actor class.

    0 讨论(0)
  • 2021-02-13 20:27

    So I have been playing around with Akka and Guice recently alot and I feel that those two don't play too well together.

    What I suggest is you take a similar approach what Play is doing.

    Kutschkem's answer comes closest to that.

    1. use the ActorCreator interface
    2. make sure you have an argumentless Creator. Don't try to do @AssisstedInject in your Creator as this will imply that you will need a new creator for every Actor that you want to create. Personally I believe that initializing this in the actor is better done through messaging.
    3. let the ActorCreator consume an injector such that you can easily create the Actor Object within the Creator.

    Here is a code example using current Akka 2.5. This is the preferred setup we chose for our Akka 2.5 deployment. For brevity I did not provide the Module, but it should be clear from the way the Members are injected, what you want to provide.

    Code:

     class ActorCreator implements Creator<MyActor>
       @Inject
       Injector injector;
       public MyActor create() {
         return injector.getInstance(MyActor.class);
       }
     }
    
     class MyActor extends AbstractActor {
       @Inject
       SomeController object;
    
       @Nullable
       MyDataObject data;
    
       public ReceiveBuilder createReceiveBuilder() {
        return receiveBuilder()
          .match(MyDataObject.class, m -> { /* doInitialize() */ })
          .build(); 
       }
    }
    
    class MyParentActor extends AbstractActor {
       @Inject
       ActorCreator creator;
    
       void createChild() {
         getContext().actorOf(new Props(creator));
       }
    
       void initializeChild(ActorRef child, MyDataObject obj) {
         child.tell(obj);
       }
    }
    
    0 讨论(0)
  • 2021-02-13 20:31

    Use an akka Creator:

    public class GuiceCreator<T> implements Creator<T> {
     Class<T> clz;
     Module module;
     /*Constructor*/
    
     public T create() {
        Injector injector = Guice.createInjector(this.module);
        return injector.getInstance(this.clz);
      }
    }
    

    Then use Props.create with your shiny new guice-based creator.

    Disclaimer: I don't actually know Akka, the mentioned information comes from browsing the documentation and JavaDoc.

    0 讨论(0)
  • 2021-02-13 20:43

    Unless you are trying to bind UntypedActor to FizzActor, then you can just inject it into other classes as is:

    class SomeOtherClass {
    
        @Inject 
        public SomeOtherClass(FizzActor fizzActor) {
            //do stuff
        }
    }
    

    If you're trying to bind it to the interface, you'll need to specifically do that in the module:

    public class MyActorSystemModule extends AbstractModule {
        @Override
        public void configure() {
            bind(MyService.class).to(MyServiceImpl.class);
            bind(UntypedActor.class).to(FizzActor.class);
        }
    }
    

    Edit:

    What about using @Named to distinguish the UntypedActor, e.g.:

    class SomeOtherClass {
    
        @Inject 
        public SomeOtherClass(@Named("fizzActor")UntypedActor fizzActor, @Named("fooActor") UntypedActor fooActor) {
            //do stuff
        }
    }
    

    Then in your module you could do the akka lookups:

    public class MyActorSystemModule extends AbstractModule {
    
        ActorSystem system = ActorSystem.create("MySystem");
    
        @Override
        public void configure() {
            bind(MyService.class).to(MyServiceImpl.class);
        }
    
        @Provides
        @Named("fizzActor")
        public UntypedActor getFizzActor() {
            return system.actorOf(Props.create(FizzActor.class), "fizzActor");
        }
    
        @Provides
        @Named("fooActor")
        public UntypedActor getFooActor() {
            return system.actorOf(Props.create(FooActor.class), "fooActor");
        }
    }
    
    0 讨论(0)
  • 2021-02-13 20:44

    Generic Akka Guice integration without dependency on Play, keeping in mind, not the only actor should be created in the actor system.

    import akka.actor.Actor;
    import akka.actor.ActorRef;
    import akka.actor.ActorSystem;
    import com.google.inject.AbstractModule;
    import com.google.inject.Provider;
    import com.google.inject.name.Names;
    
    public abstract class AkkaGuiceModule extends AbstractModule {
    
        protected <T extends Actor> void bindActor(Class<T> actorClass, String name) {
            bind(actorClass);
    
            Provider<ActorSystem> actorSystemProvider = getProvider(ActorSystem.class);
            Provider<T> actorProvider = getProvider(actorClass);
    
            bind(ActorRef.class)
                    .annotatedWith(Names.named(name))
                    .toProvider(ActorRefProvider.of(actorSystemProvider, actorProvider, name))
                    .asEagerSingleton();
        }
    
    }
    

    Generic ActorRefProvider to create ActorRef for each Actor

    import akka.actor.Actor;
    import akka.actor.ActorRef;
    import akka.actor.ActorSystem;
    import akka.actor.Props;
    import akka.japi.Creator;
    import com.google.inject.Provider;
    import lombok.Value;
    
    @Value(staticConstructor = "of")
    public class ActorRefProvider<T extends Actor> implements Provider<ActorRef> {
    
        private Provider<ActorSystem> actorSystemProvider;
        private Provider<T> actorProvider;
        private String name;
    
        public final class ActorCreator implements Creator<Actor> {
            @Override
            public Actor create() {
                return actorProvider.get();
            }
        }
    
        @Override
        public ActorRef get() {
            return actorSystemProvider.get().actorOf(Props.create(new ActorCreator()), name);
        }
    
    }
    

    Usage example

    import akka.actor.ActorSystem;
    import com.google.inject.Provides;
    import com.typesafe.config.Config; // optional
    
    public class MyAkkaModule extends AkkaGuiceModule {
    
        @Provides
        @Singleton
        ActorSystem actorSystem(Config config) {
            return ActorSystem.create("actor-system-name", config);
        }
    
        @Override
        protected void configure() {
            bindActor(SomeActor1.class, "actorName1");
            bindActor(SomeActor2.class, "actorName2");
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题