Actions as Controller

Actions can be used to implement MVC Controllers by acting as the external interface of a service, receiving requests, operating over the requests values and forwarding the call to other components.

Defining the proto file

To illustrate how you can use an Action as a Controller, we will build on top of the Value Entity Counter example and add an Action that will take the incoming Increase command, double its value and forward the request to a Counter entity.

Note that in this example we are adding an Action to an existing service, the Value Entity Counter service.

Java
src/main/proto/com/example/actions/double-counter.proto
syntax = "proto3";
package com.example.actions;

import "akkaserverless/annotations.proto";
import "com/example/counter_api.proto"; (1)
import "google/protobuf/empty.proto";

option java_outer_classname = "DoubleCounterApi";

service DoubleCounter {
  option (akkaserverless.service) = {
    type : SERVICE_TYPE_ACTION  (2)
  };

  rpc Increase (com.example.IncreaseValue) returns (google.protobuf.Empty); (3)

}
1 Import the Counter API definition
2 The protobuf option (akkaserverless.service) is specific to code-generation as provided by the Akka Serverless Maven plugin. This annotation indicates to the code-generation that an Action must be generated.
3 The Action mimics the Counter API
Scala
src/main/proto/com/example/actions/double-counter.proto
syntax = "proto3";
package com.example.actions;

import "akkaserverless/annotations.proto";
import "com/example/counter_api.proto"; (1)
import "google/protobuf/empty.proto";

service DoubleCounter {
  option (akkaserverless.service) = {
    type : SERVICE_TYPE_ACTION  (2)
  };

  rpc Increase (com.example.IncreaseValue) returns (google.protobuf.Empty); (3)

}
1 Import the Counter API definition
2 The protobuf option (akkaserverless.service) is specific to code-generation as provided by the Akka Serverless sbt plugin. This annotation indicates to the code-generation that an Action must be generated.
3 The Action mimics the Counter API

Implementing the Action

The class DoubleCounterAction gets generated for us based on the proto file defined above.

Java
src/main/java/com/example/actions/DoubleCounterAction.java
/**
 * An action.
 */
public class DoubleCounterAction extends AbstractDoubleCounterAction {

  private final ServiceCallRef<CounterApi.IncreaseValue> increaseCallRef;

  public DoubleCounterAction(ActionCreationContext creationContext) {
    this.increaseCallRef =
        creationContext.serviceCallFactory() (1)
            .lookup(
                "com.example.CounterService",
                "Increase",
                CounterApi.IncreaseValue.class);
  }

  /**
   * Handler for "Increase".
   */
  @Override
  public Effect<Empty> increase(CounterApi.IncreaseValue increaseValue) {
    int doubled = increaseValue.getValue() * 2;
    CounterApi.IncreaseValue increaseValueDoubled =
        increaseValue.toBuilder().setValue(doubled).build(); (2)

    return effects()
            .forward(increaseCallRef.createCall(increaseValueDoubled)); (3)
  }


}
1 In the constructor, we use the ActionCreationContext to create a increaseCallRef pointing to the Increase method in the Counter entity.
2 On incoming requests, we double the value of IncreaseValue
3 We use the increaseCallRef to build a ServiceCall that is then passed to effects().forward() method.
Scala
src/main/scala/com/example/actions/DoubleCounterAction.scala
/** An action. */
class DoubleCounterAction(creationContext: ActionCreationContext) extends AbstractDoubleCounterAction {

  private val increaseCallRef =
    creationContext.serviceCallFactory.lookup( (1)
      "com.example.CounterService",
      "Increase",
      classOf[IncreaseValue])

  /** Handler for "Increase". */
  override def increase(increaseValue: IncreaseValue): Action.Effect[Empty] = {
    val doubled = increaseValue.value * 2
    val increaseValueDoubled = increaseValue.copy(value = doubled) (2)

    effects.forward(increaseCallRef.createCall(increaseValueDoubled)) (3)
  }


}
1 We use the ActionCreationContext to create a increaseCallRef pointing to the Increase method in the Counter entity.
2 On incoming requests, we double the value of IncreaseValue
3 We use the increaseCallRef to build a ServiceCall that is then passed to effects.forward() method.

Registering the Action

To make Akka Serverless aware of the Action, we need to register it with the service.

From the code-generation, the registration gets automatically inserted in the generated AkkaServerlessFactory.withComponents method from the Main class.

When we add an Action to an existing service, like we did here, the Main class needs to be adapted. In this example, we build on top of the Value Entity Counter example. Therefore, the generated AkkaServerlessFactory.withComponents is now accepting two registrations: the Counter and the DoubleCounterAction, and we need to adapt it.

Java
/src/main/java/com/example/Main.java
public final class Main {

  private static final Logger LOG = LoggerFactory.getLogger(Main.class);
  public static AkkaServerless createAkkaServerless() {
    // The AkkaServerlessFactory automatically registers any generated Actions, Views or Entities,
    // and is kept up-to-date with any changes in your protobuf definitions.
    // If you prefer, you may remove this and manually register these components in a
    // `new AkkaServerless()` instance.
    return AkkaServerlessFactory.withComponents(
            Counter::new,
            DoubleCounterAction::new);
  }

  public static void main(String[] args) throws Exception {
    LOG.info("starting the Akka Serverless service");
    createAkkaServerless().start();
  }
}
Scala
/src/main/scala/com/example/fibonacci/Main.scala
object Main {

  private val log = LoggerFactory.getLogger("com.example.Main")

  def createAkkaServerless(): AkkaServerless = {
    // The AkkaServerlessFactory automatically registers any generated Actions, Views or Entities,
    // and is kept up-to-date with any changes in your protobuf definitions.
    // If you prefer, you may remove this and manually register these components in a
    // `AkkaServerless()` instance.
    AkkaServerlessFactory.withComponents(
      new Counter(_),
      new DoubleCounterAction(_))
  }

  def main(args: Array[String]): Unit = {
    log.info("starting the Akka Serverless service")
    createAkkaServerless().start()
  }
}