Akka with Google Guice

Anita Karacs
Supercharge's Digital Product Guide
9 min readMar 8, 2019

--

This article discusses the combination of Akka’s actor model and dependency injection using Google Guice. It assumes that you are familiar with the basics of the Akka framework, and the example will show the power of Guice.

It is very frequent that you want to use replaceable components in your application. This is useful when you want to initialise a service at a higher level, or create unit tests and mock the behaviour of a service.

If you use dependency injection in your Akka application it would be also a great idea to build the whole actor hierarchy using DI; however, that is not so simple as actors always belong to a parent and have a specific lifecycle. In the following you will see a solution for injecting actors at the top level as well as creating child actors while keeping the parent context. We use:

  • Akka 2.5.9 (Java DSL, Scala version 2.12)
  • Java 8
  • Guice 4.0

In this example, we will have an actor which saves cars into a database, and test it with a mocked service.

Database service

public final class CarDatabaseCassandraService implements CarDatabaseService {    public void connect() {
//implement cassandra connection
}
public void insertCar(Car carEntity) {
//implement insert
}
public List<Car> selectCars() {
//implement select
return new ArrayList();
}
public void close() {
//implement connection closing
}
}

You can use any kind of database; right now we have an empty implementation of CarDatabaseService as our focus is on testing and replacing the components. Let’s call this database service CarDatabaseCassandraService. This should contain the logic of connecting to a database and CRUD actions.

Actors

public class CarRepositoryActor extends AbstractActor {    private final CarDatabaseService carDatabaseService; 
@Inject
public CarRepositoryActor(CarDatabaseService carDatabaseService) {
this.carDatabaseService = carDatabaseService;
}

@Override
public void preStart() throws Exception {
super.preStart();
this.carDatabaseService.connect();
}

@Override
public void postStop() throws Exception {
super.postStop();
this.carDatabaseService.close();
}

@Override
public Receive createReceive() {
return receiveBuilder()
.match(InsertCarCommand.class, command -> insertCar(command, getSender()))
.match(SelectCarCommand.class, command -> selectAllCars(getSender()))
.build();
}

private void insertCar(InsertCarCommand command, ActorRef sender) {
carDatabaseService.insertCar(command.getCarEntity());
sender.tell(“Success”, getSelf());
}

private void selectAllCars(ActorRef sender) {
sender.tell(carDatabaseService.selectCars(), getSelf());
}
}

CarRepositoryActor only communicates with the database service. You meet Guice here at first time with the @Inject annotation.

Google Guice supports setter-based, field-based, and constructor-based injection. In our example, we will use the last one.

This actor is only responsible for saving the given Car object into the database, and retrieving all cars. We need another service which adds additional business logic on our car data.

public class CarManagerActor extends AbstractActor implements InjectedActorSupport {    private final ActorRef carRepositoryActor;    
@Inject
public CarManagerActor(@Named(“car-repository-actor-factory”) ChildInjectionFactory childFactory) {
this.carRepositoryActor = injectedChild(childFactory, “car-repository-actor”);
}

@Override
public Receive createReceive() {
return receiveBuilder()
.match(InsertCarCommand.class, command -> insertCar(command))
.match(SelectCarCommand.class, command -> selectAllCars(getSender()))
.build();
}

private void insertCar(InsertCarCommand command) {
//do some businesslogic
carRepositoryActor.tell(command, getSelf());
}

private void selectAllCars(ActorRef sender) {
//do some businesslogic
PatternsCS.ask(carRepositoryActor, new SelectCarCommand(), timeout)
.thenAccept(modifiedResult -> sender.tell(modifiedResult, getSelf()));
}
}

CarManagerActor is the parent of CarRepositoryActor, which can be injected using Guice. The challenge here is to keep the actor hierarchy while adding CarRepositoryActor to the injection system to be able to inject CarDatabaseService by constructor. To achieve this, we have InjectedActorSupport and ChildInjectionFactory. (Note that we don’t inject the actual child actor, but a factory. This way, the concrete class of the child actor can be replaced at module level.) But before introducing these interfaces let’s create the actor system and CarManagerActor.

Main application

public class CarApplication {    private final ActorSystem actorSystem;
private final ActorRef carManagerActor;
private final Timeout timeout;
@Inject
public CarApplication(ActorSystem actorSystem, @Named(“car-manager-actor”) ActorRef carManagerActor) {
this.actorSystem = actorSystem;
this.carManagerActor = carManagerActor;
this.timeout = Timeout.apply(5, TimeUnit.SECONDS);
}

public CompletionStage start(List<String> carNames) {
try {
for (String carName : carNames) {
Car carEntity = new Car(carName);
carManagerActor.tell(new InsertCarCommand(carEntity), ActorRef.noSender());
}
return PatternsCS.ask(carManagerActor, new SelectCarCommand(), timeout); } finally {
actorSystem.terminate();
}
}
}

If you want to call your application, you need to reach it in the following way:
CarApplication application = new CarApplicationModule().getInjector().getInstance(CarApplication.class);

So you need to define the module which contains the bindings:

public final class CarApplicationModule extends AbstractActorProviderModule {    private final ActorSystem actorSystem;     
private CarApplicationModule() {
this.actorSystem = ActorSystem.create();
injector = Guice.createInjector(this);
}

@Override
protected void configure() {
bind(ActorSystem.class).toInstance(actorSystem);
bind(CarDatabaseService.class).toInstance(new CarDatabaseCassandraService());
bindActor(actorSystem, CarManagerActor.class, “car-manager-actor”);
bindActorChildFactory(CarRepositoryActor.class, “car-repository-actor-factory”);
}
}

For implementing the binding service, Guice provides the AbstractModule abstract class, and we need to implement the configure() method. Actually, it can be found in the AbstractActorProviderModule, which provides the actor creation and a common injector. When the CarApplicationModule() constructor calls Guice.createInjector(this), the configure() method runs and creates the singletons. You can see that the actor system is created here, and bindActor will use it to create actors at the top level. CarDatabaseService is now bound to CarDatabaseCassandraService implementation, but can be easily replaced to another one. Now let’s see how Guice works with actors.

Dependency injection with Guice

The following code has some common parts with the Play framework.

public abstract class AbstractActorProviderModule extends AbstractModule {    protected Injector injector;    public AbstractActorProviderModule() {
ActorProviderModuleFactory.registerInstance(this);
}
public Injector getInjector() {
return injector;
}

public ActorRef createInjectedActor(ActorRefFactory parentContext, String name, Class<? extends Actor> actorClass, Function<Props, Props> props) {
return new ActorRefProvider(parentContext, name, actorClass, props).get();
}

protected void bindActor(ActorSystem system, Class<? extends Actor> actorClass, String name) {
bind(ActorRef.class)
.annotatedWith(Names.named(name))
.toProvider(Providers.guicify(new ActorRefProvider(system, name, actorClass)));
}
protected void bindActorChildFactory(Class<? extends Actor> childActorClass, String name) { bind(ChildInjectionFactory.class)
.annotatedWith(Names.named(name))
.toInstance(new ChildInjectionFactory(childActorClass));
}

private class ActorRefProvider implements Provider<ActorRef> {
private final ActorRefFactory parentContext;
private final String name;
private final Class<? extends Actor> actorClass;
private final Function<Props, Props> props;
ActorRefProvider(ActorRefFactory parentContext, String name, Class<? extends Actor> actorClass) { this(parentContext, name, actorClass, Function.identity());
}

ActorRefProvider(ActorRefFactory parentContext, String name, Class<? extends Actor> actorClass, Function<Props, Props> props) {
this.name = name;
this.actorClass = actorClass;
this.parentContext = parentContext;
this.props = props;
}

@Override
public ActorRef get() {
Props appliedProps = props.apply(Props.create(GuiceInjectedActor.class, injector, actorClass));
return parentContext.actorOf(appliedProps, name);
}
}
}

This provider class is quite huge, so let’s discuss the rows one-by-one.

As mentioned earlier the abstract class extends Guice’s AbstractModule.

The module needs a common injector for all bindings to reach the singletons. That’s why we have a protected injector with getter.

In the constructor, it registers the self instance. This way, the child injection will reach the currently used module, which contains the information on how and where to inject the child.

The createInjectedActor() method is responsible for creating child actors. It will be reached in InjectedActorSupport.

The bindActor() method extends binding with the first-level actor creation. It passes the actor system as the parent context, adds name to Named annotation, and binds the ActorRefProvider with the actor class.

The bindActorChildFactory() method binds the child factory to the exact child actor class by name.

The ActorRef creation is wrapped in the ActorRefProvider class, which implements javax.inject.Provider. This is responsible for creating the ActorRef in the right context with the given props, using parentContext.actorOf(appliedProps, name). It uses another class to create the child actors using the same binding service, which is why we need Props.create(GuiceInjectedActor.class, injector, actorClass).

public class GuiceInjectedActor implements IndirectActorProducer {    private final Injector injector;
private final Class<? extends Actor> concreteActorClass;

public GuiceInjectedActor(Injector injector, Class<? extends Actor> concreteActorClass) {
this.injector = injector;
this.concreteActorClass = concreteActorClass;
}

@Override
public Class<? extends Actor> actorClass() {
return concreteActorClass;
}

@Override
public Actor produce() {
return injector.getInstance(concreteActorClass);
}
}

This implements IndirectActorProducer, which allows the use of any dependency injection framework. In the produce(), we can instantiate the concrete actor class with the injector of the application, so the constructor-based injection will work.

Child actor injection

If you would also like to create the actor hierarchy by dependency injection, then two more classes are needed.

public interface InjectedActorSupport {    default ActorRef injectedChild(ChildInjectionFactory factory, String name, Function<Props, Props> props) {        return ActorProviderModuleFactory.getInstance().createInjectedActor(context(), name, factory.getChildActorClass(), props);
}

default ActorRef injectedChild(ChildInjectionFactory factory, String name) {
return injectedChild(factory, name, Function.identity());
}
ActorContext context();}public class ChildInjectionFactory { private final Class<? extends Actor> childActorClass;
public ChildInjectionFactory(Class<? extends Actor> childActorClass) {
this.childActorClass = childActorClass;
}

public Class<? extends Actor> getChildActorClass() {
return childActorClass;
}
}

As you may have noticed CarManagerActor implements InjectedActorSupport, and creates the child actor with injectedChild(). This is how the child acquires the parent context. The method is also able to create the actor with specific props like RoundRobinPool or BackoffSupervisor.

Test it — with MockedCarDatabaseService

Since we want to test the application but don’t want to read from or write to a real database, we can create a mocked CarDatabaseService.

public final class MockedCarDatabaseService implements CarDatabaseService {    private List<Car> carList;
public void connect() {
carList = new ArrayList();
}

public void insertCar(Car carEntity) {
carList.add(carEntity);
}

public List<Car> selectCars() {
return carList;
}

public void close() {
carList.clear();
}
}

Now bind the mocked database service in the test module.

public final class CarApplicationTestModule extends AbstractActorProviderModule {    private final ActorSystem actorSystem;    
public CarApplicationTestModule() {
this.actorSystem = ActorSystem.create();
injector = Guice.createInjector(this);
}

@Override
protected void configure() {
bind(ActorSystem.class).toInstance(actorSystem);
bind(CarDatabaseService.class).toInstance(new MockedCarDatabaseService());
bindActor(actorSystem, CarManagerActor.class, “car-manager-actor”);
bindActorChildFactory(CarRepositoryActor.class, “car-repository-actor-factory”);
}
}public class CarApplicationTest { @Test
public void test() {
CarApplication application = new CarApplicationTestModule().getInjector().getInstance(CarApplication.class); Thread.sleep(3000);
CompletionStage resultStage = application.start(Arrays.asList("Ferrari"));
resultStage.thenApplyAsync(result -> {
if (result instanceof List && !((List) result).isEmpty() && ((List) result).get(0) instanceof Car) {
List<Car> resultCars = (List<Car>) result;
assertEquals("Ferrari", resultCars.get(0).getName());
}

return null;
});
}}

Test it — with MockedCarRepositoryActor

In the actor model, the actor is a unit, and you can create unit test by mocking an actor. This way, you don’t need to mock the database service, just the repository actor.

public class MockedCarRepositoryActor extends AbstractActor {    private ActorRef testActor;
@Override
public Receive createReceive() {
return initTestActor();
}

private Receive initTestActor() {
return receiveBuilder()
.match(ActorRef.class, actorRef -> {
this.testActor = actorRef;
getContext().become(receiveOriginalActorCommand());
}).build();
}

private Receive receiveOriginalActorCommand() {
return receiveBuilder()
.match(InsertCarCommand.class, command -> testActor.tell(“CarRepository with InsertCarCommand has been called”, getSelf()))
.match(SelectCarCommand.class, command -> {
testActor.tell(“CarRepository with SelectCarCommand has been called”, getSelf());
getSender().tell(Arrays.asList(new Car(“Kia”), new Car(“Fiat”)), getSelf());
})
.build();
}
}public final class CarManagerActorTestModule extends AbstractActorProviderModule { private final ActorSystem actorSystem;
public CarManagerActorTestModule(ActorSystem actorSystem) {
this.actorSystem = actorSystem;
injector = Guice.createInjector(this);
}

@Override
protected void configure() {
bindActor(actorSystem, CarManagerActor.class, “car-manager-actor”);
bindActorChildFactory(MockedCarRepositoryActor.class, “car-repository-actor-factory”);
}
}public class CarManagerActorTest { private ActorSystem system;
private TestKit testActor;
private TestKit testCarRepositoryActor;

@Inject
@Named(“car-manager-actor”)
private ActorRef testCarManagerActor;

@Before
public void setup() throws InterruptedException {
system = ActorSystem.create();
testActor = new TestKit(system);
testCarRepositoryActor = new TestKit(system);
new CarManagerActorTestModule(system).getInjector().injectMembers(this); Thread.sleep(3000); ActorRef carRepositoryActor = system.actorFor(“user/car-manager-actor/car-repository-actor”);
carRepositoryActor.tell(testCarRepositoryActor.getRef(), testActor.getRef());
}

@After
public void cleanUp() {
TestKit.shutdownActorSystem(system);
system = null;
}

@Test
public void testInsertCar() {
Car carEntity = new Car(“Renault”);
testCarManagerActor.tell(new InsertCarCommand(carEntity), testActor.getRef());
testCarRepositoryActor.expectMsg(“CarRepository with InsertCarCommand has been called”);
}

@Test
public void testSelectCars() {
testCarManagerActor.tell(new SelectCarCommand(), testActor.getRef());
testCarRepositoryActor.expectMsg(“CarRepository with SelectCarCommand has been called”); Object response = testActor.receiveOne(Duration.Inf());
assertTrue(response instanceof List);
assertEquals(2, ((List) response).size());
Car car1 = (Car)((List) response).get(0);
Car car2 = (Car)((List) response).get(1);
assertEquals(“Kia”, car1.getName());
assertEquals(“Fiat”, car2.getName());
}
}

I’m going to assume that you are familiar with the Akka Testkit, which can simulate communication between actors in an easily readable way. In this unit test, you can test whether the CarRepositoryActor was called or not, but it won’t try to save any cars or retrieve them from a database. This test also demonstrates how field-based injection works (you just need to inject the current instance to the injector).

It’s a kind of hacky, but unfortunately we have to wait a few seconds to get the actor system ready, which is why we need the Thread.sleep(). The magic part comes after that: we get the actor by name from the actor system and replace it with our mocked actor! This will replace the behaviour of the original actor.

Conclusion

Although it generates loads of code, the goal has been reached, and now Akka is testable with dependency injection.

You can find the full source code on Github:
https://github.com/team-supercharge/akka-guice-example

At Supercharge we build high impact digital products that make life easier for millions of users. If you liked this article, check out some of Supercharge’s other articles on our blog, and follow us on LinkedIn, and Facebook. If you’re interested in open positions, follow this link.

--

--