Documentation

You are viewing the documentation for the 2.6.0-M4 development release. The latest stable release series is 3.0.x.

§Integrating with Akka

Akka uses the Actor Model to raise the abstraction level and provide a better platform to build correct concurrent and scalable applications. For fault-tolerance it adopts the ‘Let it crash’ model, which has been used with great success in the telecoms industry to build applications that self-heal - systems that never stop. Actors also provide the abstraction for transparent distribution and the basis for truly scalable and fault-tolerant applications.

§The application actor system

Akka can work with several containers called actor systems. An actor system manages the resources it is configured to use in order to run the actors which it contains.

A Play application defines a special actor system to be used by the application. This actor system follows the application life-cycle and restarts automatically when the application restarts.

§Writing actors

To start using Akka, you need to write an actor. Below is a simple actor that simply says hello to whoever asks it to.

package actors;

import akka.actor.*;
import actors.HelloActorProtocol.*;

public class HelloActor extends UntypedActor {

    public static Props getProps() {
        return Props.create(HelloActor.class);
    }

    public void onReceive(Object msg) throws Exception {
        if (msg instanceof SayHello) {
            sender().tell("Hello, " + ((SayHello) msg).name, self());
        }
    }
}

Notice here that the HelloActor defines a static method called getProps, this method returns a Props object that describes how to create the actor. This is a good Akka convention, to separate the instantiation logic from the code that creates the actor.

Another best practice shown here is that the messages that HelloActor sends and receives are defined as static inner classes of another class called HelloActorProtocol:

package actors;

public class HelloActorProtocol {

    public static class SayHello {
        public final String name;

        public SayHello(String name) {
            this.name = name;
        }
    }
}

§Creating and using actors

To create and/or use an actor, you need an ActorSystem. This can be obtained by declaring a dependency on an ActorSystem, then you can use the actorOf method to create a new actor.

The most basic thing that you can do with an actor is send it a message. When you send a message to an actor, there is no response, it’s fire and forget. This is also known as the tell pattern.

In a web application however, the tell pattern is often not useful, since HTTP is a protocol that has requests and responses. In this case, it is much more likely that you will want to use the ask pattern. The ask pattern returns a Scala Future, which you can convert to a Java CompletionStage using scala.compat.java8.FutureConverts.toJava, and then map to your own result type.

Below is an example of using our HelloActor with the ask pattern:

import akka.actor.*;
import play.mvc.*;
import scala.compat.java8.FutureConverters;
import javax.inject.*;
import java.util.concurrent.CompletionStage;

import static akka.pattern.Patterns.ask;

@Singleton
public class Application extends Controller {

    final ActorRef helloActor;

    @Inject public Application(ActorSystem system) {
        helloActor = system.actorOf(HelloActor.getProps());
    }

    public CompletionStage<Result> sayHello(String name) {
        return FutureConverters.toJava(ask(helloActor, new SayHello(name), 1000))
                .thenApply(response -> ok((String) response));
    }
}

A few things to notice:

§Dependency injecting actors

If you prefer, you can have Guice instantiate your actors and bind actor refs to them for your controllers and components to depend on.

For example, if you wanted to have an actor that depended on the Play configuration, you might do this:

import akka.actor.UntypedActor;
import com.typesafe.config.Config;

import javax.inject.Inject;

public class ConfiguredActor extends UntypedActor {

    private Config configuration;

    @Inject
    public ConfiguredActor(Config configuration) {
        this.configuration = configuration;
    }

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof ConfiguredActorProtocol.GetConfig) {
            sender().tell(configuration.getString("my.config"), self());
        }
    }
}

Play provides some helpers to help providing actor bindings. These allow the actor itself to be dependency injected, and allows the actor ref for the actor to be injected into other components. To bind an actor using these helpers, create a module as described in the dependency injection documentation, then mix in the AkkaGuiceSupport interface and use the bindActor method to bind the actor:

import com.google.inject.AbstractModule;
import play.libs.akka.AkkaGuiceSupport;

public class MyModule extends AbstractModule implements AkkaGuiceSupport {
    @Override
    protected void configure() {
        bindActor(ConfiguredActor.class, "configured-actor");
    }
}

This actor will both be named configured-actor, and will also be qualified with the configured-actor name for injection. You can now depend on the actor in your controllers and other components:

import akka.actor.ActorRef;
import play.mvc.*;
import scala.compat.java8.FutureConverters;

import javax.inject.Inject;
import javax.inject.Named;
import java.util.concurrent.CompletionStage;

import static akka.pattern.Patterns.ask;

public class Application extends Controller {

    private ActorRef configuredActor;

    @Inject
    public Application(@Named("configured-actor") ActorRef configuredActor) {
       this.configuredActor = configuredActor;
    }

    public CompletionStage<Result> getConfig() {
        return FutureConverters.toJava(ask(configuredActor,
                        new ConfiguredActorProtocol.GetConfig(), 1000)
        ).thenApply(response -> ok((String) response));
    }
}

§Dependency injecting child actors

The above is good for injecting root actors, but many of the actors you create will be child actors that are not bound to the lifecycle of the Play app, and may have additional state passed to them.

In order to assist in dependency injecting child actors, Play utilises Guice’s AssistedInject support.

Let’s say you have the following actor, which depends configuration to be injected, plus a key:

import akka.actor.UntypedActor;
import com.google.inject.assistedinject.Assisted;
import com.typesafe.config.Config;

import javax.inject.Inject;

public class ConfiguredChildActor extends UntypedActor {

    private final Config configuration;
    private final String key;

    @Inject
    public ConfiguredChildActor(Config configuration, @Assisted String key) {
        this.configuration = configuration;
        this.key = key;
    }

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof ConfiguredChildActorProtocol.GetConfig) {
            sender().tell(configuration.getString(key), self());
        }
    }
}

In this case we have used constructor injection - Guice’s assisted inject support is only compatible with constructor injection. Since the key parameter is going to be provided on creation, not by the container, we have annotated it with @Assisted.

Now in the protocol for the child, we define a Factory interface that takes the key and returns the Actor:

import akka.actor.Actor;

public class ConfiguredChildActorProtocol {

    public static class GetConfig {}

    public interface Factory {
        public Actor create(String key);
    }
}

We won’t implement this, Guice will do that for us, providing an implementation that not only passes our key parameter, but also locates the Configuration dependency and injects that. Since the trait just returns an Actor, when testing this actor we can inject a factor that returns any actor, for example this allows us to inject a mocked child actor, instead of the actual one.

Now, the actor that depends on this can extend InjectedActorSupport, and it can depend on the factory we created:

import akka.actor.ActorRef;
import akka.actor.UntypedActor;
import play.libs.akka.InjectedActorSupport;

import javax.inject.Inject;

public class ParentActor extends UntypedActor implements InjectedActorSupport {

    private ConfiguredChildActorProtocol.Factory childFactory;

    @Inject
    public ParentActor(ConfiguredChildActorProtocol.Factory childFactory) {
        this.childFactory = childFactory;
    }

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof ParentActorProtocol.GetChild) {
            String key = ((ParentActorProtocol.GetChild) message).key;
            ActorRef child = injectedChild(() -> childFactory.create(key), key);
            sender().tell(child, self());
        }
    }
}

It uses the injectedChild to create and get a reference to the child actor, passing in the key. The second parameter (key in this example) will be used as the child actor’s name.

Finally, we need to bind our actors. In our module, we use the bindActorFactory method to bind the parent actor, and also bind the child factory to the child implementation:

import com.google.inject.AbstractModule;
import play.libs.akka.AkkaGuiceSupport;

public class MyModule extends AbstractModule implements AkkaGuiceSupport {
    @Override
    protected void configure() {
        bindActor(ParentActor.class, "parent-actor");
        bindActorFactory(ConfiguredChildActor.class,
            ConfiguredChildActorProtocol.Factory.class);
    }
}

This will get Guice to automatically bind an instance of ConfiguredChildActorProtocol.Factory, which will provide an instance of Configuration to ConfiguredChildActor when it’s instantiated.

§Configuration

The default actor system configuration is read from the Play application configuration file. For example, to configure the default dispatcher of the application actor system, add these lines to the conf/application.conf file:

akka.actor.default-dispatcher.fork-join-executor.pool-size-max = 64
akka.actor.debug.receive = on

For Akka logging configuration, see configuring logging.

§Changing configuration prefix

In case you want to use the akka.* settings for another Akka actor system, you can tell Play to load its Akka settings from another location.

play.akka.config = "my-akka"

Now settings will be read from the my-akka prefix instead of the akka prefix.

my-akka.actor.default-dispatcher.fork-join-executor.pool-size-max = 64
my-akka.actor.debug.receive = on

§Built-in actor system name

By default the name of the Play actor system is application. You can change this via an entry in the conf/application.conf:

play.akka.actor-system = "custom-name"

Note: This feature is useful if you want to put your play application ActorSystem in an akka cluster.

§Executing a block of code asynchronously

A common use case within Akka is to have some computation performed concurrently without needing the extra utility of an Actor. If you find yourself creating a pool of Actors for the sole reason of performing a calculation in parallel, there is an easier (and faster) way:

import play.mvc.*;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

public class Application extends Controller {
    public CompletionStage<Result> index() {
        return CompletableFuture.supplyAsync(this::longComputation)
                .thenApply((Integer i) -> ok("Got " + i));
    }
}

§Scheduling asynchronous tasks

You can schedule sending messages to actors and executing tasks (functions or Runnable instances). You will get a Cancellable back that you can call cancel on to cancel the execution of the scheduled operation.

For example, to send a message to the testActor every 30 minutes:

system.scheduler().schedule(
    Duration.create(0, TimeUnit.MILLISECONDS), //Initial delay 0 milliseconds
    Duration.create(30, TimeUnit.MINUTES),     //Frequency 30 minutes
    testActor,
    "tick",
    system.dispatcher(),
    null
);

Alternatively, to run a block of code ten milliseconds from now:

system.scheduler().scheduleOnce(
    Duration.create(10, TimeUnit.MILLISECONDS),
    () -> file.delete(),
    system.dispatcher()
);

Next: Internationalization with Messages