Open Credo

June 24, 2016 | Software Consultancy

Many improvements in Java 8 API for Akka

Akka has been designed with a Java API from the very first version. Though widely adopted, as a Java developer I think Akka has been mainly a Scala thing… until recently. Things are changing and Akka is moving to a proper Java 8 support.

WRITTEN BY

Lorenzo Nicora

Lorenzo Nicora

Many improvements in Java 8 API for Akka

A Java 8 API for Akka

Akka
Due to Java syntax limitations, the Java API built over the Scala implementation was, at least, clumsy. With version 2.3.0 (on March 2014) Akka introduced a Java 8 API, called lambda support. More recently (Akka 2.4.2, on February 2016) they started replacing scala.concurrent.Future with java.util.concurrent.CompletableFuture.

Let’s walk through some of the improvements that are making Akka much more Java developer friendly.

AbstractActor and receive message loop

Actors may now extend AbstractActor, and implement message handling logic in a receive(...) block, defined inside the constructor.

// New Java 8 API version
public class Barista extends AbstractLoggingActor {
    private Barista() {
        final CoffeeMachine coffeeMachine = new CoffeeMachine();
        log().info("The coffee machine is ready");

        receive(
            match(Order.class, order -> {
                log().info("Received an order for {}", order);

                final Coffee coffee = coffeeMachine.prepare(order.type);
                log().info("Your {} is ready", coffee);

                sender().tell(coffee, self());
            })
            .matchAny(this::unhandled)
            .build()
        );
    }
    public static Props props() {
        return Props.create(Barista.class, () -> new Barista());
    }
}

This clearly mimics Scala pattern matching. It pushes Java syntax to the limit (I still cannot convince my IntelliJ to format the receive block properly…), to create a sort of DSL, but the result is pretty clear. Much, much better than the verbosity of the pre-Java8 interface:

// Old Java API version
public class OldBarista extends UntypedActor {
    private LoggingAdapter log = Logging.getLogger(getContext().system(), this);

    private final CoffeeMachine coffeeMachine;

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof Order) {
            Order order = (Order) message;
            log.info("Received an order for {}", order);

            Coffee coffee = coffeeMachine.prepare(order.type);
            log.info("Your {} is ready", coffee);

            getSender().tell(coffee, getSelf());
        } else {
            unhandled(message);
        }
    }

    public static Props props() {
        return Props.create(new Creator() {
            private static final long serialVersionUID = 1L;

            @Override
            public OldBarista create() throws Exception {
                return new OldBarista();
            }
        });
    }

    private OldBarista() {
        coffeeMachine = new CoffeeMachine();
        log.info("The coffee machine is ready");
    }
}

In my example above, I extended AbstractLoggingActor, instead of AbstractActor, just to avoid creating the LoggingAdapter.

Behaviour HotSwap: become and unbecome

To change the message handling behaviour dynamically you have to create one or more PartialFunction defining different behaviours. The syntax is no different from defining the main behaviour in a receive(...) block:

// New Java 8 API
private PartialFunction behaviour =
   match(Order.class, order -> {
       // ... process order ...
   })

The former interface forced you to create an anonymous class, with the usual clutter:

// Old Java API
Procedure behaviour = new Procedure() {   @Override   public void apply(Object message) {     if (message instanceof Order) {         // ...process order...     }   }};

Finite State Machine

FSM, is a frequently used pattern with Actors:State(S) x Event(E) -> Action(A), State(S')...or, rewording Akka documentation:If an Actor is in state S and receives a message E, it should perform the action A and make a transition to state S'.The pre-Java 8 Java Actor interface has no support for FSM. You have to implement it by hand, applying a lot of discipline. The lambda-support provides a specialised AbstractFSM superclass, with a quasi-DSL for matching combinations of Event (received message) and State. It also isolate creation and handling of a transition (goto(State), stay()...) and the transition logic (onTransition(...)). Better, extending AbstractLoggingFSM  gives you more than a pre-defined logger. Beside automatically logging all event and state transitions, it also keeps a rolling log of the last events, amazingly useful when debugging.Akka documentation contains a very simple example of FSM.

CompletableFuture to replace Scala Future

Another recent important improvement is the gradual replacement of Scala Future with proper Java 8 CompletableFuture throughout the Java API. The authors of Akka were forced to leak Scala Standard Library elements into Java interface, to go beyond the Java Standard Library limits. As a Java developer, coping with scala.Future is a hacky experience, regardless the Futures helper class.For example, a new helper class PatternsCS had been recently introduced, to implement the very useful ask pattern (asynchronous request/response), piping the asynchronous result to another actor without blocking, using CompletableFuture. It replaces Patterns that uses scala.concurrent.Future.Consider you have a Java method (not an actor) returning and asynchronous result:
public CompletableFuture prepare(Coffee.CoffeeType type) {   CompletableFuture eventualCoffee = new CompletableFuture<>();   // Good cooking takes time. If you are made to wait, it is to serve you better, and to please you.   return eventualCoffee;}

An actor using this method may pipe the result, after potentially adding more processing stages, with a clean syntax:

receive(   match(Order.class, order -> {       pipe(coffeeMachine.prepare(order.type)           .thenApply(coffee -> {               // ... additional preparation stages...               return coffee;           }), context().dispatcher())           .to(sender(), self());   }));

Still some Scala Future around

Unfortunately, the migration to CompletableFuture is not... complete, yet. There is still some Scala Future around, remarkably in the CircuitBreaker:

receive(   match(Order.class, order -> {       scala.concurrent.Future response = circuitBreaker           .callWithCircuitBreaker( () -> coffeeMachine.prepare(order.type) );        pipe( response, getContext().system().dispatcher() ).to( sender() );    }));

Note that pipe(...) method here is from akka.pattern.Patterns, while in the previous example it was from the newer akka.pattern.PatternsCS.

[Updated after publishing]
With a prompt reaction, Akka team has added support for CircuitBreakers using CompletableFuture. It will be probably included in the next Akka release.
Thanks, Konrad!

Code examples

Code examples used in this post (and a bit more): https://github.com/opencredo/akka-java8-examples

Wrapping up

Akka is moving to a proper Java 8 interface. In this article, I just skimmed some of the changes to the Java API. More have been introduced, radically changing the interface. For example, all the Akka Persistence event-sourcing support have been rewritten.

As of today (Akka version 2.4.7), the migration is not complete. The lambda support is still marked as experimental, so you may still expect breaking changes even through minor releases. I think Lightbend (formerly Typesafe), the company governing Akka development, is steering toward Java and improvements of Akka Java API are a direct consequence. From a Java developer point of view, these improvements are making Akka much more friendly that it used to be.

As a Java developer, you may still see Scala under the hood. Akka is written in Scala. Akka authors think in Scala, so the Java 8 interface looks very Scala-like. Some of the syntax choices appear quirky, to the eye of a Java developer.
But, after all, this is useful. The more similar the two interface are, the easier is for Java developer to use the Akka community content, answer, blogposts, examples, mostly written in Scala.

 

RETURN TO BLOG

SHARE

Twitter LinkedIn Facebook Email

SIMILAR POSTS

Blog